using dataladder.Data;
using DataMatch.Data.Enums;
using SampleServiceNamespace;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using log4net;
using System.Configuration;

namespace SampleWebService
{
    public class ApiWebService : IDmeApiWebService
    {
        #region Constants
        private const char _settingSeparator = ';';
        private const char _settingSeparatorEquals = '=';
        private const string _projectNameToken = "ProjectName";
        private const string _resultTableToken = "ResultTable";
        private const string _cashedOptionsToken = "Cache";

        #endregion

        #region Fields
        private static readonly ILog Log = LogManager.GetLogger("ServiceLogger");

        private static Dictionary<string, EngineWrapper> startedEngines = new Dictionary<string, EngineWrapper>();
        private static Dictionary<string, Dictionary<string, string>> _settingsMain = new Dictionary<string, Dictionary<string, string>>();
        private static int _records = 10;
        private static double _highThreshold = 0.95;
        private static double _lowThreshold = 0.8;

        #endregion


        #region Constructors

        public ApiWebService()
        {
        }

        static ApiWebService()
        {
            ApiWebService instance = new ApiWebService();
            instance.Init();
        }

        #endregion

        public void Init()
        {
            foreach (SettingsProperty currentProperty in Properties.Settings.Default.Properties)
            {
                object obj = Properties.Settings.Default[currentProperty.Name];
                if (obj is string project)
                {
                    string projName;
                    Dictionary<string, string> settings;
                    if(TryExtractInstanceSettingsFromString(project, out projName, out settings))
                    {
                        if (!startedEngines.ContainsKey(projName))
                        {
                            var engine = new EngineWrapper(projName, pathToConfig:Properties.Settings.Default.DataPath);
                            engine.UseCacheTable = Properties.Settings.Default.UseCashedTable;
                            engine.FloatThreshold = true;
                            engine.SetThreshold(_lowThreshold);
                            startedEngines.Add(projName, engine);
                        }
                        if (!_settingsMain.ContainsKey(projName) && settings != null)
                        {
                            _settingsMain.Add(projName, settings);
                        }
                    }
                }
            }

            
            //if (!startedEngines.ContainsKey("MyDemoDb"))
            //{
            //    startedEngines.Add("MyDemoDb", new EngineWrapperForService("MyDemoDb"));
            //}

            //Dictionary<string, string> set = new Dictionary<string, string>() {
            //    { _resultTableToken, "Example1_ins_res" },
            //    { _cashedOptionsToken, "CacheOptions.ExceptStrongDuplicates" },
            //};
            //_settingsMain.Add("MyDemoDb", set);
            

        }

        private bool TryExtractInstanceSettingsFromString(string rawstring, out string projName, out Dictionary<string, string> settings)
        {
            bool res = false;
            settings = new Dictionary<string, string>();
            projName = "";

            string[] snippets = rawstring.Split(_settingSeparator);
            foreach (var snippet in snippets)
            {
                string[] parts = snippet.Split(_settingSeparatorEquals);
                if (parts.Length != 2) continue;
                if (parts[0].Contains(_projectNameToken))
                {
                    projName = parts[1].Trim();
                    res = true;
                }
                else
                {
                    if (!settings.ContainsKey(parts[0].Trim()))
                        settings.Add(parts[0].Trim(), parts[1].Trim());
                }
            }
            return res;
        }
        
