using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using IOB_UT_NEXT;
using MTConnect;
using MTConnect.Assets;
using MTConnect.Clients;
using MTConnect.Devices;
using MTConnect.Observations;
using MTConnect.Streams;
using NLog;
using Spectre.Console;

namespace IOB_WIN_MTC_CLI
{
    public class IobComm
    {
        #region Public Constructors

        /// <summary>
        /// Init classe
        /// </summary>
        public IobComm()
        {
        }

        /// <summary>
        /// Init Classe
        /// </summary>
        /// <param name="opcAddr"></param>
        public IobComm(string opcAddr)
        {
            baseUrl = opcAddr;
        }

        /// <summary>
        /// Init Classe
        /// </summary>
        /// <param name="opcAddr"></param>
        /// <param name="titolo"></param>
        /// <param name="sottotitolo"></param>
        public IobComm(string opcAddr, string titolo, string sottotitolo)
        {
            baseUrl = opcAddr;
            title = titolo;
            subtitle = sottotitolo;
        }

        #endregion Public Constructors

        #region Public Methods

        public void DoConfig()
        {
            msgData = new List<string>();
            dispData = new Dictionary<string, double>();

            // primo display
            msgData.Add("Starting CLI setup...");
            DoDisplayAsync();
        }

        /// <summary>
        /// Esecuzione log secondo conf
        /// </summary>
        /// <param name="msg"></param>
        public void doLog(string msg = "")
        {
            try
            {
                if (fileLog)
                {
                    lg.Info($"{msg}");
                }

                if (consoleLog)
                {
                    Console.WriteLine(msg);
                }
            }
            catch (Exception exc)
            {
                Console.WriteLine($"Eccezione in log:{Environment.NewLine}{exc}");
            }
        }

        /// <summary>
        /// Esecuzione probe call indipendente
        /// </summary>
        public void ForceProbe()
        {
            // init
            sw.Restart();
            msgData = new List<string>();
            dispData = new Dictionary<string, double>();

            // primo display
            msgData.Add("Calling PROBE...");
            DoDisplayAsync();

            // inizio processing
            var prbClient = new MTConnectHttpProbeClient(baseUrl);
            prbClient.Timeout = 20000;
            var prbDoc = prbClient.Get();
            // preparo output
            if (prbDoc == null)
            {
                msgData.Add($"Preliminari Probe | {sw.ElapsedMilliseconds}ms | No answer, continue...");
            }
            else
            {
                displayProbeDoc("Preliminari Probe RECEIVED", prbDoc);
            }
        }

        public void MainLoop()
        {
            sw.Restart();
            msgData = new List<string>();
            dispData = new Dictionary<string, double>();

            ConsoleKeyInfo answ = Console.ReadKey();
            while (answ.Key != ConsoleKey.Escape)
            {
                msgData.Add($"CONSOLE LOG: {consoleLog}");
                msgData.Add($"FILE LOG: {fileLog}");
                DoDisplayAsync();

                answ = Console.ReadKey();
                // controllo key...
                if (answ.Key == ConsoleKey.C)
                {
                    consoleLog = !consoleLog;
                }
                else if (answ.Key == ConsoleKey.F)
                {
                    fileLog = !fileLog;
                }
                else if (answ.Key == ConsoleKey.P)
                {
                    msgData = new List<string>();
                    dispData = new Dictionary<string, double>();
                    ForceProbe();
                }
                else if (answ.Key == ConsoleKey.R)
                {
                    msgData = new List<string>();
                    dispData = new Dictionary<string, double>();
                    client.Stop();
                    Task.Delay(500);
                    dtStart = DateTime.Now;
                    StartClient();
                    msgData.Add($"Client restarted!");
                }
                else if (answ.Key == ConsoleKey.S)
                {
                    msgData = new List<string>();
                    dispData = new Dictionary<string, double>();
                    client.Stop();
                    msgData.Add($"Client stopped!");
                }
            }
            doLog("...closing Now!");
        }

        private DateTime displayVeto = DateTime.Now;

        /// <summary>
        /// Avvio del thread principale
        /// </summary>
        public void StartClient()
        {
            // init
            sw.Restart();
            msgData = new List<string>();
            dispData = new Dictionary<string, double>();

            // primo display
            msgData.Add("Client App setup...");
            DoDisplayAsync();

            // setup client
            client = new MTConnectHttpClient(baseUrl);
            // sample interval
            client.Interval = 500;
            // timeout x richieste
            client.Timeout = 4000;
            // timeout riconnessione
            client.ReconnectionInterval = 5000;

            // aggancio eventi
            if (enabAsset)
            {
                client.AssetsReceived += Client_AssetsReceived;
            }
            client.ClientStarted += Client_ClientStarted;
            client.ClientStopped += Client_ClientStopped;
            client.ConnectionError += Client_ConnectionError;
            client.CurrentReceived += Client_CurrentReceived;
            client.ProbeReceived += Client_ProbeReceived;
            if (enabSample)
            {
                client.SampleReceived += Client_SampleReceived;
            }
            if (enabObserv)
            {
                // conviene usare sample x avere la stessa cosa ma con altre info...
                client.ObservationReceived += Client_ObservationReceived;
            }

            msgData.Add($"Setup completed! | {sw.ElapsedMilliseconds}ms");
            DoDisplayAsync();

            // vero avvio
            client.Start();
            msgData.Add($"Client Started | {sw.ElapsedMilliseconds} ms");
            DoDisplayAsync();
        }

