// Copyright 2018 RED Software, LLC. All Rights Reserved.

using IgniteEngine;
using IgniteEngine.IO;
using IgniteEngine.Networking;
using IgniteEngine.Protocols;
using LoginServer.Handlers;
using System;
using System.Collections.Generic;
using System.Data.Odbc;
using System.IO;
using System.Runtime;
using System.Threading;

namespace LoginServer
{
    internal class Program
    {
        internal static Dictionary<string, Account> CachedAccounts = new Dictionary<string, Account>();
        internal static INIFile Config;
        internal static NetworkServer ClientServer = new NetworkServer(NetworkConnectionType.NCT_CLIENT);
        internal static OdbcConnection ODBC;
        internal static List<NetworkTransfer> Transfers = new List<NetworkTransfer>();
        internal static List<World> Worlds = new List<World>();
        internal static NetworkServer WorldServer = new NetworkServer(NetworkConnectionType.NCT_WORLDMANAGER);
		internal static NetworkConnection GameLogServer = new NetworkConnection(NetworkConnectionType.NCT_DB_GAMELOG);

        private static readonly string configFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{Assembly.Name}.ini");

        internal static void Main()
        {
            var stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();

            Console.Title = $"{Assembly.Name} {Assembly.Version}{(System.Diagnostics.Debugger.IsAttached ? " (Debugging)" : "")} ";

            Debug.Log($"Local Time:          {DateTime.Now}");
            Debug.Log($"System (UTC) Time:   {DateTime.UtcNow}\n");

            // Check application recommendations
            if (!Environment.Is64BitProcess)
            {
                Debug.LogWarning("The process is currently not in 64-bit mode. Performance issues may occur. Press any key to continue code execution.");
                Console.ReadKey();
            }

            if (!GCSettings.IsServerGC)
            {
                Debug.LogWarning("Garbage collection is not configured to server mode.");
            }

            Debug.Log($"Loading configuration file '{Path.GetFileName(configFilePath)}'.");

            // Load configuration
            if (!File.Exists(configFilePath))
            {
                Debug.LogError("Configuration file was not found, please make sure it exists and has the correct properties. Press any key to quit.");
                Console.ReadKey();
                return;
            }

            Config = new INIFile(configFilePath, "Config");

            Debug.Log("Configuration file loaded successfully.");
            Debug.Log("Launching the server...\n");

            // Connect to the database
            InitDBConnection();
			
	        // Store handlers
	        StoreHandlers();

			// Setup networking
            WorldServer.Listen(Config.GetString("world_server_ip"), (ushort) Config.GetInt16("world_server_port"));
            ClientServer.Listen(Config.GetString("client_server_ip"), (ushort) Config.GetInt16("client_server_port"));
	        GameLogServer.Connect(Config.GetString("gamelog_server_ip"), (ushort) Config.GetInt16("gamelog_server_port"));

			stopwatch.Stop();
            Debug.Log($"Time taken to start: {stopwatch.ElapsedMilliseconds}ms\n");

            // Starts the main server loop.
            new Thread(() =>
            {
                while (true)
                {
                    Update(Time.Milliseconds);
                    Thread.Sleep(10);
                }

            }).Start();

            // Handle console commands
            while (true)
            {
                if (Console.ReadKey(true).Key == ConsoleKey.Enter)
                {
                    Console.Write("$ ");
            
                    var input = Console.ReadLine();
                    if (input?.Length > 0)
                    {
                        var parts = input.Split(new[] {' '}, 2);

                        if (parts.Length > 1)
                        {
                            HandleConsoleCommand(parts[0], parts[1].Split(' '));
                            continue;
                        }

                        HandleConsoleCommand(parts[0], null);
                    }
                }
            }
        }

        private static void HandleConsoleCommand(string command, string[] args)
        {
            switch (command.ToLower())
            {
                case "clear":
                    Console.Clear();
                    break;
                case "performance":
                    Debug.Log($"CPU: {Profiler.CPUUsage}% RAM: {Profiler.RAMUsage}MB");
                    break;
	            case "clients":
		            Debug.Log($"World Clients: {WorldServer.Connections.Count}");
					Debug.Log($"Game Clients: {ClientServer.Connections.Count}");
					break;
			}
        }

        private static void InitDBConnection()
        {
            try
            {
                ODBC = new OdbcConnection(Config.GetString("odbc_connection_string"));
                ODBC.Open();

                var startupCommand = ODBC.CreateCommand();
                startupCommand.CommandText = Config.GetString("odbc_startup_command");
                startupCommand.ExecuteNonQuery();
            }
            catch (Exception e)
            {
                Debug.LogException($"Could not initialize the ODBC database connection: {e.Message}");
            }
        }

        private static void StoreHandlers()
        {
			NetworkMessageHandler.Store(NetworkCommand.NC_MISC_SEED_ACK, MiscHandlers.NC_MISC_SEED_ACK);
	        NetworkMessageHandler.Store(NetworkCommand.NC_MISC_S2SCONNECTION_RDY, MiscHandlers.NC_MISC_S2SCONNECTION_RDY);
			NetworkMessageHandler.Store(NetworkCommand.NC_MISC_S2SCONNECTION_REQ, MiscHandlers.NC_MISC_S2SCONNECTION_REQ);

            NetworkMessageHandler.Store(NetworkCommand.NC_USER_CLIENT_VERSION_CHECK_REQ, UserHandlers.NC_USER_CLIENT_VERSION_CHECK_REQ);
            NetworkMessageHandler.Store(NetworkCommand.NC_USER_US_LOGIN_REQ, UserHandlers.NC_USER_US_LOGIN_REQ);
            NetworkMessageHandler.Store(NetworkCommand.NC_USER_XTRAP_REQ, UserHandlers.NC_USER_XTRAP_REQ);
            NetworkMessageHandler.Store(NetworkCommand.NC_USER_WORLD_STATUS_REQ, UserHandlers.NC_USER_WORLD_STATUS_REQ);
            NetworkMessageHandler.Store(NetworkCommand.NC_USER_WORLDSELECT_REQ, UserHandlers.NC_USER_WORLDSELECT_REQ);
            NetworkMessageHandler.Store(NetworkCommand.NC_USER_NORMALLOGOUT_CMD, UserHandlers.NC_USER_NORMALLOGOUT_CMD);
            NetworkMessageHandler.Store(NetworkCommand.NC_USER_LOGIN_WITH_OTP_REQ, UserHandlers.NC_USER_LOGIN_WITH_OTP_REQ);
            NetworkMessageHandler.Store(NetworkCommand.NC_USER_WILLLOGIN_ACK, UserHandlers.NC_USER_WILLLOGIN_ACK);
        }

        private static void Update(long now)
        {
            Worlds.FilteredAction(world => !world.Connection.IsConnected, world =>
            {
                world.Status = 0x00;
            });

            // Remove transfers that have been waiting for more that 5 seconds.
            var timeouts = Transfers.Filter(t => now - t.CreateTime >= 5000);
            timeouts.ForBackwards(transfer =>
            {
                new PROTO_NC_USER_LOGINFAIL_ACK(0x49).Send(transfer.Connection);
                Transfers.RemoveSafe(transfer);
            });
        }
    }
}
