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

using Atom.Core;
using Atom.Core.Diagnostics;
using Atom.Core.Game.GameObjects.Characters;
using Atom.Core.Game.GameObjects.Items;
using Atom.Core.Game.Quests;
using Atom.Core.Game.Tutorial;
using Atom.Core.Mathematics;
using Atom.Core.Networking;
using Atom.Core.Networking.Messages.Structures.Avatar;
using Atom.Core.SQL;
using System;
using System.IO;
using System.Linq;
using Atom.Core.Game;

namespace Atom.WorldManagerServer.Services
{
    internal static class CharacterService
    {
        internal static bool LoadCharacter(int nCharNo, out SimpleCharacter Character)
        {
            Character = new SimpleCharacter
            {
                CharNo = nCharNo
            };

            return GetLoginData(Character) && GetEquippedItems(Character);
        }

        internal static bool GetLoginData(SimpleCharacter Character)
        {
            using (var p_Char_GetLoginData = new StoredProcedure("p_Char_GetLoginData", Program.ServerInstance.ODBCConnection))
            {
                p_Char_GetLoginData.AddParameter("nCharNo", Character.CharNo);

                using (var Reader = p_Char_GetLoginData.RunReader())
                {
                    if (!Reader.HasRows)
                    {
                        return false;
                    }

                    while (Reader.Read())
                    {
                        Character.Name = Reader.GetString(0);
                        Character.Level = Convert.ToByte(Reader.GetValue(1));
                        Character.Slot = (byte)Reader.GetValue(2);
                        Character.MapIndx = Reader.GetString(3);
                        Character.IsDeleted = Reader.GetBoolean(7);
                        Character.DeletedDate = Reader.GetDateTime(8);
                        Character.Class = (CharacterClass)(byte)Reader.GetValue(10);
                        Character.Gender = (Gender)(byte)Reader.GetValue(11);
                        Character.Hair = (byte)Reader.GetValue(12);
                        Character.HairColor = (byte)Reader.GetValue(13);
                        Character.Face = (byte)Reader.GetValue(14);
                        Character.KQHandle = Reader.GetInt32(15);
                        Character.KQMapIndx = Reader.GetString(16);
                        Character.KQPosition = new Vector2(Reader.GetInt32(17), Reader.GetInt32(18));
                        Character.KQDate = Reader.GetDateTime(20);
                        Character.FriendPoints = Convert.ToUInt16(Reader.GetValue(21));
                        Character.TutorialState = (TutorialState)Reader.GetInt32(22);
                        Character.TutorialStep = Reader.GetByte(23);
                    }

                    return true;
                }
            }
        }

        internal static bool GetEquippedItems(SimpleCharacter Character)
        {
            using (var p_Item_GetListType = new StoredProcedure("p_Item_GetListType", Program.ServerInstance.ODBCConnection))
            {
                p_Item_GetListType.AddParameter("nCharNo", Character.CharNo);
                p_Item_GetListType.AddParameter("nStorageType", (byte) InventoryType.EQUIPPED);

                using (var Reader = p_Item_GetListType.RunReader())
                {
                    if (!Reader.HasRows)
                    {
                        return true;
                    }

                    while (Reader.Read())
                    {
                        var ItemSlot = (ItemSlot) (byte) Reader.GetValue(1);

                        if (Character.EquippedItems.ContainsKey(ItemSlot))
                        {
                            Log.Warning($"Character had multiple items in the same equip slot [{Character.Name}]");
                            Character.EquippedItems.Remove(ItemSlot);
                        }

                        Character.EquippedItems.Add(ItemSlot, Convert.ToUInt16(Reader.GetValue(2)));
                    }
                }
            }

            return true;
        }

        internal static bool IsNameInUse(string Name)
        {
            using (var p_Char_Find = new StoredProcedure("p_Char_Find", Program.ServerInstance.ODBCConnection))
            {
                p_Char_Find.AddParameter("sID", Name, 40);
                p_Char_Find.AddOutput<int>("nCharNo");
                var CharNo = p_Char_Find.Run().GetOutput<int>("nCharNo");

                return CharNo != 0;
            }
        }

