using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Timers;
using Timer = System.Timers.Timer;

using PSO2SERVER.Database;
using PSO2SERVER.Models;
using PSO2SERVER.Object;
using PSO2SERVER.Packets;
using PSO2SERVER.Packets.PSOPackets;
using PSO2SERVER.Zone;
using Org.BouncyCastle.Tls;

namespace PSO2SERVER
{
    public delegate void ConsoleCommandDelegate(string[] args, int length, string full, Client client);

    public class ConsoleCommandArgument
    {
        public ConsoleCommandArgument(string name, bool optional)
        {
            Name = name;
            Optional = optional;
        }

        public string Name { get; set; }
        public bool Optional { get; set; }
    }

    public class ConsoleCommand
    {
        public ConsoleCommand(ConsoleCommandDelegate cmd, params string[] cmdNames)
        {
            if (cmdNames == null || cmdNames.Length < 1)
                throw new NotSupportedException();

            Command = cmd;
            Names = new List<string>(cmdNames);
            Arguments = new List<ConsoleCommandArgument>();
        }

        public ConsoleCommandDelegate Command { get; set; }
        public List<string> Names { get; set; }
        public List<ConsoleCommandArgument> Arguments { get; set; }
        public string Help { get; set; }

        public bool Run(string[] args, int length, string full, Client client)
        {
            try
            {
                Command(args, length, full, client);
            }
            catch (IndexOutOfRangeException ex)
            {
                Logger.WriteException("无效指令参数", ex);
                return false;
            }
            catch (Exception ex)
            {
                Logger.WriteException("指令错误", ex);
                return false;
            }

            return true;
        }
    }

    /// <summary>
    ///     Handles commands and drawing/updating the console
    /// </summary>
    public class ConsoleSystem
    {
        private readonly object _consoleLock = new object();

        private readonly List<ConsoleKey> _controlKeys = new List<ConsoleKey>
        {
            ConsoleKey.Backspace,
            ConsoleKey.Enter,
            ConsoleKey.UpArrow,
            ConsoleKey.DownArrow,
            ConsoleKey.LeftArrow,
            ConsoleKey.RightArrow,
            ConsoleKey.Home,
            ConsoleKey.End,
            ConsoleKey.Delete
        };

        private const string Prompt = "[CMD]> ";

        private string _commandLine = string.Empty;
        private int _commandIndex;
        private int _commandRowInConsole;

        private readonly List<string> _history = new List<string>();
        private int _historyIndex;

        private int _lastDrawnCommandLineSize;
        private int _maxCommandLineSize = -1;

        public Thread Thread;

        public List<ConsoleCommand> Commands = new List<ConsoleCommand>();
        private ConsoleKeyInfo _key;
        
        // ReSharper disable once InconsistentNaming
        public Timer timer;

        public ConsoleSystem()
        {
            Console.Title = ServerApp.ServerName;
            Console.CursorVisible = true;
            SetSize(80, 24);

            timer = new Timer(1000);
            timer.Elapsed += TimerRefresh;
            timer.Start();

            CreateCommands();
        }

        public int Width { get; private set; }
        public int Height { get; private set; }

        public void SetSize(int width, int height)
        {
            Width = width;
            Height = height;

            _maxCommandLineSize = width - Prompt.Length;

            Console.SetWindowSize(width, height);
        }

        public void AddLine(ConsoleColor color, string text)
        {
            lock (_consoleLock)
            {
                BlankDrawnCommandLine();

                var useColors = ServerApp.Config.UseConsoleColors;
                var saveColor = Console.ForegroundColor;

                Console.SetCursorPosition(0, _commandRowInConsole);
                if (useColors)
                    Console.ForegroundColor = color;
                Console.WriteLine(text);

                if (useColors)
                    Console.ForegroundColor = saveColor;

                Console.WriteLine();
                _commandRowInConsole = Console.CursorTop - 1;

                RefreshCommandLine();
                FixCursorPosition();
            }
        }

        private void BlankDrawnCommandLine()
        {
            if (_lastDrawnCommandLineSize > 0)
            {
                Console.SetCursorPosition(0, _commandRowInConsole);

                for (int i = 0; i < _lastDrawnCommandLineSize; i++)
                    Console.Write(' ');

                _lastDrawnCommandLineSize = 0;
            }
        }

        private void RefreshCommandLine()
        {
            BlankDrawnCommandLine();

            Console.SetCursorPosition(0, _commandRowInConsole);
            Console.Write(Prompt);
            Console.Write(_commandLine);

            _lastDrawnCommandLineSize = Prompt.Length + _commandLine.Length;
        }

        private void FixCursorPosition()
        {
            Console.SetCursorPosition(Prompt.Length + _commandIndex, _commandRowInConsole);
        }

        private string AssembleInfoBar()
        {
            if (ServerApp.Instance != null && ServerApp.Instance.Server != null)
            {
                var clients = ServerApp.Instance.Server.Clients.Count;
                float usage = Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024;

                var time = DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToLongTimeString();

                return string.Format("当前玩家: {0} | 实时内存: {1} MB | {2}", clients, usage, time);
            }

            return "正在初始化...";
        }

