// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2018 Thomas Klose & Torsten Schache, Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.



using System;
using System.Collections.Generic;
using System.IO;

using Hiperscan.Unix;
using Hiperscan.Extensions.ApoIdent.SubstanceManagementExtension;
using Hiperscan.I18n;
using Hiperscan.Spectroscopy.IdentModule;
using Hiperscan.Extensions.ApoIdentCommon;

namespace Hiperscan.Extensions.ApoIdent
{
    public class LocalizedStrings
    {
        public string BatchExtension            {get; set;}
        public string NotAvailable              {get; set;}
        public string UserDefinedClassifierName {get; set;}
    }

    public class SubstanceNameResolver
    {
        public SubstanceNameResolver(string classifier_name, string raw_substance_name)
        {
            if (classifier_name != SubstanceManagement.LocalizedUserdefinedClassifierName)
            {
                string[] tmp = raw_substance_name.Split(new string[]{SubstanceManagement.LocalizedBatchExtension}, StringSplitOptions.RemoveEmptyEntries);
                this.BatchNumber = tmp.Length == 2 ? tmp[1] : String.Empty;
                this.SubstanceName = tmp.Length == 0 ? String.Empty : tmp[0];
            }
            else
            {
                this.SubstanceName = raw_substance_name;
            }
        }

        public string SubstanceName {get; private set;} = String.Empty;
        public string BatchNumber   {get; private set;} = String.Empty;
    }

    public class SubstanceManagement
    {
        private const string SEPARATOR = "<<>>";

        public event EventHandler BeginLoadingData                  = null;
        public event EventHandler LoadingDataProgressed             = null;
        public event EventHandler LoadingDataFailed                 = null;
        public event EventHandler LoadingDataFinshed                = null;
        public event EventHandler BeforeClassifierSelectionShown    = null;


        public event EventHandler BeforeSubstanceSelectionChanged   = null;
        public event EventHandler AfterSubstanceSelectionChanged    = null;

        public event EventHandler LoadingSomeModelClassesFailed     = null;

        public SubstanceManagement(Configurations config, IdentServerClient client)
        {
            this.Config = config;
            this.Server = client;
            this.Pdfs = new SubstancePdfs(this);

            SubstanceManagement.CurrentInstance = this;

            SubstanceManagement.AllLocalizedStrings = new Dictionary<Locale.LocaleType, LocalizedStrings>();


            //TODO find translations for czech variant
            LocalizedStrings translations = new LocalizedStrings();
            translations.BatchExtension = ", Batch ";
            translations.NotAvailable = "n/a";
            translations.UserDefinedClassifierName = "Userdefined Substances";
            SubstanceManagement.AllLocalizedStrings.Add(Locale.LocaleType.cs_CZ, translations);

            translations = new LocalizedStrings();
            translations.BatchExtension = ", Charge ";
            translations.NotAvailable = "k.A.";
            translations.UserDefinedClassifierName = "Benutzerdefinierte Substanzen";
            SubstanceManagement.AllLocalizedStrings.Add(Locale.LocaleType.de_DE, translations);

            translations = new LocalizedStrings();
            translations.BatchExtension = ", Batch ";
            translations.NotAvailable = "n/a";
            translations.UserDefinedClassifierName = "Userdefined Substances";
            SubstanceManagement.AllLocalizedStrings.Add(Locale.LocaleType.en_GB, translations);
        }

        public void StartLoading()
        {
            this.IsLoadingStarted = true;
            if (this.BeginLoadingData != null)
                this.BeginLoadingData(this, EventArgs.Empty);
        }

        public void LoadingProgress(double progress)
        {
            if (this.LoadingDataProgressed != null)
            {
                DataEventArgs args = new DataEventArgs();
                args.Data.Add(DataEventArgs.PROGRESS, progress);

                this.LoadingDataProgressed(this, args);
            }
        }

        public void LoadingFailed(string error)
        {
            this.IsLoadingStarted = false;
            if (this.LoadingDataFailed != null)
            {
                DataEventArgs args = new DataEventArgs();
                args.Data.Add(DataEventArgs.ERROR, error);

                this.LoadingDataFailed(this, args);
            }
        }