        #endregion Public Methods

        #region Protected Fields

        protected bool enabAsset = true;
        protected bool enabObserv = false;
        protected bool enabSample = true;

        #endregion Protected Fields

        #region Protected Properties

        protected bool consoleLog { get; set; } = false;

        protected bool fileLog { get; set; } = true;

        #endregion Protected Properties

        #region Protected Methods

        /// <summary>
        /// Main display function
        /// </summary>
        /// <param name="msgList"></param>
        /// <param name="graphData"></param>
        protected async Task DoDisplayAsync()
        {
            // attesa che non sia in corso update
            while (inRefresh || displayVeto > DateTime.Now)
            {
                //Thread.Sleep(rand.Next(50, 150));
                await Task.Delay(rand.Next(50, 150));
            }

            try
            {
                inRefresh = true;
                displayVeto = DateTime.Now.AddMilliseconds(1000);
                AnsiConsole.Clear();

                // titolo
                AnsiConsole.Write(
                    new FigletText(title)
                    .LeftJustified()
                    .Color(Color.Cyan1));

                var rule = new Rule(subtitle);
                rule.RightJustified();
                AnsiConsole.Write(rule);

                // se presenti messaggi
                if (msgData.Count > 0)
                {
                    foreach (var msg in msgData)
                    {
                        if (fileLog)
                        {
                            lg.Info(msg);
                        }
                        AnsiConsole.MarkupLine(msg);
                    }
                    rule = new Rule();
                    rule.RuleStyle("grey dim");
                    AnsiConsole.Write(rule);
                }

                // se presenti graph...
                if (dispData.Count > 0)
                {
                    foreach (var graph in dispData)
                    {
                    }
                    rule = new Rule();
                    rule.RuleStyle("grey dim");
                    AnsiConsole.Write(rule);
                }

                // comandi
                if (commandList.Count > 0)
                {
                    // mostro cmd list...
                    foreach (var cmd in commandList)
                    {
                        AnsiConsole.MarkupLine(cmd);
                    }

                    rule = new Rule();
                    rule.RuleStyle("grey dim");
                    AnsiConsole.Write(rule);
                }
                string uptime = $"Uptime: {DateTime.Now.Subtract(dtStart).TotalMinutes:N1} min";
                AnsiConsole.WriteLine(uptime);
            }
            catch (Exception exc)
            {
                Console.WriteLine($"Eccezione in DoDisplay:{Environment.NewLine}{exc}");
            }
            inRefresh = false;
        }

        #endregion Protected Methods

        #region Private Fields

        private DateTime dtStart = DateTime.Now;

        private static Logger lg = LogManager.GetCurrentClassLogger();

        private string baseUrl = "192.168.1.27:5000";

        private List<string> commandList = new List<string>() {
            "R = Restart Service | S = Stop Service | P = force probe",
            "C = toggle console log | F = toggle file log | Press ESC to exit"
        };

        /// <summary>
        /// Dizionario variabili conteggio collezionate
        /// </summary>
        private Dictionary<string, int> DataCounters = new Dictionary<string, int>();

        private Dictionary<string, double> dispData = new Dictionary<string, double>();
        private bool inRefresh = false;
        private List<string> msgData = new List<string>();
        private Random rand = new Random();
        private string sep = "------------------------";

        private string subtitle = "EgalWare 2024+";

        private Stopwatch sw = new Stopwatch();

        private string title = "MtConnect Client";

        #endregion Private Fields

        #region Private Properties

        private MTConnectHttpClient client { get; set; } = new MTConnectHttpClient("");

        #endregion Private Properties

        #region Private Methods

        private void Client_AssetsReceived(object sender, MTConnect.Assets.IAssetsResponseDocument document)
        {
            displayAsset(document);
        }

        private void Client_ClientStarted(object sender, EventArgs e)
        {
            msgData = new List<string>();
            msgData.Add($"Client Started!");
            msgData.Add(sep);
            DoDisplayAsync();
        }

        private void Client_ClientStopped(object sender, EventArgs e)
        {
            msgData = new List<string>();
            msgData.Add($"Client Stopped!");
            msgData.Add(sep);
            DoDisplayAsync();
        }

        private void Client_ConnectionError(object sender, Exception exc)
        {
            msgData = new List<string>();
            msgData.Add($"Connection ERROR returned!");
            msgData.Add($"{exc}");
            msgData.Add(sep);
            DoDisplayAsync();
        }

        private void Client_CurrentReceived(object sender, MTConnect.Streams.IStreamsResponseDocument document)
        {
            displayCurrent(document);
        }

        private void Client_ObservationReceived(object sender, IObservation observ)
        {
            msgData = new List<string>();
            msgData.Add("OBSERVATION");
            msgData.Add(sep);
            processObservation(observ);
        }