        private void TimerRefresh(object sender, ElapsedEventArgs e)
        {
            lock (_consoleLock)
            {
                Console.Title = ServerApp.ServerName + " - " + AssembleInfoBar();
            }
        }

        public static void StartThread()
        {
            while (true)
            {
                try
                {
                    ServerApp.ConsoleSystem.CheckInput();
                }
                catch (ThreadInterruptedException ex)
                {
                    Logger.WriteException("Thread inturrupted", ex);
                }
                catch (ThreadStateException ex)
                {
                    Logger.WriteException("Thread state error", ex);
                }
                catch (Exception ex)
                {
                    Logger.WriteException(string.Empty, ex);
                }
            }
        }

        public void CreateCommands()
        {
            // Help
            var help = new ConsoleCommand(Help, "help") { Help = "显示服务端所有指令帮助" };
            Commands.Add(help);

            // Config
            var config = new ConsoleCommand(Config, "config", "c");
            config.Arguments.Add(new ConsoleCommandArgument("Save | Load | List | Option Name", false));
            config.Arguments.Add(new ConsoleCommandArgument("Value", true));
            config.Help = "Set a configuration variable to the specified value";
            Commands.Add(config);

            // Clear
            var clearLog = new ConsoleCommand(ClearLog, "clear", "cls") { Help = "Clears the current log buffer" };
            Commands.Add(clearLog);

            // Echo
            var echo = new ConsoleCommand(Echo, "echo") { Help = "Echo the given text back into the Console" };
            echo.Arguments.Add(new ConsoleCommandArgument("text", false));
            Commands.Add(echo);

            var lua = new ConsoleCommand(RunLUA, "lua");
            lua.Arguments.Add(new ConsoleCommandArgument("user", true));
            lua.Arguments.Add(new ConsoleCommandArgument("lua", false));
            Commands.Add(lua);

            // Announce
            var announce = new ConsoleCommand(Announce, "announce", "a");
            announce.Arguments.Add(new ConsoleCommandArgument("Message", false));
            announce.Help = "Sends a message to all connected players";
            Commands.Add(announce);

            // ClearPlayers
            var clearPlayers = new ConsoleCommand(ClearPlayers, "clearplayers", "cp");
            clearPlayers.Arguments.Add(new ConsoleCommandArgument("Exempt Username", true));
            clearPlayers.Help = "Disconnects all players from the server";
            Commands.Add(clearPlayers);

            // SpawnClone
            var spawnClone = new ConsoleCommand(SpawnClone, "spawnclone");
            spawnClone.Arguments.Add(new ConsoleCommandArgument("Username", false));
            spawnClone.Arguments.Add(new ConsoleCommandArgument("Player Name", false));
            spawnClone.Arguments.Add(new ConsoleCommandArgument("X", false));
            spawnClone.Arguments.Add(new ConsoleCommandArgument("Y", false));
            spawnClone.Arguments.Add(new ConsoleCommandArgument("Z", false));
            spawnClone.Help = "Spawns a clone of your character";
            Commands.Add(spawnClone);

            // SendPacket
            var sendPacket = new ConsoleCommand(SendPacket, "sendpacket", "sendp");
            sendPacket.Arguments.Add(new ConsoleCommandArgument("Name", false));
            sendPacket.Arguments.Add(new ConsoleCommandArgument("Type", false));
            sendPacket.Arguments.Add(new ConsoleCommandArgument("SubType", false));
            sendPacket.Arguments.Add(new ConsoleCommandArgument("Flags", false));
            sendPacket.Arguments.Add(new ConsoleCommandArgument("Data ...", false));
            sendPacket.Help = "Sends a packet to a client";
            Commands.Add(sendPacket);

            // SendPacketFile
            var sendPacketFile = new ConsoleCommand(SendPacketFile, "sendpacketfile", "sendpf");
            sendPacketFile.Arguments.Add(new ConsoleCommandArgument("Username", false));
            sendPacketFile.Arguments.Add(new ConsoleCommandArgument("Filename", false));
            sendPacketFile.Help = "Sends the specified file's contents as a packet";
            Commands.Add(sendPacketFile);

            // SendPacketDirectory
            var sendPacketDirectory = new ConsoleCommand(SendPacketDirectory, "sendpacketdirectory", "sendpd");
            sendPacketDirectory.Arguments.Add(new ConsoleCommandArgument("Username", false));
            sendPacketDirectory.Arguments.Add(new ConsoleCommandArgument("Dirname", false));
            sendPacketDirectory.Help = "Sends the specified directory's contents as a packet";
            Commands.Add(sendPacketDirectory);

            // SendPacketDirectory Slow
            var sendPacketDirectorySlow = new ConsoleCommand(SendPacketDirectorySlow, "sendpacketdirectoryslow", "sendpds");
            sendPacketDirectorySlow.Arguments.Add(new ConsoleCommandArgument("Username", false));
            sendPacketDirectorySlow.Arguments.Add(new ConsoleCommandArgument("Dirname", false));
            sendPacketDirectorySlow.Arguments.Add(new ConsoleCommandArgument("Sleeptime", false));
            sendPacketDirectorySlow.Help = "Sends the specified directory's contents as a packet (With delay between packets)";
            Commands.Add(sendPacketDirectorySlow);

            var teleportPlayer = new ConsoleCommand(TeleportPlayer, "teleportplayer", "tp");

            teleportPlayer.Arguments.Add(new ConsoleCommandArgument("RotX", false));
            teleportPlayer.Arguments.Add(new ConsoleCommandArgument("RotY", false));
            teleportPlayer.Arguments.Add(new ConsoleCommandArgument("RotZ", false));
            teleportPlayer.Arguments.Add(new ConsoleCommandArgument("RotW", false));

            teleportPlayer.Arguments.Add(new ConsoleCommandArgument("PosX", false));
            teleportPlayer.Arguments.Add(new ConsoleCommandArgument("PosY", false));
            teleportPlayer.Arguments.Add(new ConsoleCommandArgument("PosZ", false));

            teleportPlayer.Arguments.Add(new ConsoleCommandArgument("Username", true));

            teleportPlayer.Help = "Teleports a player to the given position.";
            Commands.Add(teleportPlayer);

            var teleportPlayer2 = new ConsoleCommand(TeleportPlayer_POS, "teleportplayerpos", "tpp");

            teleportPlayer2.Arguments.Add(new ConsoleCommandArgument("PosX", false));
            teleportPlayer2.Arguments.Add(new ConsoleCommandArgument("PosY", false));
            teleportPlayer2.Arguments.Add(new ConsoleCommandArgument("PosZ", false));

            teleportPlayer2.Arguments.Add(new ConsoleCommandArgument("Username", true));

            teleportPlayer2.Help = "Teleports a player to the given position. (pos only)";
            Commands.Add(teleportPlayer2);

            var changeThezone = new ConsoleCommand(ChangeArea, "areachange", "map");
            changeThezone.Arguments.Add(new ConsoleCommandArgument("username", false));
            changeThezone.Arguments.Add(new ConsoleCommandArgument("zoneID", false));
            changeThezone.Arguments.Add(new ConsoleCommandArgument("mapNumber", false));
            changeThezone.Arguments.Add(new ConsoleCommandArgument("flags", false));
            changeThezone.Arguments.Add(new ConsoleCommandArgument("seed", false));
            changeThezone.Arguments.Add(new ConsoleCommandArgument("sizeX", false));
            changeThezone.Arguments.Add(new ConsoleCommandArgument("sizeY", false));
            changeThezone.Arguments.Add(new ConsoleCommandArgument("templateNum", false));

            teleportPlayer.Help = "Spawns you elsewhere.";
            Commands.Add(changeThezone);

            var SpawnObjectCommand = new ConsoleCommand(SpawnObject, "spawnobject", "sobj");
            SpawnObjectCommand.Arguments.Add(new ConsoleCommandArgument("username", true));
            SpawnObjectCommand.Arguments.Add(new ConsoleCommandArgument("objectName", false));
            SpawnObjectCommand.Arguments.Add(new ConsoleCommandArgument("entityID", false));

            SpawnObjectCommand.Arguments.Add(new ConsoleCommandArgument("RotX", false));
            SpawnObjectCommand.Arguments.Add(new ConsoleCommandArgument("RotY", false));
            SpawnObjectCommand.Arguments.Add(new ConsoleCommandArgument("RotZ", false));
            SpawnObjectCommand.Arguments.Add(new ConsoleCommandArgument("RotW", false));
            SpawnObjectCommand.Arguments.Add(new ConsoleCommandArgument("PosX", false));
            SpawnObjectCommand.Arguments.Add(new ConsoleCommandArgument("PosY", false));
            SpawnObjectCommand.Arguments.Add(new ConsoleCommandArgument("PosZ", false));

            SpawnObjectCommand.Help = "Spawns an object. (Does not contain object arguments/flags.";
            Commands.Add(SpawnObjectCommand);

            var ImportNPC = new ConsoleCommand(ImportNPCs, "importnpc");
            ImportNPC.Arguments.Add(new ConsoleCommandArgument("zone", false));
            ImportNPC.Arguments.Add(new ConsoleCommandArgument("npcfolder", false));
            ImportNPC.Help = "Imports a folder of NPC spawn packets into the database.";
            Commands.Add(ImportNPC);

            var ImportObject = new ConsoleCommand(ImportObjects, "importobjects");
            ImportObject.Arguments.Add(new ConsoleCommandArgument("zone", false));
            ImportObject.Arguments.Add(new ConsoleCommandArgument("objectfolder", false));
            ImportObject.Help = "Imports a folder of object spawn packets into the database.";
            Commands.Add(ImportObject);

            var tellLoc = new ConsoleCommand(TellPosition, "pos")
            {
                Help = "Tells you your current location. (Need to be a client to use this.)"
            };
            Commands.Add(tellLoc);

            // Exit
            var exit = new ConsoleCommand(Exit, "exit", "quit") { Help = "Close the PSO2 Server" };
            Commands.Add(exit);
        }