        public void LoadingFinished()
        {
            this.IsLoadingStarted = false;
            if (this.LoadingDataFinshed != null)
                this.LoadingDataFinshed(this, EventArgs.Empty);
        }


        public bool AddModelClass(AccessDatabaseInfo database_info, ModelClassInfo class_info)
        {            
            if (this.ModelClassNameDetailResolver.ContainsKey(class_info.ModelClassName) == true)
            {
                this.HiddenModelClasses.Add(database_info.Name + 
                                            SubstanceManagement.SEPARATOR +
                                            class_info.ModelClassName);
                return false;
            }
            else
            { 
                class_info.DatabaseInfo = database_info;
                this.AddSubstancesToResolver(class_info);
                return true;
            }
        }

        private void AddSubstancesToResolver(ModelClassInfo class_info)
        {
            this.ModelClassNameDetailResolver.Add(class_info.ModelClassName, class_info);

            foreach (ClassificationInfo classification_info in class_info.Classifications)
            {
                foreach (SubstanceInfo substance_info in classification_info.Substances)
                {
                    if (substance_info.IsVisible == false)
                        continue;

                    List<string> corrected_names = new List<string>();
                    foreach (string substance_name in substance_info.AllNames)
                    {
                        if (classification_info.ClassificationType == ClassificationType.NonMultiple)
                        {
                            foreach (string batch_number in substance_info.BatchNumbers)
                                corrected_names.Add(substance_name + SubstanceManagement.LocalizedBatchExtension + batch_number);
                        }
                        else
                        {
                            corrected_names.Add(substance_name);
                        }
                    } 

                    foreach (string corrected_name in corrected_names)
                    {
                        if (this.SubstanceNameModelClassNameResolver.ContainsKey(corrected_name) == false)
                            this.SubstanceNameModelClassNameResolver.Add(corrected_name, new List<string>());

                        if (this.SubstanceNameModelClassNameResolver[corrected_name].Contains(class_info.ModelClassName) == true)
                            throw new Exception(Catalog.GetString(String.Format("The substance name {0} of the model class {1} is not unique",
                                                                                corrected_name,
                                                                                class_info.ModelClassName)));
                        this.SubstanceNameModelClassNameResolver[corrected_name].Add(class_info.ModelClassName);
                    }
                }
            }
        }

        public void ListHiddenModelClasses()
        {
            string message = String.Empty;
            bool first_loop = true;

            foreach (string entry in this.HiddenModelClasses)
            {
                if (first_loop == true)
                {
                    first_loop = false;
                    message += Catalog.GetString("Following classifiers could not be loaded. " +
                                                 "A possible reason is, that the classifier is " +
                                                 "already defined in another database.") + "\n\n";
                }


                string[]  tmp = entry.Split(new string[]{SubstanceManagement.SEPARATOR}, StringSplitOptions.RemoveEmptyEntries);
                message += "\t" + String.Format(Catalog.GetString("Classifier: {0} from database{1}"), tmp[1], 
                                                Path.GetFileNameWithoutExtension(tmp[0])) + "\n";
            }

            if (this.LoadingSomeModelClassesFailed != null && String.IsNullOrEmpty(message) == false)
            {
                DataEventArgs args = new DataEventArgs();
                args.Data.Add(DataEventArgs.ERROR, message);
                this.LoadingSomeModelClassesFailed(this, args);
            }
        }

        public List<ModelClassInfo> GetMasterClassifierInfos()
        {
            List<ModelClassInfo> values = new List<ModelClassInfo>();
            foreach (ModelClassInfo classifier_info in this.ModelClassNameDetailResolver.Values)
            {
                if (classifier_info.DatabaseInfo == AccessDatabaseInfo.Master)
                    values.Add(classifier_info);
            }

            return values;
        }