        private void Client_ProbeReceived(object sender, MTConnect.Devices.IDevicesResponseDocument document)
        {
            displayProbeDoc("Client Probe RECEIVED", document);
        }

        private void Client_SampleReceived(object sender, MTConnect.Streams.IStreamsResponseDocument document)
        {
            displaySample(document);
        }

        private void displayAsset(IAssetsResponseDocument document)
        {
            msgData = new List<string>();
            int numAssets = 0;
            msgData.Add("ASSET received");
            doLog(sep);
            foreach (var asset in document.Assets)
            {
                // Print AssetId to the Console
                doLog($"Asset: {asset.AssetId}");
                numAssets++;
            }
            msgData.Add($" Found [green]{numAssets} Assets[/]");

            DoDisplayAsync();
        }

        private void displayCurrent(IStreamsResponseDocument document)
        {
            msgData = new List<string>();
            int numStream = 0;
            int numCompo = 0;
            msgData.Add($"CURRENT received!");

            foreach (var deviceStream in document.Streams)
            {
                // DeviceStream
                msgData.Add($"  Stream: {deviceStream.Name}");
                numStream++;

                // Component Streams
                foreach (var componentStream in deviceStream.ComponentStreams)
                {
                    processData(componentStream);
                    numCompo++;
                }
            }
            msgData.Add($" Found [green]{numStream} Streams[/]");
            msgData.Add($" Found [yellow]{numCompo} Components[/]");

            DoDisplayAsync();
        }

        private void displayProbeDoc(string message, IDevicesResponseDocument prbDoc)
        {
            msgData = new List<string>();
            msgData.Add(message);
            msgData.Add(sep);

            foreach (var device in prbDoc.Devices)
            {
                // Device
                msgData.Add($"DEV: {device.Id}");
                int numDataItem = 0;
                int numComponents = 0;
                int numCompositions = 0;

                // All DataItems (traverse the entire Device model)
                if (device.DataItems.Count() > 0)
                {
                    foreach (var dataItem in device.GetDataItems())
                    {
                        lg.Trace($" DItm | {dataItem.IdPath}");
                        numDataItem++;
                    }
                }
                msgData.Add($" Found [green]{numDataItem} DataItems[/]");

                // All Components (traverse the entire Device model)
                if (device.Components.Count() > 0)
                {
                    foreach (var component in device.GetComponents())
                    {
                        lg.Trace($" Comp | {component.IdPath}");
                        numComponents++;
                    }
                }
                msgData.Add($" Found [blue]{numComponents} Components[/]");

                // All Compositions (traverse the entire Device model)
                if (device.Compositions.Count() > 0)
                {
                    foreach (var composition in device.GetCompositions())
                    {
                        lg.Trace($" Cpst | {composition.IdPath}");
                        numCompositions++;
                    }
                }
                msgData.Add($" Found [yellow]{numCompositions} Compositions[/]");
            }
            msgData.Add(sep);

            DoDisplayAsync();
        }

        private void displaySample(IStreamsResponseDocument document)
        {
            msgData = new List<string>();
            int numStream = 0;
            int numCompo = 0;
            msgData.Add($"SAMPLE");

            foreach (var deviceStream in document.Streams)
            {
                // DeviceStream
                msgData.Add($"  Stream: {deviceStream.Name}");
                numStream++;

                // Component Streams
                foreach (var componentStream in deviceStream.ComponentStreams)
                {
                    processData(componentStream);
                    numCompo++;
                }
            }
            msgData.Add($" Found [green]{numStream} Streams[/]");
            msgData.Add($" Found [yellow]{numCompo} Components[/]");

            DoDisplayAsync();
        }

        private void processData(IComponentStream dataStream)
        {
            int numCondition = 0;
            int numEvent = 0;
            int numSample = 0;
            // DataItems (Samples, Events, and Conditions)
            foreach (var observation in dataStream.Observations)
            {
                processObservation(observation);
                switch (observation.Category)
                {
                    case DataItemCategory.CONDITION:
                        numCondition++;
                        break;

                    case DataItemCategory.EVENT:
                        numEvent++;
                        break;

                    case DataItemCategory.SAMPLE:
                        numSample++;
                        break;

                    default:
                        break;
                }
            }
            msgData.Add($"    Component {dataStream.Name} | {numCondition} Conditions | {numEvent} Events | {numSample} Samples");
        }

        private void processObservation(IObservation observation)
        {
            string sMsg = $" - {observation.DataItemId} | {observation.Category}";
#if false
            switch (observation.Category)
            {
                case MTConnect.Devices.DataItemCategory.CONDITION:
                    break;

                case MTConnect.Devices.DataItemCategory.EVENT:
                    break;

                case MTConnect.Devices.DataItemCategory.SAMPLE:
                    break;

                default:
                    break;
            }
#endif
            if (observation.Values != null && observation.Values.Count() > 0)
            {
                foreach (var item in observation.Values)
                {
                    sMsg += $" | {item.Key}: {item.Value}";
                }
            }
            doLog(sMsg);
        }

        #endregion Private Methods
    }
}