        public void CheckInput()
        {
            // Read key
            _key = Console.ReadKey(true);

            // Check to make sure this is a valid key to append to the command line
            var validKey = _controlKeys.All(controlKey => _key.Key != controlKey);

            // Append key to the command line
            if (validKey && (_commandLine.Length + 1) < _maxCommandLineSize)
            {
                _commandLine = _commandLine.Insert(_commandIndex, _key.KeyChar.ToString());
                _commandIndex++;
            }

            // Backspace
            if (_key.Key == ConsoleKey.Backspace && _commandLine.Length > 0 && _commandIndex > 0)
            {
                _commandLine = _commandLine.Remove(_commandIndex - 1, 1);
                _commandIndex--;
            }

            // Cursor movement
            if (_key.Key == ConsoleKey.LeftArrow && _commandLine.Length > 0 && _commandIndex > 0)
                _commandIndex--;
            if (_key.Key == ConsoleKey.RightArrow && _commandLine.Length > 0 && _commandIndex <= _commandLine.Length - 1)
                _commandIndex++;
            if (_key.Key == ConsoleKey.Home)
                _commandIndex = 0;
            if (_key.Key == ConsoleKey.End)
                _commandIndex = _commandLine.Length;

            // History
            if (_key.Key == ConsoleKey.UpArrow && _history.Count > 0)
            {
                _historyIndex--;

                if (_historyIndex < 0)
                    _historyIndex = _history.Count - 1;

                _commandLine = _history[_historyIndex];
                _commandIndex = _history[_historyIndex].Length;
            }
            if (_key.Key == ConsoleKey.DownArrow && _history.Count > 0)
            {
                _historyIndex++;

                if (_historyIndex > _history.Count - 1)
                    _historyIndex = 0;

                _commandLine = _history[_historyIndex];
                _commandIndex = _history[_historyIndex].Length;
            }

            // Run Command
            if (_key.Key == ConsoleKey.Enter)
            {
                var valid = false;

                // Stop if the command line is blank
                if (string.IsNullOrEmpty(_commandLine))
                    Logger.WriteWarning("[CMD] 未指定指令");
                else
                {
                    // Iterate commands
                    foreach (var command in Commands)
                    {
                        var full = _commandLine;
                        var args = full.Split(' ');

                        if (command.Names.Any(name => args[0].ToLower() == name.ToLower()))
                        {
                            command.Run(args, args.Length, full, null);
                            valid = true;
                        }

                        if (valid)
                            break;
                    }

                    if (!valid)
                        Logger.WriteError("[CMD] {0} - 指令未找到", _commandLine.Split(' ')[0].Trim('\r'));

                    // Add the command line to history and wipe it
                    _history.Add(_commandLine);
                    _historyIndex = _history.Count;
                    _commandLine = string.Empty;
                }

                _commandIndex = 0;
            }

            lock (_consoleLock)
            {
                RefreshCommandLine();
                FixCursorPosition();
            }
        }