        public List<SubstanceListAutoCompletionEntry> GetAllSubstanceNames()
        {
            List<SubstanceListAutoCompletionEntry> values = new List<SubstanceListAutoCompletionEntry>();

            foreach (string substance_name in this.SubstanceNameModelClassNameResolver.Keys)
            {
                SubstanceListAutoCompletionEntry entry = new SubstanceListAutoCompletionEntry();
                entry.Name = substance_name;

                foreach (string class_name in this.SubstanceNameModelClassNameResolver[substance_name])
                {
                    ModelClassInfo class_info = this.ModelClassNameDetailResolver[class_name];

                    SubstanceNameResolver resolver = new SubstanceNameResolver(class_name, substance_name);

                    SubstanceCategory category = SubstanceListAutoCompletionEntry.ConvertToSubstanceCategory(class_info.GetClassificationInfo(resolver.SubstanceName));

                    if (entry.SubstanceCategory < category)
                        entry.SubstanceCategory = category;
                }

                values.Add(entry);
            }

            values.Sort();
            return values;
        }

        public void SelectNewSubstance(ScannerInfo scanner_info)
        {
            List<string> model_class_names = this.GetModelClasses(scanner_info.SubstanceName);
            this.SelectNewSubstance(scanner_info.SubstanceName, model_class_names, ProtocolAutoCompletionType.Scanner);
        }

        public void SelectNewSubstance(FuzzyNameSet fuzzy_set)
        {
            List<string> model_class_names = new List<string>();
            model_class_names.Add(fuzzy_set.ModelClassName);
            this.SelectNewSubstance(fuzzy_set.SubstanceName, model_class_names, ProtocolAutoCompletionType.ThirdParty);
        }

        public void SelectNewSubstance(string substance_name)
        {
            List<string> model_class_names = this.GetModelClasses(substance_name);
            this.SelectNewSubstance(substance_name, model_class_names, ProtocolAutoCompletionType.None);
        }

        private void SelectNewSubstance(string substance_name, List<string> model_class_names, 
                                        ProtocolAutoCompletionType protocol_completion_type)
        {
            string old_model_class_name = this.CurrentModelClass?.ModelClassName ?? String.Empty;
            string old_substance_name = this.CurrentSubstance?.SelectedName ?? String.Empty;
            string model_class_name = String.Empty;


            switch (model_class_names.Count)
            {
                case 0:
                    return;

                case 1:
                    model_class_name = model_class_names[0];
                    break;

                default:                    
                    this.ResetSelection();
                    if (this.BeforeClassifierSelectionShown != null)
                        this.BeforeClassifierSelectionShown(this, EventArgs.Empty);

                    ModelClassDialog dialog = new ModelClassDialog(this, substance_name, model_class_names);
                    model_class_name = dialog.SelectedClassName; 
                    break;
            }

            if (model_class_name == old_model_class_name &&
                substance_name == old_substance_name &&
                protocol_completion_type == ProtocolAutoCompletionType.None)
            {
                this.ProtocolCompletion = protocol_completion_type;
                this.SelectSubstance(old_model_class_name, old_substance_name); 
                return;
            }

            this.ContinueAction = true;
            if (this.BeforeSubstanceSelectionChanged != null)
                this.BeforeSubstanceSelectionChanged(this, EventArgs.Empty);

            if (this.ContinueAction == false && String.IsNullOrEmpty(old_substance_name) == false)
            {
                this.SelectSubstance(old_model_class_name, old_substance_name); 
                return;
            }

            this.ProtocolCompletion = protocol_completion_type;
            this.SelectSubstance(model_class_name, substance_name);

            if (this.AfterSubstanceSelectionChanged != null)
                this.AfterSubstanceSelectionChanged(this, EventArgs.Empty);
        }

        public void ResetScannerMode()
        {
            if (this.ProtocolCompletion == ProtocolAutoCompletionType.Scanner)
                this.ProtocolCompletion = ProtocolAutoCompletionType.None;
        }

        public void ReselectSubstance()
        {
            if (this.CurrentSubstance == null)
                return;

            this.ProtocolCompletion = ProtocolAutoCompletionType.None;
            this.IsSubstanceReselected = true;

            if (this.AfterSubstanceSelectionChanged != null)
                this.AfterSubstanceSelectionChanged(this, EventArgs.Empty);
        }

