using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Threading;
using System.IO;
using System.Threading.Tasks;

namespace iSTP_DataProcessing
{
    /// <summary>
    /// Execute scripts for SPS mode
    /// </summary>
    public class SPSscripts
    {
        /// <summary>
        /// Infomration about one script
        /// </summary>
        public struct ExeInfo_t
        {
            public uint number;
            public string name;
            public List<string> parameters;
            public bool isLast;
            public uint afterCycles;
            public bool isParamCycleRoot;
            public bool isParamRoot;
            public string[] Copies;
        }
        /// <summary>
        /// Infomration about all script from one file
        /// </summary>
        public struct Sets_t
        {
            public string name;
            public List<ExeInfo_t> scripts;
            public bool isActive;
        }

        /// <summary>
        /// Information about all scripts files
        /// </summary>
        static public List<Sets_t> Sets = new List<Sets_t>();
        static private bool isInitComplete = false;
        static public Dictionary<string, string> TempList = new Dictionary<string, string>(1000);
        /// <summary>
        /// Wait time between sets [seconds]
        /// </summary>
        static public int TaskWaitTime = 30 * 60;

        static List<string> SPSdestMemory = new List<string>(); 

        private enum PhaseRead_e
        { 
            Exe,
            Param
        }

        public readonly string RootFolder_exe = "SPS" + Path.DirectorySeparatorChar;

        /// <summary>
        /// Find and parse all script defined files: *script*.txt
        /// </summary>
        public SPSscripts()
        {
            if (!isInitComplete)
            {
                ///-----------
                ///Load all scripts & SPStempList
                const string partialName = "script";
                const string fullName = "SPStempList";

                DirectoryInfo hdDirectoryInWhichToSearch = new DirectoryInfo(RootFolder_exe);
                if (hdDirectoryInWhichToSearch.Exists)
                {
                    FileInfo[] filesInDir = hdDirectoryInWhichToSearch.GetFiles(string.Format("*{0}*.txt", partialName));

                    foreach (FileInfo foundFile in filesInDir)
                    {
                        Sets_t _set = new Sets_t() { name = foundFile.FullName };
                        ReadScriptFile(ref _set, _set.name);
                        Sets.Add(_set);
                    }

                    filesInDir = hdDirectoryInWhichToSearch.GetFiles(string.Format("{0}.csv", fullName));
                    foreach (FileInfo foundFile in filesInDir)
                    {
                        StreamReader f = new StreamReader(foundFile.FullName);
                        while (!f.EndOfStream)
                        {
                            string[] partial = f.ReadLine().Split(',');
                            if (partial.Length == 2)
                            {
                                TempList.Add(partial[0], partial[1]);
                            }
                        }
                    }

                    isInitComplete = true;
                }

            }
        }