        internal static void CreateCharacter(Client Client, byte Slot, string Name, CharacterClass Class, Gender Gender, byte Hair, byte HairColor, byte Face)
        {
            int CharNo;

            using (var p_Char_Create = new StoredProcedure("p_Char_Create", Program.ServerInstance.ODBCConnection))
            {
                p_Char_Create.AddParameter("nUserNo", Client.Account.UserNo);
                p_Char_Create.AddParameter("nCreateWorld", Program.ServerInstance.Config.GetInt32("SERVER", "Number"));
                p_Char_Create.AddParameter("nAdminLevel", 0);
                p_Char_Create.AddParameter("nSlotNo", Slot);
                p_Char_Create.AddParameter("sID", Name, 40);
                p_Char_Create.AddParameter("nRace", 1);
                p_Char_Create.AddParameter("nClass", (byte) Class);
                p_Char_Create.AddParameter("nGender", (byte) Gender);
                p_Char_Create.AddParameter("nHairType", Hair);
                p_Char_Create.AddParameter("nHairColor", HairColor);
                p_Char_Create.AddParameter("nFaceShape", Face);
                p_Char_Create.AddOutput<int>("nCharNo");

                CharNo = p_Char_Create.Run().GetOutput<int>("nCharNo");

                if (CharNo <= -1)
                {
                    new PROTO_NC_AVATAR_CREATEFAIL_ACK(CreateCharacterFailedErrorCode.FAILED_CREATE).Send(Client);
                    return;
                }
            }

            if (Program.DefaultCharacterData.TryGetValue(Class, out SimpleCharacter DefaultValues))
            {
                using (var p_Char_CreateSetDefaultData = new StoredProcedure("p_Char_CreateSetDefaultData", Program.ServerInstance.ODBCConnection))
                {
                    p_Char_CreateSetDefaultData.AddParameter("nCharNo", CharNo);
                    p_Char_CreateSetDefaultData.AddParameter("sLoginZone", DefaultValues.MapIndx, 16);
                    p_Char_CreateSetDefaultData.AddParameter("nLoginZoneX", DefaultValues.X);
                    p_Char_CreateSetDefaultData.AddParameter("nLoginZoneY", DefaultValues.Y);
                    p_Char_CreateSetDefaultData.AddParameter("nHP", DefaultValues.HP);
                    p_Char_CreateSetDefaultData.AddParameter("nSP", DefaultValues.SP);
                    p_Char_CreateSetDefaultData.AddParameter("nHPS", DefaultValues.HPStone);
                    p_Char_CreateSetDefaultData.AddParameter("nSPS", DefaultValues.SPStone);
                    p_Char_CreateSetDefaultData.AddParameter("nLevel", DefaultValues.Level);
                    p_Char_CreateSetDefaultData.AddParameter("nMoney", DefaultValues.Money);
                    p_Char_CreateSetDefaultData.AddParameter("nExp", DefaultValues.EXP);
                    p_Char_CreateSetDefaultData.AddOutput<int>("nRet");

                    var Return = p_Char_CreateSetDefaultData.Run().GetOutput<int>("nRet");

                    if (Return == 0)
                    {
                        new PROTO_NC_AVATAR_CREATEFAIL_ACK(CreateCharacterFailedErrorCode.FAILED_CREATE).Send(Client);
                        return;
                    }
                }

                using (var p_Char_RedistributePointSet = new StoredProcedure("p_Char_RedistributePointSet", Program.ServerInstance.ODBCConnection))
                {
                    p_Char_RedistributePointSet.AddParameter("nCharNo", CharNo);
                    p_Char_RedistributePointSet.AddParameter("nSetPoint", DefaultValues.Level);
                    p_Char_RedistributePointSet.AddOutput<byte>("nRet");
                    p_Char_RedistributePointSet.Run();
                }

                foreach (var Item in DefaultValues.ItemLots.Keys)
                {
                    long ItemKey;

                    using (var p_Item_Create = new StoredProcedure("p_Item_Create", Program.ServerInstance.ODBCConnection))
                    {
                        p_Item_Create.AddParameter("nOwner", CharNo);
                        p_Item_Create.AddParameter("nStorageType", (byte) InventoryType.CHAR_INVENTORY);
                        p_Item_Create.AddParameter("nStorage", (byte) DefaultValues.ItemLots.Keys.ToList().IndexOf(Item));
                        p_Item_Create.AddParameter("nItemID", (int) Item);
                        p_Item_Create.AddParameter("nFlags", 0);
                        p_Item_Create.AddOutput<long>("nItemKey");
                        p_Item_Create.AddOutput<int>("nRet");
                        ItemKey = p_Item_Create.Run().GetOutput<long>("nItemKey");
                    }

                    using (var p_Item_SetOption = new StoredProcedure("p_Item_SetOption", Program.ServerInstance.ODBCConnection))
                    {
                        p_Item_SetOption.AddParameter("nItemKey", ItemKey);
                        p_Item_SetOption.AddParameter("nOptionType", (int) ItemOptionType.IOT_NUMBER);
                        p_Item_SetOption.AddParameter("nOptionData", DefaultValues.ItemLots[Item]);
                        p_Item_SetOption.AddOutput<int>("nRet");
                        p_Item_SetOption.Run();
                    }
                }

                long HouseKey;

                using (var p_Item_Create = new StoredProcedure("p_Item_Create", Program.ServerInstance.ODBCConnection))
                {
                    p_Item_Create.AddParameter("nOwner", CharNo);
                    p_Item_Create.AddParameter("nStorageType", (byte) InventoryType.MINIHOUSE_SKIN);
                    p_Item_Create.AddParameter("nStorage", 0);
                    p_Item_Create.AddParameter("nItemID", Convert.ToInt32(31000));
                    p_Item_Create.AddParameter("nFlags", 0);
                    p_Item_Create.AddOutput<long>("nItemKey");
                    p_Item_Create.AddOutput<int>("nRet");
                    HouseKey = p_Item_Create.Run().GetOutput<long>("nItemKey");
                }

                using (var p_Item_SetOption = new StoredProcedure("p_Item_SetOption", Program.ServerInstance.ODBCConnection))
                {
                    p_Item_SetOption.AddParameter("nItemKey", HouseKey);
                    p_Item_SetOption.AddParameter("nOptionType", (int) ItemOptionType.IOT_NUMBER);
                    p_Item_SetOption.AddParameter("nOptionData", 1);
                    p_Item_SetOption.AddOutput<int>("nRet");
                    p_Item_SetOption.Run();
                }

                foreach (var Quest in DefaultValues.Quests)
                {
                    using (var p_Quest_Add = new StoredProcedure("p_Quest_Add", Program.ServerInstance.ODBCConnection))
                    {
                        p_Quest_Add.AddParameter("nCharNo", CharNo);
                        p_Quest_Add.AddParameter("nQuestNo", Quest);
                        p_Quest_Add.AddParameter("nStatus", (byte) QuestStatus.QS_NONE);
                        p_Quest_Add.AddParameter("sData", new byte[100], 100);
                        p_Quest_Add.AddOutput<int>("nRet");
                        p_Quest_Add.Run();
                    }
                }

                foreach (var Skill in DefaultValues.Skills)
                {
                    using (var p_Skill_Set = new StoredProcedure("p_Skill_Set", Program.ServerInstance.ODBCConnection))
                    {
                        p_Skill_Set.AddParameter("nCharNo", CharNo);
                        p_Skill_Set.AddParameter("nSkillNo", Convert.ToInt32(Skill));
                        p_Skill_Set.AddParameter("nSkillLevel", 0);
                        p_Skill_Set.AddParameter("nSkillExp", 0);
                        p_Skill_Set.AddParameter("nSkillWriteTime", 0);
                        p_Skill_Set.AddParameter("nSkillCoolTime", 0);
                        p_Skill_Set.AddParameter("nSkillPowerDemage", 0);
                        p_Skill_Set.AddParameter("nSkillPowerSP", 0);
                        p_Skill_Set.AddParameter("nSkillPowerKeepTime", 0);
                        p_Skill_Set.AddParameter("nSkillPowerCoolTime", 0);
                        p_Skill_Set.AddOutput<int>("nRet");
                        p_Skill_Set.Run();
                    }
                }

                if (!LoadCharacter(CharNo, out SimpleCharacter Character))
                {
                    new PROTO_NC_AVATAR_CREATEFAIL_ACK(CreateCharacterFailedErrorCode.FAILED_CREATE).Send(Client);
                    return;
                }

                Character.Shortcuts = DefaultValues.Shortcuts;
                Client.Account.Characters.Add(Character);

                new PROTO_NC_AVATAR_CREATESUCC_ACK(Client, Character).Send(Client);
            }
            else
            {
                Log.Warning($"No default character data found for {Class}");
            }
        }