        #region Command Handlers

        // TODO: Use that fancy popup box when sending help to a client
        private void Help(string[] args, int length, string full, Client client)
        {
            Logger.WriteCommand(client, "[CMD] 指令帮助菜单");

            foreach (var command in Commands)
            {
                if (command.Help != string.Empty)
                {
                    var help = string.Empty;

                    // Name
                    help += command.Names[0];

                    // Arguments
                    if (command.Arguments.Count > 0)
                        foreach (var argument in command.Arguments)
                            if (argument.Optional)
                                help += " [" + argument.Name + "]";
                            else
                                help += " <" + argument.Name + ">";

                    // Seperator
                    help += " - ";

                    // Help
                    help += command.Help + " ";

                    // Aliases
                    if (command.Names.Count > 1)
                    {
                        help += "[aliases: ";

                        for (var i = 1; i < command.Names.Count; i++)
                            if (i == command.Names.Count - 1)
                                help += command.Names[i];
                            else
                                help += command.Names[i] + ", ";

                        help += "]";
                    }

                    // Log it
                    Logger.WriteCommand(client, "[CMD] {0}", help);
                }
            }
        }

        private void Echo(string[] args, int length, string full, Client client)
        {
            var echo = string.Empty;
            for (var i = 1; i < args.Length; i++)
                echo += args[i];

            Logger.WriteCommand(client, "[CMD] " + echo);
        }

        private void Announce(string[] args, int length, string full, Client client)
        {
            var message = full.Split('"')[1].Split('"')[0].Trim('\"');
            var messagePacket = new SystemMessagePacket(message, SystemMessagePacket.MessageType.GoldenTicker);

            foreach (var c in Server.Instance.Clients)
            {
                if (c.Character == null)
                    continue;

                c.SendPacket(messagePacket);
            }

            Logger.WriteCommand(client, "[CMD] 发送公告至所有玩家");
        }

        private void ClearLog(string[] args, int length, string full, Client client)
        {
            // What do, how do we deal with this now --Ninji
        }