        public void MatchAndInsert(string projName, long id, string[] values)
        {
            Log.Info($"MatchAndInsert(..) has got values:{string.Join(", ", values)} with PK={id} for '{projName}'");

            EngineWrapper engineWrapper;
            if (!startedEngines.TryGetValue(projName, out engineWrapper))
                Log.Info($"Can't find a started engine for '{projName}'");
            else
            {
                int duplicates = 0;
                int strongDuplicates = 0;

                lock (engineWrapper.Lock)
                {
                    try
                    {
                        InMemoryTable table = engineWrapper.FindMatches(values, _records);
                        for (int i = 0; i < table.RecordCount; i++)
                        {
                            object obj = table.GetData(i, (int)PreviewFinalResultsStaticFields.Score);
                            double score = -1;
                            if (obj is double) score = (double)obj;
                            if (obj is string)
                                double.TryParse((string)obj, out score);
                            if (score >= 0)
                            {
                                if (score > _highThreshold * 100)
                                    strongDuplicates++;
                                else if (score > _lowThreshold * 100)
                                    duplicates++;
                            }
                        }
                                                
                        if (!_settingsMain.ContainsKey(projName))
                            Log.Info($"Can't find the settings for project '{projName}' ");
                        else
                        {
                            Dictionary<string, string> settings1 = _settingsMain[projName];
                            if (!settings1.ContainsKey(_resultTableToken))
                                Log.Info($"Can't find {_resultTableToken} definition in the settings of '{projName}'");
                            else
                            {
                                string error;
                                if (!engineWrapper.UpdateRecord(_settingsMain[projName][_resultTableToken], id, strongDuplicates, duplicates, out error))
                                {

                                    Log.Info($"Error happehed while update record in result table: {error}");
                                }
                                else
                                {
                                    Log.Info($"Results '{projName}' are updated PK={id} strong={strongDuplicates} duplicates={duplicates} ");

                                    CacheOptions cache = CacheOptions.None;
                                    if (settings1.ContainsKey(_cashedOptionsToken))
                                    {
                                        if (Enum.TryParse(settings1[_cashedOptionsToken], out cache))
                                        {
                                            Log.Info($"Unknown a 'CashedOptions' definition value in the settings of '{projName}'");
                                        }
                                    }
                                    bool doIt = false;
                                    switch (cache)
                                    {
                                        case CacheOptions.Allways:
                                            doIt = true;
                                            break;
                                        case CacheOptions.ExceptStrongDuplicates:
                                            if (strongDuplicates == 0)
                                                doIt = true;
                                            break;
                                        case CacheOptions.ExceptDuplicates:
                                            if (duplicates == 0 && strongDuplicates == 0)
                                                doIt = true;
                                            break;
                                        case CacheOptions.None:
                                        default:
                                            doIt = false;
                                            break;
                                    }

                                    if (doIt)
                                    {
                                        engineWrapper.AddToCacheTableRecord(values);
                                        Log.Info($"Values was inserted in API cache table of '{projName}'");

                                    }
                                    else
                                    {
                                        Log.Info($"Values was not inserted in API cache table of '{projName}' through {cache.ToString()}");
                                    }
                                }

                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Error($"Exception happens MatchAndInsert(..) values={string.Join(", ", values)} PK={id} with '{projName}':{ex.Message}");
                    }
                }
            }
        }

        //for Debug purposes
        public string MatchAndInsertSync(string projName, long id, string[] values)
        {
            string res = "Matching " + string.Join(", ", values) + ". ";
            EngineWrapper engineWrapper;
            if (!startedEngines.TryGetValue(projName, out engineWrapper))
                res += "Not loaded. ";
            else
            {
                int duplicates = 0;
                int strongDuplicates = 0;

                lock (engineWrapper.Lock)
                {
                    try
                    {
                        InMemoryTable table = engineWrapper.FindMatches(values, _records);
                        res = "Matching ok.";
                        for (int i = 0; i < table.RecordCount; i++)
                        {
                            object obj = table.GetData(i, (int)PreviewFinalResultsStaticFields.Score);
                            double score = -1;
                            if (obj is double) score = (double)obj;
                            if (obj is string)
                                double.TryParse((string)obj, out score);
                            if (score >= 0)
                            {
                                if (score > _highThreshold * 100)
                                    strongDuplicates++;
                                else if (score > _lowThreshold * 100)
                                    duplicates++;
                            }
                        }

                        if (!_settingsMain.ContainsKey(projName))
                            res +=  $"Can't find the settings for project '{projName}'. ";
                        else
                        {
                            Dictionary<string, string> settings1 = _settingsMain[projName];
                            if (!settings1.ContainsKey(_resultTableToken))
                                res += $"ResultTable  missing. ";
                            else
                            {
                                string error;
                                if (!engineWrapper.UpdateRecord(_settingsMain[projName][_resultTableToken], id, strongDuplicates, duplicates, out error))
                                {

                                    res =  "udate: " + error;
                                }
                                else
                                {
                                    Log.Info($"Results '{projName}' are updated PK={id} strong={strongDuplicates} duplicates={duplicates} ");

                                    CacheOptions cache = CacheOptions.None;
                                    if (settings1.ContainsKey(_cashedOptionsToken))
                                    {
                                        if (Enum.TryParse(settings1[_cashedOptionsToken], out cache))
                                        {
                                            Log.Info($"Unknown a 'CashedOptions' definition value in the settings of '{projName}'");
                                        }
                                    }
                                    bool doIt = false;
                                    switch (cache)
                                    {
                                        case CacheOptions.Allways:
                                            doIt = true;
                                            break;
                                        case CacheOptions.ExceptStrongDuplicates:
                                            if (strongDuplicates == 0)
                                                doIt = true;
                                            break;
                                        case CacheOptions.ExceptDuplicates:
                                            if (duplicates == 0 && strongDuplicates == 0)
                                                doIt = true;
                                            break;
                                        case CacheOptions.None:
                                        default:
                                            doIt = false;
                                            break;
                                    }

                                    if (doIt)
                                    {
                                        engineWrapper.AddToCacheTableRecord(values);
                                        Log.Info($"Values was inserted in API cache table of '{projName}'");

                                    }
                                    else
                                    {
                                        Log.Info($"Values was not inserted in API cache table of '{projName}' through {cache.ToString()}");
                                    }
                                }

                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        res += $"{ex.Message} {ex.StackTrace}";
                    }
                }
            }
            return res;
        }

        public bool AddToCachedTable(string projName, string[] matchihgValues)
        {
            Log.Info($"AddToCachedTable() has got values:{string.Join(", ", matchihgValues)} '{projName}'");

            bool res = false;

            EngineWrapper engineWrapper;
            if (!startedEngines.TryGetValue(projName, out engineWrapper))
                Log.Info($"Can't find a started engine for '{projName}'");
            else
            {
                lock (engineWrapper.Lock)
                {
                    try
                    {
                        engineWrapper.AddToCacheTableRecord(matchihgValues);
                        res = true;
                    }
                    catch (Exception ex)
                    {
                        Log.Error($"Exception happens AddToCachedTable()  with '{projName}':{ex.Message}");
                    }
                }
            }

            return res;
        }

        public string GetVersion()
        {
            return $"{ProductInfo.VersionApi} ({ProductInfo.Version})";
        }

        public void ReInit()
        {
            foreach (var pair in startedEngines)
            {
                string key = pair.Key;
                EngineWrapper engineWrapper = pair.Value;
                lock(engineWrapper.Lock)
                {
                    engineWrapper.Dispose();
                }
            }
            startedEngines = null;
            GC.Collect();
            Init();
        }


        public string[] GetErors()
        {
            List<string> res = new List<string>();
            foreach (var pair in startedEngines)
            {
                string key = pair.Key;
                EngineWrapper engineWrapper = pair.Value;
                lock (engineWrapper.Lock)
                {
                    string tmp = (engineWrapper.Errors);
                    //TODO: problem with long messages - maybe we need configure service
                    int length = 300;
                    if (length > tmp.Length)
                        res.Add(tmp);
                    else
                        res.Add(tmp.Substring(0, length));

                    engineWrapper.Errors = string.Empty;

                }
            }
            return res.ToArray();
        }

        public string[] GetLoadedProjects()
        {
            List<string> res = new List<string>();
            foreach (var pair in startedEngines)
            {
                string key = pair.Key;
                res.Add(key);
            }
            return res.ToArray();
        }

        public string[] GetPathes()
        {
            List<string> res = new List<string>();
            foreach (var pair in startedEngines)
            {
                string key = pair.Key;
                EngineWrapper engineWrapper = pair.Value;
                lock (engineWrapper.Lock)
                {
                    foreach (var item in engineWrapper.Pathes)
                    {
                        string tmp = item;
                        int length = 300;
                        if (length > tmp.Length)
                            res.Add(tmp);
                        else
                            res.Add(tmp.Substring(0, length));
                    }
                }
            }
            return res.ToArray();
        }
    }

    //Define how a checked record will be added into cached table
    public enum CacheOptions
    {
        Allways,        // allways after matching
        ExceptStrongDuplicates, //in case if strong duplicates was not founded
        ExceptDuplicates, //in case if no strong duplicates and no duplicates was founded
        None            //never
    }
}