        internal static void LoadShortcuts(SimpleCharacter Character)
        {
            using (var p_Char_GetOptShortCutData = new StoredProcedure("p_Char_GetOptShortCutData", Program.ServerInstance.ODBCConnection))
            {
                p_Char_GetOptShortCutData.AddParameter("nCharNo", Character.CharNo);
                p_Char_GetOptShortCutData.AddOutput<int>("nRet");
                p_Char_GetOptShortCutData.AddOutput<byte[]>("sData", 1024);
                p_Char_GetOptShortCutData.Run();

                var Data = p_Char_GetOptShortCutData.GetOutput<byte[]>("sData");

                using (var Stream = new MemoryStream(Data))
                using (var Reader = new BinaryReader(Stream))
                {
                    var ShortcutCount = Reader.ReadUInt16();

                    for (var i = 0; i < ShortcutCount; i++)
                    {
                        var Shortcut = new Shortcut
                        {
                            Slot = Reader.ReadByte(),
                            Code = Reader.ReadUInt16(),
                            Value = Reader.ReadUInt16()
                        };

                        Reader.ReadUInt16();

                        if (Character.Shortcuts.ContainsKey(Shortcut.Slot))
                        {
                            continue;
                        }

                        Character.Shortcuts.Add(Shortcut.Slot, Shortcut);
                    }
                }

                UpdateShortcutDataBinary(Character);
            }
        }

        internal static void SaveShortcuts(SimpleCharacter Character)
        {
            UpdateShortcutDataBinary(Character);

            using (var p_Char_SetOptShortCutData = new StoredProcedure("p_Char_SetOptShortCutData", Program.ServerInstance.ODBCConnection))
            {
                p_Char_SetOptShortCutData.AddParameter("nCharNo", Character.CharNo);
                p_Char_SetOptShortCutData.AddParameter("sData", Character.ShortcutData, 1024);
                p_Char_SetOptShortCutData.AddOutput<int>("nRet");
                p_Char_SetOptShortCutData.Run();
            }
        }

        internal static void UpdateShortcutDataBinary(SimpleCharacter Character)
        {
            using (var Stream = new MemoryStream())
            using (var Writer = new BinaryWriter(Stream))
            {
                Writer.Write((ushort)Character.Shortcuts.Count);

                foreach (var Shortcut in Character.Shortcuts.Values)
                {
                    Writer.Write(Shortcut.Slot);
                    Writer.Write(Shortcut.Code);
                    Writer.Write(Shortcut.Value);
                    Writer.Write((ushort)0);
                }

                Character.ShortcutData = Stream.ToArray();
            }
        }
    }
}