        public List<FuzzyNameSet> FindSubstanceForDataExchange(ExchangeDataSet data_set)
        {
            List<FuzzyNameSet> values = this.FindSubstancesById(data_set);

            if  (values.Count == 0)
                values.AddRange(this.FindSubstanceByName(data_set));

            return values;
        }

        private List<FuzzyNameSet> FindSubstancesById(ExchangeDataSet data_set)
        {
            List<FuzzyNameSet> fuzzy_sets = new List<FuzzyNameSet>();

            if (String.IsNullOrEmpty(data_set.HiperScanSubstanceId) == true)
                return fuzzy_sets;

            foreach (ModelClassInfo class_info in this.ModelClassNameDetailResolver.Values)
            {
                if (class_info.DatabaseInfo != AccessDatabaseInfo.Master)
                    continue;

                foreach (ClassificationInfo classification_info in class_info.Classifications)
                {
                    foreach (SubstanceInfo substance_info in classification_info.Substances)
                    {
                        if (substance_info.IsVisible == false)
                            continue;
                        
                        if (data_set.HiperScanSubstanceId == substance_info.Id)
                        {
                            fuzzy_sets.Add(new FuzzyNameSet(class_info.ModelClassName,
                                                            data_set.PrimarySubstanceName,
                                                            true,
                                                            substance_info));
                        }
                    }
                }

            }

            return  fuzzy_sets;
        }

        private List<FuzzyNameSet> FindSubstanceByName(ExchangeDataSet data_set)
        {
            List<FuzzyNameSet> name_sets = new List<FuzzyNameSet>();

            foreach (ModelClassInfo class_info in this.ModelClassNameDetailResolver.Values)
            {
                if (class_info.DatabaseInfo != AccessDatabaseInfo.Master)
                    continue;

                foreach (ClassificationInfo classification_info in class_info.Classifications)
                {
                    foreach (SubstanceInfo substance_info in classification_info.Substances)
                    {
                        if (substance_info.IsVisible == false)
                            continue;
                        
                        FuzzyNameSet name_set = new FuzzyNameSet(class_info.ModelClassName,
                                                                 data_set.PrimarySubstanceName,
                                                                 false,
                                                                 substance_info);

                        if (name_set.Score >= FuzzyNameSet.SCORE_THRESHOLD)
                            name_sets.Add(name_set);
                    }
                }
            }

            return name_sets;
        }


        public List<string> GetModelClasses(string substance_name)
        {
            if (this.SubstanceNameModelClassNameResolver.ContainsKey(substance_name) == false)
                return new List<string>();

            return this.SubstanceNameModelClassNameResolver[substance_name];
        }

        public Dictionary<string, string> CheckUniqueSubstanceName(string cleaned_substance_name)
        {
            Dictionary<string, string> values = new Dictionary<string, string>();

            foreach (string key in this.SubstanceNameModelClassNameResolver.Keys)
            {
                if (key.CleanForSubstanceSearch() == cleaned_substance_name)
                {
                    foreach (string classifier_name in  this.SubstanceNameModelClassNameResolver[key])
                    {
                        string primary_name = this.ModelClassNameDetailResolver[classifier_name].GetSubstanceInfo(key).PrimaryDefaultName;
                        values.Add(classifier_name, primary_name);
                    }

                    return values;
                }
            }

            return values;
        }



        private void SelectSubstance(string classifier_name, string substance_name)
        {
            this.CurrentModelClass = this.ModelClassNameDetailResolver[classifier_name]; 
            SubstanceNameResolver resolver = new SubstanceNameResolver(classifier_name, substance_name);

            this.CurrentClassification  = this.CurrentModelClass.GetClassificationInfo(resolver.SubstanceName);
            this.CurrentSubstance       = this.CurrentModelClass.GetSubstanceInfo(resolver.SubstanceName);
            this.CurrentSubstance.SetSelectedNames(resolver.SubstanceName, SubstanceManagement.LocalizedBatchExtension,  resolver.BatchNumber);
        }

