// 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.SGS;
using Hiperscan.Spectroscopy;
using Hiperscan.Spectroscopy.IdentModule;
using Hiperscan.Spectroscopy.IdentServer;
using Hiperscan.Unix;


namespace Hiperscan.Extensions.ApoIdent
{

    public partial class IdentServerClient
    {

        public const string LINUX_COMMAND = "identserver";

        public IdentServerClient(Configurations config)
        {
            this.Config = config;
            this.Authentification = null;
            this.Client = null;
        }

        public void Reset()
        {
            Console.WriteLine("Reset ident module client ...");
            this.Client = null;
            IdentServerClient.Info = null;
            IdentServerClient.CanConnectToIdentModule = false;
            this.Result = null;
            this.Authentification = null;
            this.IsInitialized = false;
        }



        #region start and stop ident module

        public ApoIdentApplicationState Start(System.Version plugin_version, bool is_release, bool has_second_chance)
        {
            try
            {
                this.StartIdentModule();
                if (IdentServerClient.CanConnectToIdentModule == false)
                    throw new Exception(Catalog.GetString("IdentModule does not run."));
            }
            catch (Exception ex)
            {
                if (has_second_chance == false)
                {
                    if (IdentServerClient.CanConnectToIdentModule == true || ApoIdentEnvs.DebugOn)
                        Console.WriteLine("Starting IdentModule failed: {0}", ex);
                }
                else
                {
                    if (ApoIdentEnvs.DebugOn)
                        Console.WriteLine("IdentModule needed second chance: {0}", ex);
                }

                this.Exception = ex;
                this.ErrorMessage = ex.Message;
                return ApoIdentApplicationState.ConnectionStartIdentServerFailed;
            }

            DateTime then = DateTime.Now;
            TimeSpan span = new TimeSpan(0, 0, 10);

            for (; ; )
            {
                System.Threading.Thread.Sleep(500);
                try
                {
                    this.Client = IdentModuleClient.CreateRemoteObject(this.Address, this.TcpPort);

                    IdentServerClient.Info = this.Client.GetIdentModuleInfo();
                    this.Config.IdentModuleSettings.Locale = IdentServerClient.Info.Locale;
                    LocalizedSubstanceExtensions.Init(this.Config.IdentModuleSettings.Locale);
                    Console.WriteLine(IdentServerClient.Info);

                    if (is_release == true)
                    {
                        if (plugin_version.Major.ToString() != IdentServerClient.Info.Version.AssemblyMajorVersion ||
                            plugin_version.Minor.ToString() != IdentServerClient.Info.Version.AssemblyMinorVersion)
                        {
                            this.Exception = null;
                            this.ErrorMessage = String.Format(Catalog.GetString("Incompatible Ident Module version: {0}"),
                                                               IdentServerClient.Info.Version.ShortAssemblyVersion);

                            return ApoIdentApplicationState.ConnectionIdentServerVersionRefused;
                        }
                    }
                }
                catch (System.Runtime.Remoting.RemotingException ex)
                {
                    if (DateTime.Now - then < span)
                        continue;

                    if (has_second_chance == true)
                        Console.WriteLine("IdentModule needed second chance: {0}", ex);

                    this.Exception = ex;
                    this.ErrorMessage = String.Format(Catalog.GetString("Time out error while connecting to identmodule: {0}"), ex.Message);
                    return ApoIdentApplicationState.ConnectionIdentServerRefused;
                }
                catch (Exception ex)
                {
                    if (has_second_chance == true)
                        Console.WriteLine("IdentModule needed second chance: {0}", ex);

                    this.Exception = ex;
                    this.ErrorMessage = ex.Message;
                    return ApoIdentApplicationState.ConnectionIdentServerRefused;
                }

                break;
            }

            return ApoIdentApplicationState.None;
        }

