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

using IgniteEngine;
using IgniteEngine.Content.GameObjects;
using IgniteEngine.Definitions.SHN;
using IgniteEngine.IO;
using IgniteEngine.Networking;
using System;
using System.Collections.Generic;
using System.Data.Odbc;
using System.IO;
using System.Runtime;
using System.Threading;
using WorldManagerServer.Handlers;

namespace WorldManagerServer
{
    internal class Program
    {
        internal static INIFile Config;
        internal static NetworkServer ClientServer = new NetworkServer(NetworkConnectionType.NCT_CLIENT);
        internal static NetworkConnection LoginServer = new NetworkConnection(NetworkConnectionType.NCT_LOGIN);
		internal static NetworkConnection GameLogServer = new NetworkConnection(NetworkConnectionType.NCT_DB_GAMELOG);
        internal static OdbcConnection ODBC;
        internal static string ShinePath => Config.GetString("shine_data");
        internal static List<NetworkTransfer> Transfers = new List<NetworkTransfer>();
        internal static NetworkServer ZoneServer = new NetworkServer(NetworkConnectionType.NCT_ZONE);

        /* Shine Data */
        internal static Dictionary<CharacterClass, DefaultCharacterData> DefaultCharacterData = new Dictionary<CharacterClass, DefaultCharacterData>();
        internal static ObjectCollection<FaceInfo> FaceInfo = new ObjectCollection<FaceInfo>();
        internal static ObjectCollection<HairInfo> HairInfo = new ObjectCollection<HairInfo>();
        internal static ObjectCollection<HairColorInfo> HairColorInfo = new ObjectCollection<HairColorInfo>();
        internal static ObjectCollection<SingleData> SingleData = new ObjectCollection<SingleData>();

        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.\n");
            }

            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
			// Connect to the login server before actually listening for anything.
	        GameLogServer.Connect(Config.GetString("gamelog_server_ip"), (ushort)Config.GetInt16("gamelog_server_port"));
			LoginServer.Connect(Config.GetString("login_server_ip"), (ushort) Config.GetInt16("login_server_port"));

            ZoneServer.Listen(Config.GetString("zone_server_ip"), (ushort) Config.GetInt16("zone_server_port"));
            ClientServer.Listen(Config.GetString("client_server_ip"), (ushort) Config.GetInt16("client_server_port"));

            new Thread(() =>
            {
                while (true)
                {
                    Update(Time.Milliseconds);
                    Thread.Sleep(10);
                }

            }).Start();

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

            // 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($"Zone Clients: {ZoneServer.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_ACK, MiscHandlers.NC_MISC_S2SCONNECTION_ACK);
	        NetworkMessageHandler.Store(NetworkCommand.NC_MISC_GAMETIME_REQ, MiscHandlers.NC_MISC_GAMETIME_REQ);

			NetworkMessageHandler.Store(NetworkCommand.NC_USER_WILLLOGIN_REQ, UserHandlers.NC_USER_WILLLOGIN_REQ);
            NetworkMessageHandler.Store(NetworkCommand.NC_USER_LOGINWORLD_REQ, UserHandlers.NC_USER_LOGINWORLD_REQ);
            NetworkMessageHandler.Store(NetworkCommand.NC_USER_NORMALLOGOUT_CMD, UserHandlers.NC_USER_NORMALLOGOUT_CMD);

            NetworkMessageHandler.Store(NetworkCommand.NC_AVATAR_CREATE_REQ, AvatarHandlers.NC_AVATAR_CREATE_REQ);
            NetworkMessageHandler.Store(NetworkCommand.NC_AVATAR_ERASE_REQ, AvatarHandlers.NC_AVATAR_ERASE_REQ);
        }

        private static void Update(long now)
        {
            // Remove transfers that have been waiting for more that 5 seconds.
            var timeouts = Transfers.Filter(t => now - t.CreateTime >= 5000);
            timeouts.ForBackwards(Transfers.RemoveSafe);
        }
    }
}