        /// <summary>
        /// Parse current script defined file
        /// </summary>
        /// <param name="aSets">Reference to memory</param>
        /// <param name="aFilename">name of file</param>
        private void ReadScriptFile(ref Sets_t aSets, string aFilename)
        {
            aSets.scripts = new List<ExeInfo_t>();
            StreamReader _rfile = new StreamReader(aFilename);
            string[] _scripts = _rfile.ReadToEnd().Split(Environment.NewLine.ToCharArray());
            List<string> _cleaner = new List<string>();
            for (uint i = 0; i < _scripts.Length; i++)
            {
                if (_scripts[i] != "")
                    _cleaner.Add(_scripts[i]);
                if((i + 1) < _scripts.Length)
                {
                    if (_scripts[i] == "" && _scripts[i + 1] == "")
                        _cleaner.Add(_scripts[i]);
                }
            }
            _scripts = _cleaner.ToArray();
            PhaseRead_e _phase = PhaseRead_e.Exe;
            ExeInfo_t _exe = new ExeInfo_t();

            for(uint i = 0; i < _scripts.Length; i++)
            {
                switch (_phase)
                { 
                    case PhaseRead_e.Exe:
                        if (_scripts[i] != "")
                        {
                            _exe = new ExeInfo_t();

                            string[] _afterDot = _scripts[i].Split('.');
                            if (IsDigitsOnly(_afterDot[0]))
                            {
                                _exe.number = Convert.ToUInt32(_afterDot[0]);
                                _exe.name = _afterDot[1] + ".exe";
                                _exe.isLast = false;
                                _exe.parameters = new List<string>();
                                _phase = PhaseRead_e.Param;
                            }
                            else
                            {
                                _exe.number = Convert.ToUInt32(_afterDot[2]);
                                _exe.name = _afterDot[3] + ".exe";
                                _exe.isLast = true;
                                _exe.parameters = new List<string>();
                                _exe.afterCycles = Convert.ToUInt32(_afterDot[1]);
                                _phase = PhaseRead_e.Param;
                            }
                            if (_scripts[i].Contains(","))
                            {
                                _exe.Copies = _scripts[i].Split(',');
                            }
                            if ((i + 1) >= _scripts.Length)
                            {
                                _exe.isParamRoot = true;
                                aSets.scripts.Add(_exe);
                                _phase = PhaseRead_e.Exe;
                            }                           
                        }
                        break;
                    case PhaseRead_e.Param:
                        string[] _afterSep = _scripts[i].Split(Path.DirectorySeparatorChar);
                        if (_afterSep == null)
                        { 
                            //Root
                            _exe.isParamCycleRoot = false;
                            _exe.isParamRoot = true;
                            _phase = PhaseRead_e.Exe;
                            aSets.scripts.Add(_exe);
                        }

                        _afterSep = _afterSep.Where(val => val != "").ToArray();
                        if (_afterSep.Length == 0)
                        {
                            //cycle
                            _exe.isParamRoot = false;
                            _exe.isParamCycleRoot = true;
                            _phase = PhaseRead_e.Exe;
                            aSets.scripts.Add(_exe);
                        }
                        else
                        {
                            _exe.isParamCycleRoot = false;
                            _exe.isParamRoot = false;
                            _exe.parameters.Add(_scripts[i]);
                            if ((i + 1) < _scripts.Length)
                            {
                                if (_scripts[i + 1] == "")
                                {
                                    _phase = PhaseRead_e.Exe;
                                    aSets.scripts.Add(_exe);
                                }
                            }
                            else
                            {
                                _phase = PhaseRead_e.Exe;
                                aSets.scripts.Add(_exe);
                            }
                        }
                        break;
                }

            }
        }

