// 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.IO;
using Hiperscan.Common.RegistryManagement;
using Hiperscan.Unix;

namespace Hiperscan.AnalysisModule.Remoting
{
    public abstract partial class RemoteConnector
    {
        public event EventHandler StateChanged = null;
        public event EventHandler ErrorReceived = null;

        public RemoteConnector(string address, int port, string mono_exe_file)
        {
            this.Address = address;
            this.Port = port;
            this.MonoExeFile = mono_exe_file;
        }

        public virtual void Clear()
        {
            this.State.Clear();
            this.TakeSecondChance = true;
            this.Info = null;
            this.LoadedModelClasses.Clear();
            this.Databases.Clear();
            this.Authentification = null;
            this.RemoteClient = null;
            this.CurrentModelClass = null;
            this.DiscartResult();
        }

        public void Start()
        {
            this.State.Clear();

            if (this.Ping() == true)
                return;

            this.StartServer();

            if (this.State.NoError == false)
            {
                if (this.TakeSecondChance == false)
                    return;

                this.TakeSecondChance = false;
                this.Start();
            }
            else
            {
                this.ConnectToServer();

                if (this.State.NoError == false)
                {
                    if (this.TakeSecondChance == false)
                        return;

                    this.TakeSecondChance = false;
                    this.Start();
                }
            }
        }

        private bool Ping()
        {
            try
            {
                this.RemoteClient = this.CreateInstance();
                this.Info = this.RemoteClient.RemoteService.GetServerInfo();
                return true;
            }
            catch (Exception)
            {
                this.RemoteClient = null;
                this.Info = null;
                return false;
            }
        }

        private void StartServer()
        {
            try
            {
                if (this.ServerProcess != null)
                {
                    this.Stop();
                    this.ServerProcess.Kill();

                    System.Threading.Thread.Sleep(500);
                    this.ServerProcess = null;
                }
            }
            catch (Exception ex)
            {
                this.State.Exception = ex;
            }

            try
            {
                this.ServerProcess = this.CreateServerProcess(false);
                this.ServerProcess.Start();
            }
            catch (Exception ex)
            {
                this.HandleConnectionError(ex);
            } 
        }

        private void ConnectToServer()
        {
            DateTime then = DateTime.Now;
            TimeSpan span = new TimeSpan(0, 0, 10);

            this.State.Clear();

            for (; ; )
            {
                System.Threading.Thread.Sleep(500);
                try
                {
                    this.RemoteClient = this.CreateInstance();
                    this.Info = this.RemoteClient.RemoteService.GetServerInfo();
                    break;
                }
                catch (System.Runtime.Remoting.RemotingException ex)
                {
                    if (DateTime.Now - then < span)
                        continue;

                    this.HandleConnectionError(ex);
                    break;
                }
                catch (Exception ex)
                {
                    this.HandleConnectionError(ex);
                    break;
                }
            }
        }

        private void HandleConnectionError(Exception ex)
        {
            if (this.TakeSecondChance == true)
            {
                this.State.Message = String.Format("{0} need second chance: {1}", this.ProductName, ex);
                this.CallStateChangedEvent();
            }

            this.State.Header = String.Format(Catalog.GetString("{0} connection refused"), this.ProductName);
            this.State.Exception = ex;
            this.State.Message = String.Format(Catalog.GetString("Error while connecting to {0}: {1}"), this.ProductName, ex.Message);

            if (this.TakeSecondChance == false)
                this.CallErrorReceivedEvent();
            else
                this.State.Clear();
        }

        public void Stop()
        {
            try
            {
                this.Clear();
            }
            catch (Exception ex)
            {
                this.State.Exception = ex;
            }

            if (this.ServerProcess == null)
                return;

            try
            {
                System.Diagnostics.Process proc = this.CreateServerProcess(true);
                proc.Start();
                proc.WaitForExit(3000);
            }
            catch (Exception ex)
            {
                this.State.Exception = ex;
            }

            try
            {
                if (this.ServerProcess.WaitForExit(2500) == false)
                    this.ServerProcess.Kill();
            }
            catch (Exception ex)
            {
                this.State.Exception = ex;
            }

            this.ServerProcess = null;
        }

        private System.Diagnostics.Process CreateServerProcess(bool stop)
        {
            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo.Arguments = String.Empty;
 
            if (this.IsWindows)
            {
                string dir = this.ModuleRegistryEntry.InstallPath;
                string file = Path.Combine(dir, this.ModuleRegistryEntry.ExecutingFile);

                proc.StartInfo.FileName = this.MonoExeFile;
                proc.StartInfo.Arguments += "\"" + file + "\"";
                proc.StartInfo.WorkingDirectory = dir;
            }
            else
            {
                proc.StartInfo.FileName = this.UnixCommand;
            }

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

            if (stop == true)
                proc.StartInfo.Arguments += " --stop";
            else
                proc.StartInfo.Arguments += " --activate-server-log ";

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

            Console.WriteLine("Create server proc: {0}", proc.StartInfo.Arguments);

            return proc;
        }

        public Dictionary<string, byte[]> GetLogFiles()
        {
            return this.RemoteClient.RemoteService.GetLogfiles();
        }

        protected void CallStateChangedEvent()
        {
            this.StateChanged?.Invoke(this, EventArgs.Empty);
        }

        protected void CallErrorReceivedEvent()
        {
            this.ErrorReceived?.Invoke(this, EventArgs.Empty);
        }

        protected abstract RemoteClient CreateInstance();

        public bool IsConnected
        {
            get
            {
                return this.Info != null;
            }
        }

        public bool IsInstalled
        {
            get
            {
                if (this.IsWindows)
                    return this.ModuleRegistryEntry.HasRegistryEntry;
                else
                    return FileIO.ExistsFile(this.UnixCommand);
            }
        }

        protected bool IsWindows
        {
            get
            {
                return Environment.OSVersion.Platform != PlatformID.Unix;
            }
        }

        protected System.Diagnostics.Process ServerProcess { get; set; }

        protected abstract string UnixCommand { get; }
        protected abstract HSRegistryEntry ModuleRegistryEntry { get; }
        protected RemoteClient RemoteClient { get; set; }

        protected string Address { get; set; }
        protected int Port { get; set; }
        protected string MonoExeFile { get; set; }

        public AnalysisServerInfo Info { get; private set; }

        public RemoteState State { get; set; } = new RemoteState();
        public abstract bool IsDebug { get; }
        protected bool TakeSecondChance { get; set; }
        protected abstract string ProductName { get; }
    }
}
