using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;

namespace LiteFccCoreMain
{
    public class PerformanceMonitor
    {
        //static System.Timers.Timer performanceLoggingTimer;
        public static ILogger Logger { get; set; }// = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("Performance");

        /// <summary>
        /// by seconds, default to 60, may get changed by different log level.
        /// </summary>
        static int samplingInterval = 60;
        private static TimeSpan? previousTotalCpuTime;
        private static void Linux_LoggingPerformanceCounter()
        {
            // list extra info only for RPi3B+
            //logger.Info(Bash("/opt/vc/bin/vcgencmd measure_temp"));
            //logger.Info(Bash("/opt/vc/bin/vcgencmd measure_clock arm"));
            //logger.Info(Bash("/opt/vc/bin/vcgencmd measure_volts core"));

            // list all processes in RPi3B+
            //logger.Info(Bash("/usr/bin/top -n 1"));
            double cpuUsage = 0;
            var proc = Process.GetCurrentProcess();
            if (previousTotalCpuTime.HasValue)
            {
                cpuUsage = proc.TotalProcessorTime
                    .Subtract(previousTotalCpuTime.Value).TotalSeconds / samplingInterval;
            }

            previousTotalCpuTime = proc.TotalProcessorTime;

            var workingSet = proc.WorkingSet64;
            var npSystemMem = proc.NonpagedSystemMemorySize64;
            var pagedMem = proc.PagedMemorySize64;
            var pagedSysMem = proc.PagedSystemMemorySize64;
            var privateMem = proc.PrivateMemorySize64;
            var virMem = proc.VirtualMemorySize64;
            var cpu = proc.TotalProcessorTime;

            Logger.LogInformation("Proc: " + proc.ProcessName + "(Id: " + proc.Id + "), "
                + "CPU%: " + cpuUsage.ToString("0.##%")
                + ", workingSet: " + (workingSet / 1024.0) + "K, "
                + "nonPagedSysMem: " + (npSystemMem / 1024.0) + "K, "
                + "pagedMem: " + (pagedMem / 1024.0) + "K, "
                + "pagedSysMem: " + (pagedSysMem / 1024.0) + "K, "
                + "privateMem: " + (privateMem / 1024.0) + "K, "
                + "virMem: " + (virMem / 1024.0) + "K, "
                + "CPU total time(ms): " + cpu.TotalMilliseconds
                + ", handle: " + proc.HandleCount + ", thread: " + proc.Threads.Count);
        }

        private static void Windows_LoggingPerformanceCounter()
        {
            double cpuUsage = 0;
            var proc = Process.GetCurrentProcess();
            if (previousTotalCpuTime.HasValue)
            {
                cpuUsage = proc.TotalProcessorTime
                    .Subtract(previousTotalCpuTime.Value).TotalSeconds / samplingInterval;
            }

            previousTotalCpuTime = proc.TotalProcessorTime;

            int availableThreads, availableIOThreads;
            ThreadPool.GetAvailableThreads(out availableThreads, out availableIOThreads);
            var currentProcessRunningInfo = Windows_GetProcessRunningInformation(proc.ProcessName);
            currentProcessRunningInfo.Add("CPU%", cpuUsage.ToString("0.##%"));
            currentProcessRunningInfo.Add("Avai Threads-IoThreads", availableThreads.ToString() + "-" + availableIOThreads.ToString());
            Logger.LogInformation(currentProcessRunningInfo.Select(s => s.Key + ": " + s.Value)
                .Aggregate((p, n) => p + ", " + n));
        }

        private static Dictionary<string, string> Windows_GetProcessRunningInformation(string processName)
        {
            var returnDic = new Dictionary<string, string>();
            Process operatedProcess = null;

            try
            {
                Process[] processes = System.Diagnostics.Process.GetProcessesByName(processName);
                // if process found, then only pick the first one as default, or set with default Null value
                if (processes.Length > 0)
                {
                    operatedProcess = processes[0];
                }

                if (operatedProcess != null && !operatedProcess.HasExited)
                {
                    returnDic.Add("ProcName", operatedProcess.ProcessName + "(Id:" + operatedProcess.Id + ")");
                    returnDic.Add("VM", operatedProcess.VirtualMemorySize64.ToString());
                    returnDic.Add("WS", operatedProcess.WorkingSet64.ToString());
                    returnDic.Add("PM", operatedProcess.PrivateMemorySize64.ToString());
                    returnDic.Add("Thread", operatedProcess.Threads.Count.ToString());
                    returnDic.Add("Handle", operatedProcess.HandleCount.ToString());
                    //returnDic.Add("Start time", operatedProcess.StartTime.ToString("HH:mm:ss dd-MM-yyyy"));
                }
                else
                {
                    returnDic.Add("Process " + processName, "not existed");
                }
            }
            catch (Exception ex)
            {
                Logger.LogInformation("Exception occured in PerformanceMonitor, detail: " + ex.ToString());
                returnDic.Add("GetProcessRunningInformation error", ex.ToString());
            }

            return returnDic;
        }

        public static void Start()
        {
            /* as in some case of Threadpool drain out, the System.Timer for logging the counters may get paused,
             * so here using a dedicate thread to do it.
             *
             */
            Thread dedicatedLoopThread = new Thread(() =>
            {
                while (true)
                {
                    try
                    {
                        if (Environment.OSVersion.Platform == PlatformID.Unix)
                            Linux_LoggingPerformanceCounter();
                        else
                            Windows_LoggingPerformanceCounter();
                        if (Logger.IsEnabled(LogLevel.Debug))
                            samplingInterval = 2;
                        else
                            samplingInterval = 60;
                        Thread.Sleep(1000 * samplingInterval);
                    }
                    catch (Exception exxx)
                    {
                        Logger.LogError("Unexpected exception: " + exxx);
                    }
                }
            });

            int minThreads, minIoThreads, maxThreads, maxIoThreads;
            ThreadPool.GetMinThreads(out minThreads, out minIoThreads);
            ThreadPool.GetMaxThreads(out maxThreads, out maxIoThreads);
            Logger.LogInformation("Performance Logging is starting, ThreadPool MinThreads count: " + minThreads + ", MinIoThreads count: " + minIoThreads
                + ", MaxThreads count: " + maxThreads + ", MaxIoThreads count: " + maxIoThreads);
            dedicatedLoopThread.Start();
        }

        public static string Bash(string cmd)
        {
            var escapedArgs = cmd.Replace("\"", "\\\"");

            var process = new Process()
            {
                StartInfo = new ProcessStartInfo
                {
                    FileName = "/bin/bash",
                    Arguments = $"-c \"{escapedArgs}\"",
                    RedirectStandardOutput = true,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                }
            };
            process.Start();
            string result = process.StandardOutput.ReadToEnd();
            process.WaitForExit();
            return result;
        }
    }
}