using dataladder.Licensing;
using SampleServiceNamespace;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using dataladder.Matching.Project;
using dataladder.Matching;
using dataladder.Data;
using System.Threading;
using Microsoft.VisualBasic.Devices;

namespace LiveSearchInThreeTables
{
    public partial class Form1 : Form
    {
        private static readonly RegistrationWrapper _registrationWrapper = new RegistrationWrapper();

        private EngineWrapper[]  engineWrappers = new EngineWrapper[2];
        
        private bool _logVisible = true;
        private Boolean _increaseSpeed;

        private string[][] matchField = new string[3][];

        private volatile bool[] _loading = new bool[] { false, false };

        Task _searchTask;
        CancellationTokenSource _cancelSource;

        private string LogFile => Path.Combine(dataladder.Matching.ApplicationSettings.DataPath, "Logs", "api.txt");
        private object locker = new object();

        public Form1()
        {
            InitializeComponent();

            if (!Properties.Settings.Default.IncreaseSpeed)
            {
                Properties.Settings.Default.IncreaseSpeed = true;
                Properties.Settings.Default.Save();
            }

            Text = $"Live Search Demo {SampleServiceNamespace.ProductInfo.VersionApi} ({SampleServiceNamespace.ProductInfo.Version})";

            //Load paths
            dataladder.Matching.ApplicationSettings.Load();
            string dataPath = LoadPath(Properties.Settings.Default.DataPath, dataladder.Matching.ApplicationSettings.DataPath);
            string tempDataPath = LoadPath(Properties.Settings.Default.TempDataPath, dataladder.Matching.ApplicationSettings.TempDataPath);
            string projectsPath = LoadPath(Properties.Settings.Default.ProjectPath, dataladder.Matching.ApplicationSettings.ProjectsPath);
            dataladder.Matching.ApplicationSettings.DataPath = dataPath;
            dataladder.Matching.ApplicationSettings.TempDataPath = tempDataPath;
            dataladder.Matching.ApplicationSettings.ProjectsPath = projectsPath;

            //Check registration
            //RegistrationWrapper registrationWrapper = new RegistrationWrapper();
            _registrationWrapper.CustomPathForRegistrationFile = dataladder.App.Paths.Registration;
            DateTime expirationTime = RegistrationWrapper.ExpirationDate;
            if (expirationTime < DateTime.Now)
                MessageBox.Show("Registration is expired");
            if (!Registration.IsDmeApi)
                MessageBox.Show("License is not API");

        }

        /// <summary>
        /// Choose which of two paths we must use
        /// </summary>
        /// <param name="saved"></param>
        /// <param name="applicationPath"></param>
        /// <returns></returns>
        private string LoadPath(string saved, string applicationPath)
        {
            if (string.IsNullOrEmpty(saved))
                return applicationPath;
            
            if (!Directory.Exists(saved))
                return applicationPath;
            
            return saved;
        }

        private void bnEnginesStart_Click(object sender, EventArgs e)
        {
            bnEnginesStart.Enabled = false;

            _loading[0] = true;
            _loading[1] = true;

            CreateEngineWrapper(Properties.Settings.Default.ProjectPath1, 0, Properties.Settings.Default.IncreaseSpeed);
            Thread.Sleep(1000);
            
            new Task(() =>
            {
                while (_loading[0] /*|| _loading[1]*/)
                {
                    Thread.Sleep(500);
                }

                Action action = new Action(() => {
                    tbSearch.Text = "";
                    tbSearch.Enabled = true;
                });
                if (this.InvokeRequired)
                    this.BeginInvoke(action);
                else
                    action();
            }).Start();
        }