        /// <summary>
        /// Verify only digits chars in string
        /// </summary>
        /// <param name="str">String</param>
        /// <returns>TRUE - only digits</returns>
        private bool IsDigitsOnly(string str)
        {
            foreach (char c in str)
            {
                if (c < '0' || c > '9')
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Execute one script
        /// </summary>
        /// <param name="progName">Script name</param>
        /// <param name="progPath">Script path</param>
        /// <param name="arg">Agruments</param>
        /// <param name="cyclePath">Path to cycle</param>
        /// <param name="ct">Cancelation token</param>
        /// <param name="processID">ID of this process</param>
        /// <returns>state of Task</returns>
        private Task<bool> StartProcess(string progName, string progPath, string[] arg, string cyclePath, CancellationToken ct, out int processID)
        {
            var tcs = new TaskCompletionSource<bool>();
            CancellationTokenRegistration ctr = new CancellationTokenRegistration();

            string workplace = "";
            for (uint i = 0; i < arg.Length; i++)
            {
                workplace += arg[i];
                if ((i + 1) < arg.Length)
                    workplace += " ";
            }

            ProcessStartInfo info = new ProcessStartInfo(progPath + progName) 
                { 
                    CreateNoWindow = false, 
                    UseShellExecute = false, 
                    RedirectStandardOutput = true, 
                    RedirectStandardError = false, 
                    Arguments = workplace, 
                    WorkingDirectory = cyclePath
                };
            Process proc = new Process() { EnableRaisingEvents = true, StartInfo = info };

            proc.Exited += (sender, args) =>
            {
                tcs.TrySetResult((proc.ExitCode == 0) ? true : false);

                proc.Dispose();
                ctr.Dispose();
            };
            ctr = ct.Register(() => tcs.TrySetCanceled());

            proc.Start();

            processID = proc.Id;

            return tcs.Task;
        }

        /// <summary>
        /// Run scripts. Script param-isLast must be True
        /// </summary>
        /// <param name="aRoot">Path to root folder</param>
        /// <param name="aPathCycle">Path to actual cycles folder</param>
        /// <param name="aSet">Active set</param>
        public void Run(string aRoot, string aPathCycle, List<ExeInfo_t> aSet)
        {
            string[] _def = null;
            List<string> _local = new List<string>();
            List<int> _processesIDs = new List<int>();
            uint _max = 0;
            foreach (ExeInfo_t exe in aSet)
                if (exe.number > _max)
                    _max = exe.number;
            for (int i = 1; i <= _max; i++)
            {
                CancellationTokenSource cTs = new CancellationTokenSource();
                CancellationToken token = cTs.Token;
                List<Task<bool>> _taskList = new List<Task<bool>>();
                try
                {
                    foreach (ExeInfo_t exe in aSet)
                    {
                        _local = exe.parameters;
                        _def = exe.parameters.ToArray();
                        if (exe.number == i && !exe.isLast)
                        {
                            if (exe.isParamCycleRoot)
                                _local.Add(aPathCycle);
                            else if (exe.isParamRoot)
                                _local.Add(aRoot);
                            else
                            {
                                for (int j = 0; j < _local.Count; j++)
                                {
                                    _local[j] = aPathCycle + _local[j];
                                }
                            }

                            if (exe.Copies != null)
                            {
                                if (exe.Copies.Length > 1)
                                {
                                    string[] _file2copy = new string[exe.Copies.Length - 1];
                                    for (int h = 1; h < exe.Copies.Length; h++)
                                    {
                                        _file2copy[h - 1] = exe.Copies[h];
                                    }

                                    foreach (string file in _file2copy)
                                    {
                                        if (!File.Exists(string.Format("{0}{1}{2}",
                                                aPathCycle, Path.DirectorySeparatorChar, file)))
                                        {
                                            File.Copy(file, string.Format("{0}{1}{2}", aPathCycle, Path.DirectorySeparatorChar, file));
                                        }
                                    }
                                }
                            }

                            int _procIDbuffer;
                            Task<bool> _task = this.StartProcess(exe.name, this.RootFolder_exe, _local.ToArray(), aPathCycle, token, out _procIDbuffer);
                            _taskList.Add(_task);
                            _processesIDs.Add(_procIDbuffer);
                        }
                        exe.parameters.Clear();
                        foreach (string s in _def)
                        {
                            exe.parameters.Add(s);
                        }
                    }

                    bool killthemall = false;
                    foreach (Task<bool> t in _taskList)
                    {
                        if (!t.IsCompleted)
                        {
                            if (!t.Wait(TimeSpan.FromSeconds(TaskWaitTime)))
                            {
                                cTs.Cancel();
                                killthemall = true;
                                break;
                            }
                        }
                    }

                    if (killthemall)
                    {
                        foreach (int id in _processesIDs)
                        {
                            var ProcessesToKill = Process.GetProcesses().Where(pr => pr.Id == id);

                            foreach (var process in ProcessesToKill)
                            {
                                process.Kill();
                                DataProcessingError.Add("SPSscripts.cs", string.Format("Kill process with id {0}", id), string.Format("Kill process with id {0}", id));
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    DataProcessingError.Add("SPSscripts.cs", "Exception during starting scripts", ex.ToString());
                }
                finally
                {
                    cTs.Dispose();
                }
            }
        }

        /// <summary>
        /// Run scripts. Script param-isLast must be True
        /// </summary>
        /// <param name="aRoot">Path to root folder</param>
        /// <param name="aPathCycle">Path to actual cycles folder</param>
        /// <param name="aSet">Active set</param>
        /// <param name="aCycleNum">Number of complete cycles</param>
        public void RunLast(string aRoot, string aPathCycle, List<ExeInfo_t> aSet, int aCycleNum)
        {
            string[] _def = null;
            List<string> _local = new List<string>();
            List<int> _processesIDs = new List<int>();
            uint _max = 0;
            foreach (ExeInfo_t exe in aSet)
                if (exe.number > _max && exe.isLast)
                    _max = exe.number;
            for (int i = 1; i <= _max; i++)
            {
                CancellationTokenSource cTs = new CancellationTokenSource();
                CancellationToken token = cTs.Token;
                List<Task<bool>> _taskList = new List<Task<bool>>();
                foreach (ExeInfo_t exe in aSet)
                {
                    _local = exe.parameters;
                    _def = exe.parameters.ToArray();
                    if (exe.number == i && exe.isLast && exe.afterCycles == aCycleNum)
                    {
                        if (exe.isParamCycleRoot)
                            _local.Add(aPathCycle);
                        else if (exe.isParamRoot)
                            _local.Add(aRoot);


                        if (exe.Copies != null)
                        {
                            if (exe.Copies.Length > 1)
                            {
                                string[] _file2copy = new string[exe.Copies.Length - 1];
                                for (int h = 1; h < exe.Copies.Length; h++)
                                {
                                    _file2copy[h - 1] = exe.Copies[h];
                                }

                                foreach (string file in _file2copy)
                                {
                                    if (!File.Exists(string.Format("{0}{1}{2}",
                                            aPathCycle, Path.DirectorySeparatorChar, file)))
                                    {
                                        File.Copy(string.Format("{0}{1}", RootFolder_exe,file), string.Format("{0}{1}{2}", aPathCycle, Path.DirectorySeparatorChar, file));
                                    }
                                }
                            }
                        }

                        int _procIDbuffer;
                        Task<bool> _task = this.StartProcess(exe.name, this.RootFolder_exe, _local.ToArray(), aPathCycle, token, out _procIDbuffer);
                        _taskList.Add(_task);
                        _processesIDs.Add(_procIDbuffer);
                    }
                    exe.parameters.Clear();
                    foreach (string s in _def)
                    {
                        exe.parameters.Add(s);
                    }
                }

                bool killthemall = false;
                foreach (Task<bool> t in _taskList)
                {
                    if (!t.IsCompleted)
                    {
                        if (!t.Wait(TimeSpan.FromSeconds(TaskWaitTime)))
                        {
                            cTs.Cancel();
                            killthemall = true;
                            break;
                        }
                    }
                }

                if (killthemall)
                {
                    foreach (int id in _processesIDs)
                    {
                        var ProcessesToKill = Process.GetProcesses().Where(pr => pr.Id == id);

                        foreach (var process in ProcessesToKill)
                        {
                            process.Kill();
                            Console.WriteLine("kill process {0}", id);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Check if can run end scripts
        /// </summary>
        /// <param name="aSet">Memory of scripts</param>
        /// <param name="aCycleNum">Number of complete cycles</param>
        /// <returns></returns>
        public bool canRunLast(List<ExeInfo_t> aSet, int aCycleNum)
        {
            foreach (ExeInfo_t exe in aSet)
            {
                if (exe.isLast && exe.afterCycles == aCycleNum)
                {
                    return true;
                }
            }

            return false;
        }

        static public string CreateNewDestination(string dest, Sets_t myset)
        {
            string element = dest + Path.DirectorySeparatorChar + (myset.name.Split('\\')[myset.name.Split('\\').Length - 1]).Split('.')[0];
            if (!Directory.Exists(element))
            {
                Directory.CreateDirectory(element);
            }
            else
            {
                while(Directory.Exists(element))
                    element += 0;
                Directory.CreateDirectory(element);
            }
            SPSdestMemory.Add(element);
            return element;
        }
    }

    /// <summary>
    /// Buffer for Global SensorList.csv
    /// </summary>
    public class SPSsensorClass
    {
        private static List<string[]> MyBuffer;

        public SPSsensorClass()
        {
            MyBuffer = new List<string[]>();
        }

        ~SPSsensorClass()
        {
            if (MyBuffer != null)
                MyBuffer.Clear();
            MyBuffer = null;
        }

        public void ClearBuffer()
        {
            MyBuffer.Clear();
        }

        public string[] GetSensorList()
        {
            List<string> _buff = new List<string>();
            string[] _flist = MyBuffer[0];

            for (int i = 0; i < _flist.Length; i++)
            {
                bool _include = true;

                for (int j = 1; j < MyBuffer.Count; j++)
                {
                    if (!MyBuffer[j].Contains(_flist[i]))
                    {
                        _include = false;
                        break;
                    }

                }

                if (_include)
                {
                    _buff.Add(_flist[i]);
                }
            }

            ClearBuffer();
            return _flist.ToArray();
        }

        public void Add(string[] newList)
        {
            MyBuffer.Add(newList);
        }
    }
}