        private void StartIdentModule()
        {
            Console.WriteLine("Starting IdentServer...");
            IdentServerClient.FoundRunningServer = false;
            try
            {
                IdentServerClient.CanConnectToIdentModule = false;
                this.IdentServerProcess.Kill();
                System.Threading.Thread.Sleep(500);
            }
#pragma warning disable RECS0022
            catch (Exception) { }
#pragma warning restore RECS0022

            try
            {
                IIdentServer tmp = IdentModuleClient.CreateRemoteObject(this.Address, this.TcpPort);
                tmp.GetIdentModuleInfo();
                Console.WriteLine("IdentServer is already running ...");
                IdentServerClient.FoundRunningServer = true;
                IdentServerClient.CanConnectToIdentModule = true;
                return;
            }   
            catch (Exception ex)
            {
                if (ApoIdentEnvs.DebugOn)
                    Console.WriteLine("Exception connecting to IdentServer: " + ex.ToString());
            }

            this.IdentServerProcess = this.CreateIdentServerProcess(false);
            if (this.IdentServerProcess == null)
            {
                Console.WriteLine("No IdentModule installed.");
                return;
            }
            this.IdentServerProcess.Start();
            IdentServerClient.CanConnectToIdentModule = true;
            Console.WriteLine("IdentServer started.");
        }

        public void StopIdentModule()
        {
            try
            {
                if (IdentServerClient.FoundRunningServer == true)
                    return;

                Console.WriteLine("Stopping IdentServer...");
                try
                {
                    System.Diagnostics.Process proc = this.CreateIdentServerProcess(true);
                    proc?.Start();
                }
#pragma warning disable RECS0022
                catch (Exception) { }
#pragma warning restore RECS0022

                try
                {
                    System.Threading.Thread.Sleep(2500);
                    this.IdentServerProcess.Kill();
                    System.Threading.Thread.Sleep(500);
                }
#pragma warning disable RECS0022
                catch (Exception) { }
#pragma warning restore RECS0022

                Console.WriteLine("Ident server stopped successfully.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error while stopping ident server: {0}", ex);
            }
        }

        private System.Diagnostics.Process CreateIdentServerProcess(bool stop)
        {
            if (IdentServerClient.IsOnHostInstalled == false)
                return null;

            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo.Arguments = String.Empty;

            PlatformID osversion = Environment.OSVersion.Platform;
            if (osversion == PlatformID.Win32NT ||
                osversion == PlatformID.Win32S ||
                osversion == PlatformID.Win32Windows ||
                osversion == PlatformID.WinCE)
            {

                proc.StartInfo.FileName = FilesAndDirs.MonoExeFile;
                proc.StartInfo.Arguments += "\"" + FilesAndDirs.IdentServerExeFile + "\"";
                proc.StartInfo.WorkingDirectory = FilesAndDirs.IdentServerWorkingDir;
            }
            else
            {
                // linux
                proc.StartInfo.FileName = IdentServerClient.LINUX_COMMAND;
            }

            proc.StartInfo.Arguments += " --port " + this.TcpPort.ToString();

            if (stop == true)
            {
                proc.StartInfo.Arguments += " --stop";
            }
            /*
            else
            {
                if (ApoIdentEnvs.NoIdentMouleLogOff)
                    proc.StartInfo.Arguments += " --log \"" + FilesAndDirs.IdentServerLogFile + "\"";
            }
            */

            proc.StartInfo.CreateNoWindow = true;
            proc.StartInfo.UseShellExecute = false;

            Console.WriteLine("Identserver proc - FileName :" + proc.StartInfo.FileName);
            Console.WriteLine("Identserver proc - Arguments:" + proc.StartInfo.Arguments);

            return proc;
        }

        #endregion start and stop ident module

        public ApoIdentApplicationState RegisterDevice(IDevice device)
        {
            try
            {
                byte[] challenge = this.Client.GetChallenge();
                device.WaitForIdle(30000);  
                byte[] master_response = device.ReadMasterKey(challenge);
                this.Databases = this.Client.Initialize(device.Info.xApoIdentSerialNumber(), master_response);
                device.WaitForIdle(20000);


                this.Authentification = new AuthenticationSet
                {
                    Serial = device.Info.xApoIdentSerialNumber(),
                };

                foreach (AccessDatabaseInfo db_info in this.Databases)
                {
                    if (db_info.IndividualKey == null)
                        db_info.IndividualKey = master_response;
                    else
                        db_info.IndividualKey = device.ReadIndividualKey(db_info.IndividualKey);

                    if (db_info == AccessDatabaseInfo.Master)
                        this.Authentification.AccessDatabase = db_info;
                }

                return ApoIdentApplicationState.None;
            }
            catch (Exception ex)
            {
                this.Exception = ex;
                this.ErrorMessage = ex.Message;
                return ApoIdentApplicationState.ConnnectionDeviceRegistrationFailed;
            }
        }