        private void CreateEngineWrapper(string fn, int cnt, bool useapi)
        {
            if (File.Exists(fn))
            {
                Application.DoEvents();

                string startMsg = $"Start loading Engine Wrapper No.{ cnt} by project '{Path.GetFileNameWithoutExtension(fn)}'";
                tbLog.Text += $"{DateTime.Now} - {startMsg} {Environment.NewLine}";
                PrintToLog(startMsg); 

                new Task(() => { 
                    Stopwatch sw = new Stopwatch();
                    sw.Start();

                    EngineWrapper engine = new EngineWrapperFewDs(Path.GetFileNameWithoutExtension(fn), 
                        conString: "", useApi: useapi);

                    matchField[cnt] = engine.MatchingFieldNames;

                    sw.Stop();

                    string msg = $"{DateTime.Now} - End loading Engine Wrapper {cnt} {Environment.NewLine}";
                    if (String.IsNullOrEmpty(engine.Errors))
                        msg += $"without errors in No{cnt}{Environment.NewLine}";
                    else
                        msg += $"!!!  Errors in No{cnt}:  {engine.Errors} {Environment.NewLine}";

                    msg += $"Load Time No{cnt}: {sw.ElapsedMilliseconds} ms {Environment.NewLine}";

                    TextToLog(msg);
                    PrintToLog(msg);

                    if (String.IsNullOrEmpty(engine.Errors))
                    {
                        engineWrappers[cnt] = engine;
                        engineWrappers[cnt].UseApi = useapi;

                        WriteMemoryInfo();
                        TextToLog($"Loading finished successfully {Environment.NewLine}");
                    }
                    else
                    {
                        engineWrappers[cnt] = null;
                        TextToLog($"Loading was broken {Environment.NewLine}");
                    }

                    _loading[cnt] = false;
                }).Start();
            }
        }

        /// <summary>
        /// Write to logFile information about memory usage
        /// </summary>
        private void WriteMemoryInfo()
        {
            var ci = new ComputerInfo();
            string memory = (ci.AvailablePhysicalMemory / 1048576) + " mb free";

            var process = System.Diagnostics.Process.GetCurrentProcess();
            var virt = process.VirtualMemorySize64;
            var ram = process.WorkingSet64;
            var page = process.PagedMemorySize64;
            var page2 = process.PrivateMemorySize64;
            var msg = ($"{memory}, ram:{ram / 1048576} mb, virt:{virt / 1048576} mb,  " +
                $"paged:{page / 1048576} mb, private:{page2 / 1048576} mb   {Environment.NewLine}");
            //TextToLog(msg);
            PrintToLog(msg);
        }

        /// <summary>
        /// Print message to log window
        /// </summary>
        /// <param name="message"></param>
        private void TextToLog(string message)
        {
            Action action = new Action(() => {
                tbLog.Text += message;
            });
            if (this.InvokeRequired)
                this.BeginInvoke(action);
            else
                action();
        }

        /// <summary>
        /// Write to LogFile a message
        /// </summary>
        /// <param name="msg"></param>
        private void PrintToLog(string msg)
        {
            lock (locker)
            {
                StreamWriter log = null;
                try
                {
                    var logDir = Path.GetDirectoryName(LogFile);
                    if (!Directory.Exists(logDir))
                    {
                        Directory.CreateDirectory(logDir);
                    }

                    log = new StreamWriter(LogFile, true);
                    log.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.fff")} {msg} {System.Environment.NewLine}");
                    log.Flush();
                    log.Close();
                }
                catch
                {
                    log?.Flush();
                    log?.Close();
                }
                log = null;
            }
        }

        /// <summary>
        /// Event Hadler button 'Search'
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void bnSearch_Click(object sender, EventArgs e)
        {
            bnSearch.Enabled = false;
            var text = tbSearch.Text;
            Search(text);
        }

        /// <summary>
        /// Event Handler for live search options
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tbSearch_TextChanged(object sender, EventArgs e)
        {
            if (!cbLiveSearch.Checked) return;

            var text = tbSearch.Text;
            if (text.TrimStart().Length < 3)
            {
                label3.Text = $"Please Enter more than three characters...";
                return;
            }

            Search(text);
            
        }

        private void Search(string text, bool outputEnable = true)
        {
            RunSearching(text, outputEnable, Properties.Settings.Default.CountRecords);
        }

