using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using SuperSocket.Common;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Metadata;

namespace SuperSocket.SocketEngine
{
    class ProcessPerformanceCounterHelper : IDisposable
    {
        private PerformanceCounter m_CpuUsagePC;
        private PerformanceCounter m_ThreadCountPC;
        private PerformanceCounter m_WorkingSetPC;
        private int m_CpuCores = 1;

        private Process m_Process;

        public ProcessPerformanceCounterHelper(Process process)
        {
            m_Process = process;
            m_CpuCores = Environment.ProcessorCount;

            //Windows .Net, to avoid same name process issue
            if (!Platform.IsMono)
                RegisterSameNameProcesses(process);

            SetupPerformanceCounters();
        }

        private void RegisterSameNameProcesses(Process process)
        {
            foreach (var p in Process.GetProcessesByName(process.ProcessName).Where(x => x.Id != process.Id))
            {
                p.EnableRaisingEvents = true;
                p.Exited += new EventHandler(SameNameProcess_Exited);
            }
        }

        //When find a same name process exit, re-initialize the performance counters
        //because the performance counters' instance names could have been changed
        void SameNameProcess_Exited(object sender, EventArgs e)
        {
            SetupPerformanceCounters();
        }

        private void SetupPerformanceCounters()
        {
            var isUnix = Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX;

            var instanceName = string.Empty;

            if (isUnix || Platform.IsMono)
                instanceName = string.Format("{0}/{1}", m_Process.Id, m_Process.ProcessName);
            else
                instanceName = GetPerformanceCounterInstanceName(m_Process);

            // the process has exited
            if (string.IsNullOrEmpty(instanceName))
                return;

            SetupPerformanceCounters(instanceName);
        }

        private void SetupPerformanceCounters(string instanceName)
        {
            m_CpuUsagePC = new PerformanceCounter("Process", "% Processor Time", instanceName);
            m_ThreadCountPC = new PerformanceCounter("Process", "Thread Count", instanceName);
            m_WorkingSetPC = new PerformanceCounter("Process", "Working Set", instanceName);
        }

        //This method is only used in windows
        private static string GetPerformanceCounterInstanceName(Process process)
        {
            var processId = process.Id;
            var processCategory = new PerformanceCounterCategory("Process");
            var runnedInstances = processCategory.GetInstanceNames();

            foreach (string runnedInstance in runnedInstances)
            {
                if (!runnedInstance.StartsWith(process.ProcessName, StringComparison.OrdinalIgnoreCase))
                    continue;

                if (process.HasExited)
                    return string.Empty;

                using (var performanceCounter = new PerformanceCounter("Process", "ID Process", runnedInstance, true))
                {
                    var counterProcessId = 0;                    

                    try
                    {
                        counterProcessId = (int)performanceCounter.RawValue;
                    }
                    catch //that process has been shutdown
                    {                        
                        continue;
                    }

                    if (counterProcessId == processId)
                    {
                        return runnedInstance;
                    }
                }
            }

            return process.ProcessName;
        }

        public void Collect(StatusInfoCollection statusCollection)
        {
            int availableWorkingThreads, availableCompletionPortThreads;
            ThreadPool.GetAvailableThreads(out availableWorkingThreads, out availableCompletionPortThreads);

            int maxWorkingThreads;
            int maxCompletionPortThreads;
            ThreadPool.GetMaxThreads(out maxWorkingThreads, out maxCompletionPortThreads);

            var retry = false;

            while (true)
            {
                try
                {
                    statusCollection[StatusInfoKeys.AvailableWorkingThreads] = availableWorkingThreads;
                    statusCollection[StatusInfoKeys.AvailableCompletionPortThreads] = availableCompletionPortThreads;
                    statusCollection[StatusInfoKeys.MaxCompletionPortThreads] = maxCompletionPortThreads;
                    statusCollection[StatusInfoKeys.MaxWorkingThreads] = maxWorkingThreads;
                    statusCollection[StatusInfoKeys.TotalThreadCount] = (int)m_ThreadCountPC.NextValue();
                    statusCollection[StatusInfoKeys.CpuUsage] = m_CpuUsagePC.NextValue() / m_CpuCores;
                    statusCollection[StatusInfoKeys.MemoryUsage] = (long)m_WorkingSetPC.NextValue();

                    break;
                }
                catch (InvalidOperationException e)
                {
                    //Only re-get performance counter one time
                    if (retry)
                        throw e;

                    //Only re-get performance counter for .NET/Windows
                    if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX || Platform.IsMono)
                        throw e;

                    //If a same name process exited, this process's performance counters instance name could be changed,
                    //so if the old performance counter cannot be access, get the performance counter's name again
                    var newInstanceName = GetPerformanceCounterInstanceName(m_Process);

                    if (string.IsNullOrEmpty(newInstanceName))
                        break;

                    SetupPerformanceCounters(newInstanceName);
                    retry = true;
                }
            }
        }

        public void Dispose()
        {
            if (m_CpuUsagePC != null)
            {
                m_CpuUsagePC.Close();
                m_CpuUsagePC = null;
            }

            if (m_ThreadCountPC != null)
            {
                m_ThreadCountPC.Close();
                m_ThreadCountPC = null;
            }

            if (m_WorkingSetPC != null)
            {
                m_WorkingSetPC.Close();
                m_WorkingSetPC = null;
            }
        }
    }
}