        public SubstanceCategory GetSubstanceCategory(string classifier_name, string substance_name)
        {
            SubstanceNameResolver resolver = new SubstanceNameResolver(classifier_name, substance_name);
            ModelClassInfo class_info = this.ModelClassNameDetailResolver[classifier_name];
            ClassificationInfo classification_info = class_info.GetClassificationInfo(resolver.SubstanceName);

            return SubstanceListAutoCompletionEntry.ConvertToSubstanceCategory(classification_info);
        }

		public void InitModelExtension()
        {	
            if ((this.Config.Profiles.Current?.SubstanceExtension?.IsInitialized ?? false) == false)
                this.Extension = new SubstanceExtension(this.Server);
            else               
                this.Extension = this.Config.Profiles.Current.SubstanceExtension;

            if (this.Extension.IsInitialized == true)
                this.RefreshExtension();
        }

        public void RefreshExtension()
        {
            this.RemoveModelClassExtension();
            if (this.Extension?.IsInitialized ?? false == true)
            {
                this.Extension.ModelClassExtension.InitResolver();
                this.AddModelClass(AccessDatabaseInfo.UserDefined, this.Extension.ModelClassExtension); 
            }                
        }

        private void RemoveModelClassExtension()
        {
            this.ModelClassNameDetailResolver.Remove(SubstanceManagement.LocalizedUserdefinedClassifierName);
            List<string> all_substance_names = new List<string>(this.SubstanceNameModelClassNameResolver.Keys);
            foreach (string substance_name in all_substance_names)
            {
                this.SubstanceNameModelClassNameResolver[substance_name].Remove(SubstanceManagement.LocalizedUserdefinedClassifierName);
                if (this.SubstanceNameModelClassNameResolver[substance_name].Count == 0)
                    this.SubstanceNameModelClassNameResolver.Remove(substance_name);
            }
        }

        public void ResetSelection()
        {
            this.CurrentSubstance = null;
            this.CurrentClassification = null;
            this.CurrentModelClass = null;
            this.ProtocolCompletion = ProtocolAutoCompletionType.None;
        }

        public bool ContinueAction      {get; set;}
        public bool IsLoadingStarted    {get; set;}

        public ModelClassInfo CurrentModelClass          {get; private set;}
        public ClassificationInfo CurrentClassification  {get; private set;}
        public SubstanceInfo CurrentSubstance            {get; private set;}

        private HashSet<string> HiddenModelClasses {get; set;} = new HashSet<string>();

        private Dictionary<string, List<string>> SubstanceNameModelClassNameResolver {get; set;} = new Dictionary<string, List<string>>();
        public Dictionary<string, ModelClassInfo> ModelClassNameDetailResolver      {get; private set;} = new Dictionary<string, ModelClassInfo>();
       
        public SubstanceExtension Extension {get; private set;}
        public SubstancePdfs Pdfs           {get; private set;}
        public IdentServerClient Server     {get; private set;}
        public Configurations Config        {get; private set;}
        public ProtocolAutoCompletionType ProtocolCompletion { get; private set; }
        public bool IsSubstanceReselected   { get; set; }


        public static string LocalizedNotAvailable
        {
            get
            {
                return SubstanceManagement.AllLocalizedStrings[
                    SubstanceManagement.CurrentInstance.Config.AcquisitionSettings.Locale].NotAvailable;
            }
        }

        public static string LocalizedBatchExtension
        {
            get
            {
                return SubstanceManagement.AllLocalizedStrings[
                    SubstanceManagement.CurrentInstance.Config.AcquisitionSettings.Locale].BatchExtension;
            }
        }

        public static string LocalizedUserdefinedClassifierName
        {
            get
            {
                return SubstanceManagement.AllLocalizedStrings[
                    SubstanceManagement.CurrentInstance.Config.AcquisitionSettings.Locale].UserDefinedClassifierName;
            }
        }

        public static Dictionary<Locale.LocaleType, LocalizedStrings> AllLocalizedStrings {get; private set;}
        private static SubstanceManagement CurrentInstance {get; set;}    
    }
}