        private void Config(string[] args, int length, string full, Client client)
        {
            var fields = ServerApp.Config.GetType().GetFields();
            var field = fields.FirstOrDefault(o => o.Name == args[1]);

            switch (args[1].ToLower())
            {
                case "save":
                    ServerApp.Config.Save();
                    break;

                case "load":
                    ServerApp.Config.Load();
                    break;

                case "list":
                    Logger.WriteCommand(client, "[CMD] 设置选项");
                    foreach (var f in fields)
                        Logger.WriteCommand(client, "[CMD] {0} = {1}", f.Name, f.GetValue(ServerApp.Config));
                    break;

                default: // Set a config option
                    if (args.Length < 3)
                        Logger.WriteCommand(client, "[CMD] Too few arguments");
                    else if (field != null)
                    {
                        var value = args[2].Contains('\"') ? full.Split('"')[1].Split('"')[0].Trim('\"') : args[2];

                        if (!ServerApp.Config.SetField(args[1], value))
                            Logger.WriteCommand(client, "[CMD] Config option {0} could not be changed to {1}", args[1],
                                value);
                        else
                        {
                            Logger.WriteCommand(client, "[CMD] Config option {0} changed to {1}", args[1], value);
                            ServerApp.Config.SettingsChanged();
                        }
                    }
                    else
                        Logger.WriteCommand(client, "[CMD] Config option {0} not found", args[1]);
                    break;
            }
        }

        private void Exit(string[] args, int length, string full, Client client)
        {
            Environment.Exit(0);
        }

        private void ClearPlayers(string[] args, int length, string full, Client client)
        {
            // Temporary haxifications to pull your own connection
            var id = -1;
            var foundPlayer = false;

            if (args.Length > 1)
            {
                var name = args[1];

                // Find the player
                id = Helper.FindPlayerByUsername(name);
                if (id != -1)
                    foundPlayer = true;

                // Couldn't find the username
                if (!foundPlayer)
                {
                    Logger.WriteCommand(client, "[CMD] Could not find user " + name);
                    return;
                }
            }

            for (var i = 0; i < ServerApp.Instance.Server.Clients.Count; i++)
            {
                if (id > -1 && i == id)
                    continue;

                // This is probably not the right way to do this
                ServerApp.Instance.Server.Clients[i].Socket.Close();
                Logger.WriteCommand(client,
                    "[CMD] Logged out user " + ServerApp.Instance.Server.Clients[i].User.Username);
            }

            Logger.WriteCommand(client, "[CMD] Logged out all players successfully");
        }

        private void SpawnClone(string[] args, int length, string full, Client client)
        {
            // Temporary haxifications to pull your own connection
            var name = args[1].Trim('\"');
            var playerName = args[2].Trim('\"');
            var x = float.Parse(args[3]);
            var y = float.Parse(args[4]);
            var z = float.Parse(args[5]);

            if (client == null)
            {
                var foundPlayer = false;

                // Find the player
                var id = Helper.FindPlayerByUsername(name);
                if (id != -1)
                    foundPlayer = true;

                // Couldn't find the username
                if (!foundPlayer)
                {
                    Logger.WriteError("[CMD] Could not find user " + name);
                    return;
                }

                client = ServerApp.Instance.Server.Clients[id];
            }

            // Default coordinates
            if (x == 0)
                x = -0.417969f;
            if (y == 0)
                y = 0.000031f;
            if (z == 0)
                z = 134.375f;

            var fakePlayer = new Player
            {
                Username = name,
                Nickname = playerName,
                PlayerId = (12345678 + new Random().Next())
            };

            var fakeChar = new Character
            {
                CharacterId = 12345678 + new Random().Next(),
                Player = fakePlayer,
                Name = playerName,
                Looks = client.Character.Looks,
                Jobs = client.Character.Jobs
            };


            var fakePacket = new CharacterSpawnPacket(fakeChar, new PSOLocation(0f, 1f, 0f, 0f, x, y, z))
            {
                IsItMe = false
            };
            client.SendPacket(fakePacket);

            Logger.WriteCommand(client, "[CMD] Spawned a clone of {0} named {1}", name, playerName);
        }

        private void SendPacket(string[] args, int length, string full, Client client)
        {
            var name = args[1].Trim('\"');
            var type = byte.Parse(args[2]);
            var subType = byte.Parse(args[3]);
            var flags = byte.Parse(args[4]);
            var data = new byte[args.Length - 5];
            var foundPlayer = false;

            // Find the player
            var id = Helper.FindPlayerByUsername(name);
            if (id != -1)
                foundPlayer = true;

            // Couldn't find the username
            if (!foundPlayer)
            {
                Logger.WriteCommand(client, "[CMD] Could not find user " + name);
                return;
            }

            if (args.Length >= 5)
            {
                var packetSize = 4;
                while (++packetSize < args.Length)
                    data[packetSize - 5] = byte.Parse(args[packetSize]);
            }

            // Send packet
            ServerApp.Instance.Server.Clients[id].SendPacket(type, subType, flags, data);

            Logger.WriteCommand(client, "[CMD] Sent packet {0:X}-{1:X} with flags {2} to {3}", type, subType, flags,
                name);
        }

