// Copyright © 2017-2018 Atomic Software, LLC. All Rights Reserved.
// See LICENSE.md for full license information.

using Atom.Core.Diagnostics;
using Atom.Core.IO;
using Atom.Core.Networking;
using Atom.Core.Networking.Messages;
using Atom.Core.Platform;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Odbc;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime;
using System.Threading;

namespace Atom.Core
{
    public class Server
    {
        public bool IsRunning { get; set; }
        public OdbcConnection ODBCConnection { get; set; }
        public ConfigurationFile Config { get; set; }
        public bool IsODBCConnected => ODBCConnection.State == ConnectionState.Open;

        private readonly Stopwatch StartTimer;
        private readonly string ConfigFileName = $"{AtomAssembly.Name}.ini";
        private readonly object ExceptionLock = new object();
        private bool IsHandlingException;
        private IEnumerable<IEngine> Engines;
        
        public Server()
        {
            StartTimer = new Stopwatch();
        }

        public void Start()
        {
            AppDomain.CurrentDomain.UnhandledException += ReportCrash;

            Console.Title = $"{AtomAssembly.Name} v{AtomAssembly.Version} {(Debugger.IsAttached ? " (Debugging)" : "")}";
            IsRunning = true;
            Config = new ConfigurationFile(AppDomain.CurrentDomain.BaseDirectory + ConfigFileName);
            Engines = new Collections.List<IEngine>();

            StartTimer.Start();

            if (!Environment.Is64BitProcess)
            {
                Log.PerformanceWarning("The process is currently not in 64-bit mode. Performance issues may occur. Press any key to continue with initialization.");
                Console.ReadKey();
            }

            if (!GCSettings.IsServerGC)
            {
                Log.Warning("GC is not configured to server mode");
            }

            Log.Info($"GC latency mode is set to '{GCSettings.LatencyMode}'");
            Log.Info("Launching the server");
            Log.Info($"Local Time: {DateTime.Now}");
            Log.Info($"System Time: (UTC): {DateTime.UtcNow}");
            Log.Info("Loading configuration");

            if (!File.Exists(ConfigFileName))
            {
                Log.CriticalError($"Could not find the file '{ConfigFileName}'. Press any key to stop the server.");
                Console.ReadKey();
                Stop();
                return;
            }

            Log.Info($"Loaded server configuration '{ConfigFileName}'");

            Log.Info("Connect to the database");
            ConnectToDatabase();

            Log.Info("Loading message handlers");
            MessageHandlerLoader.LoadHandlers();

            PerformStartActions();

            StartEngines();
            StartMainLoop();

            StartTimer.Stop();
            Log.Info($"Start time: ({StartTimer.ElapsedMilliseconds})ms");
        }

        private void ConnectToDatabase()
        {
            ODBCConnection = new OdbcConnection(Config.GetString("ODBC", "ConnectionString"));

            try
            {
                ODBCConnection.Open();

                var StartupCommand = ODBCConnection.CreateCommand();
                StartupCommand.CommandText = Config.GetString("ODBC", "StartupCommand");
                StartupCommand.ExecuteNonQuery();

                Log.Info($"Connected to the database '{ODBCConnection.DataSource}'");
            }
            catch (Exception ex)
            {
                Log.Warning("Database connection attempt failed");
				Log.Warning(ex.Message);
                Thread.Sleep(3000);
            }
        }

        public void Stop(Exception exception = null)
        {
            if (exception != null)
            {
                Log.CriticalError("CrashLog", $"{exception}", false);
                Log.CriticalError("The server has crashed. Check the crash log.");
            }

            Client.ActiveClients.ToList().ForEach(x => x.Disconnect());
            Listener.ActiveListeners.ToList().ForEach(x => x.Shutdown());

            PerformStopActions();
            Log.Warning("The server is now offline. Press ESC to exit.");

            while (!Console.KeyAvailable || Console.ReadKey(true).Key != ConsoleKey.Escape)
            {
            }

            IsRunning = false;
            Environment.Exit(0);
        }

        private void StartEngines()
        {
            lock (Engines)
            {
                Engines = 
                    from Type in Assembly.GetEntryAssembly().GetTypes()
                    where Type.GetInterfaces().Contains(typeof(IEngine)) && Type.GetConstructor(Type.EmptyTypes) != null
                    select Activator.CreateInstance(Type) as IEngine;
            }
        }

        private void StartMainLoop()
        {
            new Thread(() =>
            {
                while (IsRunning)
                {
                    var CurrentTime = SystemTime.Milliseconds();

                    lock (Engines)
                    {
                        foreach (var Engine in Engines)
                        {
                            Engine.Main(CurrentTime);
                        }
                    }

                    Main(CurrentTime);
                    Thread.Sleep(10);
                }
            }).Start();
        }

        /// <summary>
        /// <see cref="PerformStartActions"/> is a method able to be overidden to perform 
        /// additional actions once the server starts. It is called immediately after the 
        /// generic start code is executed. If the method is not declared, then no additional 
        /// code will be executed.
        /// </summary>
        protected virtual void PerformStartActions()
        {
        }

        /// <summary>
        /// <see cref="PerformStopActions"/> is a method able to be overidden to perform 
        /// additional actions once the server stops. It is called immediately after the 
        /// generic stop code is executed. If the method is not declared, then no additional 
        /// code will be executed.
        /// </summary>
        protected virtual void PerformStopActions()
        {
        }

        /// <summary>
        /// <see cref="Main"/> is a method able to be overidden to perform 
        /// additional actions during the server's main loop. It is called immediately after the 
        /// generic main loop code is executed. If the method is not declared, then no additional 
        /// code will be executed.
        /// </summary>
        protected virtual void Main(long now)
        {
        }

        private void ReportCrash(object sender, UnhandledExceptionEventArgs e)
        {
            lock (ExceptionLock)
            {
                if (IsHandlingException)
                {
                    return;
                }

                IsHandlingException = true;
            }

            Stop((Exception) e.ExceptionObject);
        }
    }
}