        //make search in async task and use cancellation
        private void RunSearching(string text, bool outputEnable, int num)
        {
            if (!(_searchTask?.IsCompleted ?? true))
            {
                if (_cancelSource != null)
                {
                    _cancelSource.Cancel();
                    _cancelSource.Token.WaitHandle.WaitOne();
                }
            }

            _cancelSource = new CancellationTokenSource(50);
            _searchTask = new Task(() =>
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                InMemoryTable resTable = null;
                string errror = "";
                try
                {
                    InMemoryTable table1 = null;
                    Task task1 = new Task(() =>
                    {
                        table1 = DoMatch(engineWrappers[0], num, new string[] { text });
                    });

                    task1.Start();
                    Task.WaitAll(new Task[] { task1 });

                    sw.Stop();
                    resTable = table1;
                }
                catch (Exception ex)
                {
                    errror = ex.Message + "  " + ex.StackTrace;
                }

                // output results
                Action action = new Action(() =>
                {
                    if (outputEnable)
                    {
                        dataGridView1.DataSource = null;
                        if (resTable != null)
                        {
                            dataGridView1.DataSource = new dataladder.XtraGridHelper.VirtualListDynamic(resTable);
                            label3.Text = $"Search time '{text}': {sw.ElapsedMilliseconds} ms";
                            string records = resTable.RecordCount > 0 ? $"   - {resTable.RecordCount}" : "";
                            tbLog.Text += $"Search Time '{text}': {sw.ElapsedMilliseconds} {records} {Environment.NewLine}"
                                + engineWrappers[0]?.Errors + errror;

                            PrintToLog($"Search Time '{text}': {sw.ElapsedMilliseconds} {records}");
                        }
                        else
                            tbLog.Text += $"Got null. Search Time '{text}': {sw.ElapsedMilliseconds} {Environment.NewLine}"
                                + engineWrappers[0]?.Errors + errror;
                        tbLog.SelectionStart = tbLog.TextLength;
                        tbLog.ScrollToCaret();

                    }
                    bnSearch.Enabled = !cbLiveSearch.Checked;
                });

                if (this.InvokeRequired)
                {
                    this.BeginInvoke(action);
                }
                else
                {
                    action();
                }
            }, _cancelSource.Token);
            _searchTask.Start();
        }

        #region Not used
        private InMemoryTable ConnectTables(InMemoryTable table1, InMemoryTable table2)
        {
            if (table1 == null) return table2;
            if (table2 == null) return table1;

            List<string> names = new List<string>(table1.GetColumnNames());
            var names2 = table2.GetColumnNames();
            foreach (var item in names2)
            {
                if (!names.Contains(item)) names.Add(item);
            }
            InMemoryTable res = new InMemoryTable("Match Result");
            res.AddFields(names.ToArray());

            int row = 0;
            InMemoryTable tmpTable = table1;

            for (int j = 0; j < tmpTable.ColumnCount; j++)
            {
                string column = tmpTable.GetColumnName(j);
                for (int i = 0; i < tmpTable.RecordCount; i++)
                {
                    res.SetData(tmpTable.GetData(i, j), row + i, column);
                }
            }

            row = table1.RecordCount;
            tmpTable = table2;

            for (int j = 0; j < tmpTable.ColumnCount; j++)
            {
                string column = tmpTable.GetColumnName(j);
                for (int i = 0; i < tmpTable.RecordCount; i++)
                {
                    res.SetData(tmpTable.GetData(i, j), row + i, column);
                }
            }

            return res;
            
        }

        private InMemoryTable ConnectTablesWithScore(InMemoryTable table1, InMemoryTable table2)
        {
            if (table1 == null) return table2;
            if (table2 == null) return table1;

            List<Row> allRows = new List<Row>();
            for (int i = 0; i < table1.RecordCount; i++)
                allRows.Add(new Row(table1, i));
            for (int i = 0; i < table2.RecordCount; i++)
                allRows.Add(new Row(table2, i));
            allRows.Sort();
            allRows.Reverse();

            List<string> names = new List<string>(table1.GetColumnNames());
            var little = names.Select(name => name.ToLower()).ToList();
            var names2 = table2.GetColumnNames();
            foreach (var item in names2)
            {
                if (!little.Contains(item.ToLower()))
                {
                    little.Add(item.ToLower());
                    names.Add(item);
                }
            }
            InMemoryTable res = new InMemoryTable("Match Result");
            res.AddFields(names.ToArray());

            for (int i = 0; i < allRows.Count; i++)
            {
                var tmpTable = allRows[i].Table;
                var row = allRows[i].NumRow;
                for (int j = 0; j < tmpTable.ColumnCount; j++)
                {
                    string column = tmpTable.GetColumnName(j);
                    res.SetData(tmpTable.GetData(row, j), i, column);
                }
            }

            return res;
        }
        #endregion

        /// <summary>
        /// Send To Match Engine a searched words
        /// </summary>
        /// <param name="engine"></param>
        /// <param name="count">count of results</param>
        /// <param name="text"></param>
        /// <returns></returns>
        InMemoryTable DoMatch(EngineWrapper engine, int count, string[] text )
        {
            if (engine == null) return null;
            var data = new string[engine.MatchingFieldNames.Length];
            int j = 0;
            for (int i = 0; i < data.Length; i++)
            {
                data[i] = text[j++];
                if (j == text.Length) j = 0;
            }
            engine.Errors = "";
            return engine.FindMatches(data, count, withServiceInfo: false);
        }

        private void ShowSettingsWindow_Click(object sender, EventArgs e)
        {
            SettingsForm settings = new SettingsForm();

            settings.Project1 = Properties.Settings.Default.ProjectPath1;
            settings.IncreaseSpeed = Properties.Settings.Default.IncreaseSpeed;
            settings.CountRecords = Properties.Settings.Default.CountRecords;

            if (bnEnginesStart.Enabled)
                settings.bnStopEngine.Enabled = false;

            settings.ShowDialog(this);
            Properties.Settings.Default.ProjectPath1 = settings.Project1;

            Properties.Settings.Default.CountRecords = settings.CountRecords;
            if (Properties.Settings.Default.IncreaseSpeed != settings.IncreaseSpeed)
            {
                Properties.Settings.Default.IncreaseSpeed = settings.IncreaseSpeed;
                RefreshEngineMode();
            }


            if (settings.PathesChanged)
            {
                Properties.Settings.Default.DataPath = settings.NewDataPath;
                Properties.Settings.Default.ProjectPath = settings.NewProjectPath;
                Properties.Settings.Default.TempDataPath = settings.NewTempDataPath;
                MessageBox.Show("New paths will be applied after restarting");
            }

            Properties.Settings.Default.Save();

            if (settings.StopEngine)
            {
                engineWrappers[0]?.Dispose();
                engineWrappers[0] = null;

                bnEnginesStart.Enabled = true;
                tbSearch.Text = " -- Press 'Start' button --";
                tbSearch.Enabled = false;
            }
        }

        /// <summary>
        /// Show or hide log window
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonLog_Click(object sender, EventArgs e)
        {
            if (_logVisible)
            {
                _logVisible = false;
                splitContainer3.Panel2Collapsed = true;
                buttonLog.Text = "Show log";
            }
            else
            {
                _logVisible = true;
                splitContainer3.Panel2Collapsed = false;
                buttonLog.Text = "Hide log";
            }
        }
                
        private void RefreshEngineMode()
        {
            _increaseSpeed = Properties.Settings.Default.IncreaseSpeed;

            tbLog.Text += "-----  " + (_increaseSpeed?"use APi acceleration":" normal speed") + Environment.NewLine;
            tbLog.SelectionStart = tbLog.TextLength;
            tbLog.ScrollToCaret();

            if (engineWrappers[0] != null)
                engineWrappers[0].UseApi = _increaseSpeed;
        }
        
        private void cbLiveSearch_CheckedChanged(object sender, EventArgs e)
        {
            bnSearch.Enabled = !cbLiveSearch.Checked;
        }
    }

    /// <summary>
    /// This class is used for merging InMemoryTables
    /// </summary>
    public class Row:  IComparable
    {
        public double Score;
        public int NumRow;
        public InMemoryTable Table;

        internal Row(InMemoryTable table, int row)
        {
            Table = table;
            NumRow = row;
            if (!Double.TryParse(table.GetData(row, "Score").ToString(), out Score))
                Score = -1.0;
        }

        public int CompareTo(object obj)
        {
            if (obj == null) throw new ArgumentNullException();
            if (!(obj is Row)) throw new ArgumentException();

            var that = obj as Row;

            if (this.Score > that.Score) return 1;
            if (this.Score < that.Score) return -1;
            return 0;
        }
    }
}