        private void SendPacketDirectory(string[] args, int length, string full, Client client)
        {
            var name = args[1].Trim('\"');
            var dirname = args[2].Trim('\"');
            var foundPlayer = false;

            // Find the player
            var id = Helper.FindPlayerByUsername(name);
            if (id != -1)
                foundPlayer = true;

            // Couldn't find the username
            if (!foundPlayer)
            {
                Logger.WriteCommand(client, "[CMD] Could not find user " + name);
                return;
            }

            // Pull packets from the specified directory
            var packetList = Directory.GetFiles(dirname);
            Array.Sort(packetList);

            foreach (var path in packetList)
            {
                var index = -1;
                var data = File.ReadAllBytes(path);
                var packet = new byte[data.Length - 8];

                // Strip the header out
                while (++index < data.Length - 8)
                    packet[index] = data[index + 8];

                // Send packet
                ServerApp.Instance.Server.Clients[id].SendPacket(data[4], data[5], data[6], packet);

                Logger.WriteCommand(client, "[CMD] Sent contents of {0} as packet {1:X}-{2:X} with flags {3} to {4}",
                    path, data[4], data[5], data[6], name);
            }
        }

        private void SendPacketDirectorySlow(string[] args, int length, string full, Client client)
        {
            var name = args[1].Trim('\"');
            var dirname = args[2].Trim('\"');
            var delay = Int32.Parse(args[3]);
            var foundPlayer = false;

            // Find the player
            var id = Helper.FindPlayerByUsername(name);
            if (id != -1)
                foundPlayer = true;

            // Couldn't find the username
            if (!foundPlayer)
            {
                Logger.WriteCommand(client, "[CMD] Could not find user " + name);
                return;
            }

            // Pull packets from the specified directory
            var packetList = Directory.GetFiles(dirname);
            Array.Sort(packetList);

            foreach (var path in packetList)
            {
                var index = -1;
                var data = File.ReadAllBytes(path);
                var packet = new byte[data.Length - 8];

                // Strip the header out
                while (++index < data.Length - 8)
                    packet[index] = data[index + 8];

                // Send packet
                ServerApp.Instance.Server.Clients[id].SendPacket(data[4], data[5], data[6], packet);

                Logger.WriteCommand(client, "[CMD] Sent contents of {0} as packet {1:X}-{2:X} with flags {3} to {4}",
                    path, data[4], data[5], data[6], name);
                Thread.Sleep(delay);
            }
        }

        private void SendPacketFile(string[] args, int length, string full, Client client)
        {
            var name = args[1].Trim('\"');
            var filename = args[2].Trim('\"');
            var foundPlayer = false;

            // Find the player
            var id = Helper.FindPlayerByUsername(name);
            if (id != -1)
                foundPlayer = true;

            // Couldn't find the username
            if (!foundPlayer)
            {
                Logger.WriteError("[CMD] Could not find user " + name);
                return;
            }

            // Pull packet from the specified file
            var index = -1;
            var data = File.ReadAllBytes(filename);
            var packet = new byte[data.Length - 8];

            // Strip the header out
            while (++index < data.Length - 8)
                packet[index] = data[index + 8];

            // Send packet
            ServerApp.Instance.Server.Clients[id].SendPacket(data[4], data[5], data[6], packet);

            Logger.WriteCommand(client, "[CMD] Sent contents of {0} as packet {1:X}-{2:X} with flags {3} to {4}",
                filename, data[4], data[5], data[6], name);
        }

        private void TeleportPlayer(string[] args, int length, string full, Client client)
        {
            var foundPlayer = false;
            var id = 0;
            if (client != null)
            {
                id = client.User.PlayerId;
                foundPlayer = true;
            }
            else
            {
                var name = args[8].Trim('\"');

                Helper.FindPlayerByUsername(name);
                if (id != -1)
                    foundPlayer = true;
                client = ServerApp.Instance.Server.Clients[id];
            }

            // Couldn't find the username
            if (!foundPlayer)
            {
                Logger.WriteError("[CMD] Could not find user.");
                return;
            }

            PSOLocation destination = new PSOLocation(float.Parse(args[1]), float.Parse(args[2]), float.Parse(args[3]), float.Parse(args[4]),
                float.Parse(args[5]), float.Parse(args[6]), float.Parse(args[7]));


            client.SendPacket(new TeleportTransferPacket(ObjectManager.Instance.getObjectByID("lobby", 443), destination));

        }

        private void TeleportPlayer_POS(string[] args, int length, string full, Client client)
        {
            var foundPlayer = false;
            var id = 0;
            if (client != null)
            {
                id = client.User.PlayerId;
                foundPlayer = true;
            }
            else
            {
                var name = args[4].Trim('\"');

                Helper.FindPlayerByUsername(name);
                if (id != -1)
                    foundPlayer = true;

                client = ServerApp.Instance.Server.Clients[id];
            }


            // Couldn't find the username
            if (!foundPlayer)
            {
                Logger.WriteError("[CMD] Could not find user.");
                return;
            }

            PSOLocation destination = new PSOLocation(0f, 1f, 0f, 0f,
                float.Parse(args[1]), float.Parse(args[2]), float.Parse(args[3]));


            client.SendPacket(new TeleportTransferPacket(ObjectManager.Instance.getObjectByID("lobby", 443), destination));

        }

