// Created with MonoDevelop
//
//    Copyright (C) 2024 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.Linq;
using Hiperscan.AnalysisModule.Authentification;
using Hiperscan.AnalysisModule.Identification;
using Hiperscan.SGS;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;

namespace Hiperscan.AnalysisModule.Remoting
{
    public partial class RemoteConnector
    {
        public event EventHandler LoadingDataStarted = null;
        public event EventHandler LoadingDataProgressed = null;
        public event EventHandler LoadingDataFinished = null;

        public void RegisterDevice(IDevice device)
        {
            try
            {
                Console.WriteLine("Loading databases");

                this.State.Clear();
                this.CurrentModelClass = null;
                this.Databases.Clear();
                this.LoadedModelClasses.Clear();
                this.DiscartResult();
                this.LoadingProgressFraction = 0;

                this.Authentification = new AuthentificationSet();
                this.Authentification.Serial = device.Info.SerialNumber.Substring(0, 8);

                List<AccessDatabaseInfo> infos = this.RemoteClient.RemoteService.GetChallenges();
                Console.WriteLine("Found {0} databases.", infos.Count);

                AccessDatabaseInfo master_info = infos.First(d => d.DatabaseType == AccessDatabaseType.Master);
                this.InitializeDatabase(master_info, device);

                foreach (AccessDatabaseInfo db_info in infos)
                {
                    if (db_info.DatabaseType == AccessDatabaseType.Master)
                        continue;

                    this.InitializeDatabase(db_info, device);
                }
            }
            catch (Exception ex)
            {
                this.State.Exception = ex;
                this.State.Header = Catalog.GetString("Device registration failed");
                this.State.Message = String.Format(Catalog.GetString("The registration for device \"{0}\" failed: {1}"),
                                                 this.Authentification.Serial,
                                                  ex);

                this.CallErrorReceivedEvent();
            }
        }

        public void LoadModelClassData()
        {
            try
            {
                this.State.Clear();

                this.ReadModelClassNames();

                int count = this.Databases.SelectMany(d => d.Value).Count();
                double progress_step = 1.0 / (count == 0 ? 1 : count);
                this.LoadingProgressFraction = 0.0;

                this.LoadingDataStarted?.Invoke(this, EventArgs.Empty);

                foreach (AccessDatabaseInfo db_info in this.Databases.Keys)
                {
                    foreach (string model_class_name in this.Databases[db_info])
                    {
                        this.Authentification.ModelClassName = model_class_name;
                        this.Authentification.AccessDatabase = db_info;

                        IModelClass model_class = this.RemoteClient.RemoteService.GetModelClass(this.Authentification);
                        model_class.DatabaseInfo = db_info;

                        bool has_collisions = false;

                        if (model_class is IdentModelClass)
                        {
                            IdentModelClass tmp = (IdentModelClass)model_class;
                            tmp.InitResolver();

                            if (tmp.HasCollisions)
                            {
                                has_collisions = true;
                                Console.WriteLine(((IdentModelClass)model_class).GetCollissionMessages());
                            }

                            if (tmp.Classifications.Any(c => c.ClassificationType == ClassificationType.NotMeasurable))
                            {
                                model_class.DatabaseInfo = IdentModelClass.GetUserDefinedDatabaseInfo();
                            }

                            if (has_collisions)
                            {
                                string message = Catalog.GetString("There are some substance name collisions. Please check the log-file.");
                                throw new Exception(message);
                            }
                        }
           
                        this.LoadedModelClasses.Add(model_class);

                        this.LoadingProgressFraction += progress_step;
                        this.CurrentModelClass = model_class;
                        this.LoadingDataProgressed?.Invoke(this, EventArgs.Empty);
                    }
                }
            }
            catch (Exception ex)
            {
                this.State.Header = Catalog.GetString("Error reading model data");
                this.State.Message = String.Format(Catalog.GetString("Cannot read model data: {0}"), ex.Message);
                this.State.Exception = ex;
            }
            finally
            {
                this.LoadingProgressFraction = 1;
                this.LoadingDataFinished?.Invoke(this, EventArgs.Empty);
            }
        }

        private void InitializeDatabase(AccessDatabaseInfo db_info, IDevice device)
        {
            device.WaitForIdle(30000);
            db_info.DeviceKey = device.ReadMasterKey(db_info.DeviceKey);
            device.WaitForIdle(30000);

            this.Authentification.AccessDatabase = db_info;
            this.Authentification = this.RemoteClient.RemoteService.OpenDatabase(this.Authentification);

            Console.WriteLine("Open database:\n{0}", this.Authentification);

            if (this.Authentification.AccessDatabase.HasAccess == false)
            {
                Console.WriteLine("Device {0} has no access to database {1}.",
                                  device.Info.SerialNumber,
                                  db_info.DirName);
                return;
            }

            if (this.Authentification.AccessDatabase.DeviceKey != null)
            {
                device.WaitForIdle(30000);
                db_info.DeviceKey = device.ReadIndividualKey(this.Authentification.AccessDatabase.DeviceKey);
                device.WaitForIdle(30000);
            }

            this.Databases.Add(db_info, new List<string>());
        }

        private void ReadModelClassNames()
        {
            Dictionary<string, AccessDatabaseInfo> conflicts = new Dictionary<string, AccessDatabaseInfo>();

            foreach (AccessDatabaseInfo db_info in this.Databases.Keys)
            {
                this.Authentification.AccessDatabase = db_info;
                List<string> model_classes = this.RemoteClient.RemoteService.GetModelClassNames(Authentification);

                if (model_classes == null || model_classes.Count == 0)
                    continue;

                foreach (string model_class in model_classes)
                {
                    if (conflicts.ContainsKey(model_class) == true)
                    {
                        this.State.Clear();
                        this.State.Message = String.Format("Model class {0} already exists in {1}",
                                                         model_class,
                                                         db_info.DirName);

                        this.CallStateChangedEvent();
                        continue;
                    }

                    conflicts.Add(model_class, db_info);
                    this.Databases[db_info].Add(model_class);
                }
            }
        }

        public void UpdateAuthentificationSet(IModelClass model_class, string substance_id = AuthentificationSet.NO_SUBSTANCE)
        {
            this.Authentification.ModelClassName = model_class.Name;
            this.Authentification.AccessDatabase = model_class.DatabaseInfo;
            this.Authentification.SubstanceId = substance_id;
        }

        public bool HasModelData
        {
            get
            {
                return this.LoadedModelClasses.Count > 0;
            }
        }

        public byte[] GetValidationDocument(IModelClass model_class)
        {
            this.UpdateAuthentificationSet(model_class);
            return this.RemoteClient.RemoteService.GetValidationDocument(this.Authentification);
        }

        public abstract void DiscartResult();
        public abstract void Analyse(Spectrum spectrum);

        public abstract bool HasValidResult { get; }

        public IModelClass CurrentModelClass { get; protected set; }
        protected List<IModelClass> LoadedModelClasses { get; set; } = new List<IModelClass>();

        public double LoadingProgressFraction { get; protected set; }
        public Dictionary<AccessDatabaseInfo, List<string>> Databases { get; protected set; } = new Dictionary<AccessDatabaseInfo, List<string>>();
        protected AuthentificationSet Authentification { get; set; }
    }
}