        public ApoIdentApplicationState CheckReleaseVersion()
        {
            if (IdentServerClient.Info.IsRelease == false)
            {
                this.Exception = null;
                this.ErrorMessage = Catalog.GetString("the installed ident module is a development version. Please install a released ident module.");
                return ApoIdentApplicationState.ConnectionCheckIdentModuleFailed;
            }

            return ApoIdentApplicationState.None;
        }

        public ApoIdentApplicationState CheckForPendingUpdate()
        {
            if (this.Config.IdentModuleSettings.ReleaseExpirationTime < DateTime.Now)
            {
                this.Exception = null;
                this.ErrorMessage = String.Empty;
                return ApoIdentApplicationState.ConnectionRemindOfUpdate;
            }

            return ApoIdentApplicationState.None;
        }

        public void Classify(Spectrum spectrum)
        {
            try
            {
                this.Result = this.Client.Classify(this.Authentification, spectrum) as ApoIdentResultSet;

                if (this.Result == null)
                    throw new Exception(Catalog.GetString("Cannot classify spectrum."));
            }
            catch (Exception ex)
            {
                this.Result = null;
                throw ex;
            }
        }

        public Dictionary<string,ResultSet> ClassifiyBlindClassification(Dictionary<string, ModelClassInfo> class_names, Spectrum spectrum)
        {
            Dictionary<string, ResultSet> sets = new Dictionary<string, ResultSet>();

            foreach (string class_name in class_names.Keys)
            {
                ModelClassInfo class_info = class_names[class_name];
                AuthenticationSet authentification = new AuthenticationSet
                {
                    AccessDatabase = class_info.DatabaseInfo,
                    ModelClassName = class_name,
                    Serial = this.Authentification.Serial,
                    SubstanceId = AuthenticationSet.NO_SUBSTANCE
                };

                sets.Add(class_name, null);
                sets[class_name] = this.Client.Classify(authentification, spectrum);
            }

            return sets;
        }

        public void DiscardResult()
        {
            this.Result = null;
        }

        public bool HasValidResult
        {
            get
            {
                return this.Result != null;
            }
        }

        public bool IsTruePositive
        {
            get
            {
                return this.HasValidResult == true && this.Result.TruePositive;
            }
        }

        public byte[] GetLogfile()
        {
            return this.Client.GetLogfile();
        }

        private static bool IsOnHostInstalled
        {
            get
            {
                if (EnvironmentInfo.OS == OperatingSystem.Windows)
                {
                    return File.Exists(FilesAndDirs.IdentServerExeFile);
                }
                else
                {
                    try
                    {
                        System.Diagnostics.Process proc = new System.Diagnostics.Process();
                        proc.StartInfo.FileName = "which";
                        proc.StartInfo.Arguments = IdentServerClient.LINUX_COMMAND;
                        proc.StartInfo.UseShellExecute = false;
                        proc.StartInfo.CreateNoWindow = true;
                        proc.Start();
                        proc.WaitForExit();
                        return proc.ExitCode == 0;
                    }
                    catch(Exception ex)
                    {
                        Console.WriteLine("Cannot open command in Linux: {0}", ex);
                            return false;
                    }
                }
            }
        }

        public string Address
        {
            get
            {
                string current_server_address = String.Empty;
                switch (this.Config.IdentModuleSettings.IdentificationMode)
                {
                    case Configurations.IdentificationMode.Local:
                        current_server_address = "localhost";
                        break;

                    case Configurations.IdentificationMode.Custom:
                        current_server_address = this.Config.IdentModuleSettings.Url;
                        break;

                    default:
                        break;
                }
                return current_server_address;
            }
        }

        private int TcpPort
        {
            get
            {
                if (this.Config.IdentModuleSettings.IdentificationMode == Configurations.IdentificationMode.Local)
                    return IdentModuleClient.TCP_PORT;

                return this.Config.IdentModuleSettings.TcpPort;
            }
        }

        public static bool CanConnectToIdentModule { get; private set; }

        private AuthenticationSet Authentification { get; set; }
        public ApoIdentResultSet Result { get; private set; }

        private System.Diagnostics.Process IdentServerProcess { get; set; }
        private static bool FoundRunningServer { get; set; }
        public List<AccessDatabaseInfo> Databases { get; private set; }
            
        public static IdentModuleInfo Info { get; private set; }
        private Configurations Config { get; set; }

   
        private IIdentServer Client { get; set; }
        public bool IsInitialized { get; private set; } = false;

        public string ErrorMessage { get; private set; } = String.Empty;
        public Exception Exception { get; private set; } = null;
    }
}