        private void ChangeArea(string[] args, int length, string full, Client client)
        {
            var name = args[1].Trim('\"');
            var foundPlayer = false;


            var id = Helper.FindPlayerByUsername(name);
            if (id != -1)
                foundPlayer = true;

            // Couldn't find the username
            if (!foundPlayer)
            {
                Logger.WriteError("[CMD] Could not find user " + name);
                return;
            }

            Client context = ServerApp.Instance.Server.Clients[id];

            Map dstMap;
            if (!ZoneManager.Instance.InstanceExists(String.Format("tpinstance_{0}_{1}", Int32.Parse(args[3]), Int32.Parse(args[8]))))
            {
                dstMap = new Map("tpmap", Int32.Parse(args[3]), Int32.Parse(args[8]), (Map.MapType)Int32.Parse(args[2]), (Map.MapFlags)Int32.Parse(args[4]))
                { GenerationArgs = new Map.GenParam() { seed = UInt32.Parse(args[5]), xsize = UInt32.Parse(args[6]), ysize = UInt32.Parse(args[7]) } };
                ZoneManager.Instance.NewInstance(String.Format("tpinstance_{0}", Int32.Parse(args[3])), dstMap);
            } else
            {
                dstMap = ZoneManager.Instance.MapFromInstance("tpmap", String.Format("tpinstance_{0}_{1}", Int32.Parse(args[3]), Int32.Parse(args[8])));
            }

            dstMap.SpawnClient(context, dstMap.GetDefaultLocation());



            //PSOLocation destination = new PSOLocation(float.Parse(args[2]), float.Parse(args[3]), float.Parse(args[4]), float.Parse(args[5]),float.Parse(args[6]), float.Parse(args[7]), float.Parse(args[8]));


            //ServerApp.Instance.Server.Clients[id].SendPacket(new TeleportTransferPacket(ObjectManager.Instance.getObjectByID("lobby", 443), destination));

            context.SendPacket(0x8, 0xB, 0x0, ObjectManager.Instance.getObjectByID(443).GenerateSpawnBlob());

            //var objects = ObjectManager.Instance.getObjectsForZone("casino").Values;
            //foreach (var obj in objects)
            //{
            //    context.SendPacket(0x8, 0xB, 0x0, obj.GenerateSpawnBlob());
            //}



            context.SendPacket(new NoPayloadPacket(0x03, 0x2B));

        }

        private void SpawnObject(string[] args, int length, string full, Client client)
        {
            if(client == null)
            {
                var id = Helper.FindPlayerByUsername(args[1]);
                if (id == -1)
                    return;

                client = ServerApp.Instance.Server.Clients[id];
            }
            else
            {
                string[] newargs = new string[args.Length + 1];
                newargs[0] = "";
                newargs[1] = "";
                Array.Copy(args, 1, newargs, 2, 9);
                args = newargs;
            }
            PSOObject obj = new PSOObject
            {
                Name = args[2],
                Header = new ObjectHeader((uint)Int32.Parse(args[3]), EntityType.Object),
                Position = new PSOLocation(float.Parse(args[4]), float.Parse(args[5]), float.Parse(args[6]), float.Parse(args[7]), float.Parse(args[8]), float.Parse(args[9]), float.Parse(args[10])),
                Things = new PSOObject.PSOObjectThing[0]
            };

            client.SendPacket(0x8, 0xB, 0x0, obj.GenerateSpawnBlob());
        }

        private void RunLUA(string[] args, int length, string full, Client client)
        {
            if (client == null)
            {
                var id = Helper.FindPlayerByUsername(args[1]);
                if (id == -1)
                    return;

                client = ServerApp.Instance.Server.Clients[id];
            }
            else
            {
                string[] newargs = new string[args.Length + 1];
                newargs[0] = "";
                newargs[1] = "";
                Array.Copy(args, 1, newargs, 2, args.Length - 1);
                args = newargs;
            }

            PacketWriter luaPacket = new PacketWriter();
            luaPacket.Write((UInt16)1);
            luaPacket.Write((UInt16)1);
            luaPacket.WriteAscii(String.Join(" ", args, 2, args.Length - 2), 0xD975, 0x2F);

            client.SendPacket(0x10, 0x3, 0x4, luaPacket.ToArray());
        }

        //private void ImportNPCs(string[] args, int length, string full, Client client)
        //{
        //    string zone = args[1];
        //    string folder = args[2];

        //    var packetList = Directory.GetFiles(folder);
        //    Array.Sort(packetList);

        //    List<NPC> newNPCs = new List<NPC>();
        //    foreach (var path in packetList)
        //    {
        //        var data = File.ReadAllBytes(path);
        //        PacketReader reader = new PacketReader(data);
        //        PacketHeader header = reader.ReadStruct<PacketHeader>();
        //        if (header.Type != 0x8 || header.Subtype != 0xC)
        //        {
        //            Logger.WriteWarning("[WRN] File {0} not an NPC spawn packet, skipping.", path);
        //            continue;
        //        }

