using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Timers;
using NLog;
using Timer = System.Timers.Timer;

namespace ATxCommon.Monitoring
{
    public class Cpu
    {
        private static readonly Logger Log = LogManager.GetCurrentClassLogger();

        private readonly Timer _monitoringTimer;
        private readonly PerformanceCounter _cpuCounter;
        private readonly float[] _loadReadings = {0F, 0F, 0F, 0F};

        private float _load;
        private int _interval;
        private int _limit;
        private int _behaving;
        private int _probation;
        private bool _enabled;


        /// <summary>
        /// Current CPU load (usage percentage over all cores).
        /// </summary>
        /// <returns>The average CPU load from the last four readings.</returns>
        public float Load() => _load;

        /// <summary>
        /// How often (in ms) to check the CPU load.
        /// </summary>
        public int Interval {
            get => _interval;
            set {
                _interval = value;
                _monitoringTimer.Interval = value;
                Log.Debug("CPU monitoring interval: {0}", _interval);
            }
        }

        /// <summary>
        /// Upper limit of CPU load (usage in % over all cores) before it is classified as "high".
        /// </summary>
        public int Limit {
            get => _limit;
            set {
                _limit = value;
                Log.Debug("CPU monitoring limit: {0}", _limit);
            }
        }

        /// <summary>
        /// Number of cycles where the CPU load value has to be below the limit before it is
        /// classified as "low" again.
        /// </summary>
        public int Probation {
            get => _probation;
            set {
                _probation = value;
                Log.Debug("CPU monitoring probation cycles when violating limit: {0}", _probation);
            }
        }
        
        /// <summary>
        /// Indicating whether the CPU load monitoring is active.
        /// </summary>
        public bool Enabled {
            get => _enabled;
            set => EnableTimer(value);
        }


        /// <summary>
        /// Create performance counter and initialize it.
        /// </summary>
        public Cpu() {
            _interval = 250;
            _limit = 25;
            _probation = 40;
            Log.Debug("Initializing CPU monitoring...");
            
            try {
                _cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
                Log.Debug("CPU monitoring initializing PerformanceCounter (takes 1s)...");
                _cpuCounter.NextValue();
                Thread.Sleep(1000);
                Log.Debug("CPU monitoring current load: {0}", _cpuCounter.NextValue());
                // _monitoringTimer = new Timer(_interval);
                _monitoringTimer = new Timer(_interval);
                _monitoringTimer.Elapsed += UpdateCpuLoad;
            }
            catch (Exception) {
                Log.Error("Initializing CPU monitoring failed!");
                throw;
            }

            Log.Debug("Initializing CPU monitoring completed.");
        }

        private void EnableTimer(bool enabled) {
            Log.Debug("{0} CPU monitoring.", enabled ? "Enabling" : "Disabling");
            _monitoringTimer.Enabled = enabled;
            _enabled = enabled;
        }

        private void UpdateCpuLoad(object sender, ElapsedEventArgs e) {
            _monitoringTimer.Enabled = false;
            try {
                Log.Trace("load values: {0}, average: {1}", string.Join(", ", _loadReadings), _load);
                // ConstrainedCopy seems to be the most efficient approach to shift the array:
                Array.ConstrainedCopy(_loadReadings, 1, _loadReadings, 0, 3);
                _loadReadings[3] = _cpuCounter.NextValue();
                _load = _loadReadings.Average();
                Log.Trace("load values: {0}, average: {1}", string.Join(", ", _loadReadings), _load);
                if (_loadReadings[3] > _limit) {
                    Log.Debug("CPU load ({0}) violating limit ({1})!", _loadReadings[3], _limit);
                    _behaving = 0;
                    // TODO: fire callback for violating load limit
                } else {
                    _behaving++;
                    if (_behaving == _probation) {
                        Log.Debug("CPU load below limit for {0} cycles, yay!", _probation);
                        // TODO: fire callback for load behaving well
                    } else if (_behaving > _probation) {
                        Log.Debug("CPU load behaving well since {0} cycles.", _behaving);
                    } else if (_behaving < 0) {
                        Log.Warn("Integer wrap around happened, resetting probation counter!");
                        _behaving = 0;
                    }
                }
            }
            catch (Exception ex) {
                Log.Error("UpdateCpuLoad failed: {0}", ex.Message);
            }
            finally {
                _monitoringTimer.Enabled = true;
            }
        }

    }
}