        //        NPC newNPC = new NPC
        //        {
        //            EntityID = (int)reader.ReadStruct<ObjectHeader>().ID
        //        };
        //        var pos = reader.ReadEntityPosition();
        //        newNPC.RotX = pos.RotX;
        //        newNPC.RotY = pos.RotY;
        //        newNPC.RotZ = pos.RotZ;
        //        newNPC.RotW = pos.RotW;

        //        newNPC.PosX = pos.PosX;
        //        newNPC.PosY = pos.PosY;
        //        newNPC.PosZ = pos.PosZ;
        //        reader.ReadInt16();
        //        newNPC.NPCName = reader.ReadFixedLengthAscii(0x20);
        //        newNPC.ZoneName = zone;
        //        newNPCs.Add(newNPC);
        //        Logger.WriteInternal("[NPC] Adding new NPC {0} to the database for zone {1}", newNPC.NPCName, zone);
        //    }

        //    using (var db = new ServerEf())
        //    {
        //        db.NPCs.AddRange(newNPCs);
        //        db.SaveChanges();
        //    }

        //}
        private void ImportNPCs(string[] args, int length, string full, Client client)
        {
            string zone = args[1];
            string folder = args[2];

            var packetList = Directory.GetFiles(folder).OrderBy(f => f).ToList();
            var newNPCs = new List<NPC>();

            foreach (var path in packetList)
            {
                var data = File.ReadAllBytes(path);
                var reader = new PacketReader(data);
                var header = reader.ReadStruct<PacketHeader>();

                if (header.Type != 0x8 || header.Subtype != 0xC)
                {
                    Logger.WriteWarning($"[WRN] File {path} not an NPC spawn packet, skipping.");
                    continue;
                }

                var npc = new NPC
                {
                    EntityID = (int)reader.ReadStruct<ObjectHeader>().ID,
                    PosX = reader.ReadEntityPosition().PosX,
                    PosY = reader.ReadEntityPosition().PosY,
                    PosZ = reader.ReadEntityPosition().PosZ,
                    RotX = reader.ReadEntityPosition().RotX,
                    RotY = reader.ReadEntityPosition().RotY,
                    RotZ = reader.ReadEntityPosition().RotZ,
                    RotW = reader.ReadEntityPosition().RotW,
                    NPCName = reader.ReadFixedLengthAscii(0x20),
                    ZoneName = zone
                };

                reader.ReadInt16(); // Assuming this read is necessary

                newNPCs.Add(npc);
                Logger.WriteInternal($"[NPC] Adding new NPC {npc.NPCName} to the database for zone {zone}");
            }

            using (var db = new ServerEf())
            {
                db.NPCs.AddRange(newNPCs);
                db.SaveChanges();
            }
        }

        private void ImportObjects(string[] args, int length, string full, Client client)
        {
            string zone = args[1];
            string folder = args[2];

            var packetList = Directory.GetFiles(folder);
            Array.Sort(packetList);

            List<GameObject> newObjects = new List<GameObject>();
            foreach (var path in packetList)
            {
                var data = File.ReadAllBytes(path);
                PacketReader reader = new PacketReader(data);
                PacketHeader header = reader.ReadStruct<PacketHeader>();
                if (header.Type != 0x8 || header.Subtype != 0xB)
                {
                    Logger.WriteWarning("[WRN] File {0} not an Object spawn packet, skipping.", path);
                    continue;
                }

                GameObject newObj = new GameObject
                {
                    ObjectID = (int)reader.ReadStruct<ObjectHeader>().ID
                };
                var pos = reader.ReadEntityPosition();
                newObj.RotX = pos.RotX;
                newObj.RotY = pos.RotY;
                newObj.RotZ = pos.RotZ;
                newObj.RotW = pos.RotW;
                   
                newObj.PosX = pos.PosX;
                newObj.PosY = pos.PosY;
                newObj.PosZ = pos.PosZ;
                reader.ReadInt16();
                newObj.ObjectName = reader.ReadFixedLengthAscii(0x2C);
                var objHeader = reader.ReadStruct<ObjectHeader>(); // Seems to always be blank...
                if (objHeader.ID != 0)
                    Logger.WriteWarning("[OBJ] It seems object {0} has a nonzero objHeader! ({1}) Investigate.", newObj.ObjectName, objHeader.ID);
                newObj.ZoneName = zone;
                var thingCount = reader.ReadUInt32();
                newObj.ObjectFlags = new byte[thingCount * 4];
                for (int i = 0; i < thingCount; i++)
                {
                    Buffer.BlockCopy(BitConverter.GetBytes(reader.ReadUInt32()), 0, newObj.ObjectFlags, i * 4, 4); // This should work
                }
                newObjects.Add(newObj);
                Logger.WriteInternal("[OBJ] Adding new Object {0} to the database for zone {1}", newObj.ObjectName, zone);
            }

            using (var db = new ServerEf())
            {
                db.GameObjects.AddRange(newObjects);
                db.SaveChanges();
            }

        }

        private void TellPosition(string[] args, int length, string full, Client client)
        {
            if (client == null)
            {
                Logger.WriteError("[CMD] You need to be a client to run this command!");
            }
            else
            {
                Logger.WriteCommand(client, "[CMD] {0}", client.CurrentLocation.ToString());
            }
        }

        #endregion
    }
}