using System.Collections.Generic;
using Solar.FiestaLib;
using Solar.FiestaLib.Networking;
using Solar.Util;
using Solar.Zone.Game;
using Solar.Zone.Networking;

namespace Solar.Zone.Handlers
{
    public sealed class Handler9
    {
        [PacketHandler(CH9Type.SelectObject)]
        public static void SelectObjectHandler(ZoneClient client, Packet packet)
        {
            ushort id;
            if (!packet.TryReadUShort(out id))
            {
                Log.WriteLine(LogLevel.Warn, "Could not read entity select request.");
                return;
            }

            MapObject mo;
            // Try to see if there is a map object with this ID
            if (!client.Character.Map.Objects.TryGetValue(id, out mo))
            {
                return; // Nothing found. Just return lawl
            }

            mo.SelectedBy.Add(client.Character);

            if (mo is ZoneCharacter || mo is Mob)
            {
                client.Character.SelectedObject = mo;
                SendStatsUpdate(mo, client, false);
            }
        }

        [PacketHandler(CH9Type.DeselectObject)]
        public static void DeselectObjectHandler(ZoneClient client, Packet packet)
        {
            if (client.Character.SelectedObject != null)
            {
                client.Character.SelectedObject.SelectedBy.Remove(client.Character);
                client.Character.SelectedObject = null;
            }
        }

        [PacketHandler(CH9Type.AttackEntityMelee)]
        public static void AttackMeleeHandler(ZoneClient client, Packet packet)
        {
            client.Character.Attack(null);
        }

        [PacketHandler(CH9Type.AttackEntitySkill)]
        public static void AttackSkillHandler(ZoneClient client, Packet packet)
        {
            ushort skill;
            if (!packet.TryReadUShort(out skill))
            {
                Log.WriteLine(LogLevel.Warn, "Could not read skillID from attack entity skill function. {0}", client);
                return;
            }

            if (!client.Character.SkillsActive.ContainsKey(skill))
            {
                Log.WriteLine(LogLevel.Warn, "User tried to attack with a wrong skill. {0} {1} ", skill, client);
                return;
            }

            client.Character.AttackSkill(skill, null);
        }

        [PacketHandler(CH9Type.StopAttackingMelee)]
        public static void StopAttackMeleeHandler(ZoneClient client, Packet packet)
        {
            client.Character.AttackStop();
        }

        [PacketHandler(CH9Type.UseSkillWithTarget)]
        public static void UseSkillWithTargetHandler(ZoneClient client, Packet packet)
        {
            ushort skillid, victimid;
            if (!packet.TryReadUShort(out skillid) || !packet.TryReadUShort(out victimid))
            {
                Log.WriteLine(LogLevel.Warn, "Couldn't read useskill packet {0}", client);
                return;
            }
            Skill skill;
            if (!client.Character.SkillsActive.TryGetValue(skillid, out skill))
            {
                Log.WriteLine(LogLevel.Warn, "User tried to use a wrong skill. {0} {1} ", skillid, client);
                return;
            }


            MapObject victim;
            if (!client.Character.MapSector.Objects.TryGetValue(victimid, out victim))
            {
                Log.WriteLine(LogLevel.Warn, "User tried to do something with an unknown victim. {0} {1} {2}", skillid, victimid, client);
            }
            var self = client.Character;

            if (skill.Info.DemandType == 6)
            {
                if (!(victim is ZoneCharacter)) return;
                var zc = victim as ZoneCharacter;

                // Only Heal has this
                // Some heal calculation here
                uint amount = 12 * (uint)Program.Randomizer.Next(1, 300); //lulz

                if (amount > victim.MaxHP - victim.HP)
                {
                    amount = victim.MaxHP - victim.HP;
                }

                zc.HP += amount;

                ushort id = self.UpdateCounter;
                SendSkillStartSelf(self, skillid, victimid, id);
                SendSkillStartOthers(self, skillid, victimid, id);
                SendSkillOK(self);
                SendSkillAnimationForPlayer(self, skillid, id);
                // Damage as heal val :D
                SendSkill(self, id, victimid, amount, zc.HP, zc.UpdateCounter);
            }
            else
            {
                if (!(victim is Mob)) return;
                uint dmgmin = (uint)self.GetWeaponDamage(true);
                uint dmgmax = (uint)(self.GetWeaponDamage(true) + (self.GetWeaponDamage(true) % 3));
                uint amount = (uint)Program.Randomizer.Next((int)dmgmin, (int)dmgmax);
                if (amount > victim.HP)
                {
                    victim.HP = 0;
                }
                else {
                    victim.HP -= amount;
                }

                ushort id = self.UpdateCounter;
                SendSkillStartSelf(self, skillid, victimid, id);
                SendSkillStartOthers(self, skillid, victimid, id);
                SendSkillOK(self);
                SendSkillAnimationForPlayer(self, skillid, id);
                SendSkill(self, id, victimid, amount, victim.HP, victim.UpdateCounter, 0x01, 0x01);

                if (!victim.IsDead)
                {
                    victim.Attack(self);
                }
            }
        }

        [PacketHandler(CH9Type.UseSkillWithPosition)]
        public static void UseSkillWithPositionHandler(ZoneClient client, Packet packet)
        {
            ushort skillid;
            uint x, y;
            if (!packet.TryReadUShort(out skillid) || !packet.TryReadUInt(out x) || !packet.TryReadUInt(out y))
            {
                Log.WriteLine(LogLevel.Warn, "Couldn't read UseSkillWithPosition packet. {0} ", client);
                return;
            }

            Skill skill;
            if (!client.Character.SkillsActive.TryGetValue(skillid, out skill))
            {
                Log.WriteLine(LogLevel.Warn, "User tried to use a wrong skill. {0} {1} ", skillid, client);
                return;
            }

            var self = client.Character;
            var block = self.Map.Block;

            if (x == 0 || y == 0 || x > block.Width || y > block.Height)
            {
                Log.WriteLine(LogLevel.Warn, "User tried to use skill out of map boundaries. {0} {1} {2} {3}", x, y, skillid, client);
                return;
            }

            var pos = new Vector2((int)x, (int)y);

            if (skill.Info.MaxTargets <= 1)
            {
                // No AoE skill :s
                Log.WriteLine(LogLevel.Warn, "User tried to use skill with no MaxTargets or less than 1. {0} {1}", skillid, client);
                return;
            }

            self.AttackSkillAoE(skillid, x, y);
        }


        public static void SendAttackAnimation(MapObject from, ushort objectID, ushort attackspeed, byte stance)
        {
            using (var packet = new Packet(SH9Type.AttackAnimation))
            {
                packet.WriteUShort(from.MapObjectID);
                packet.WriteUShort(objectID);
                packet.WriteByte(stance);

                packet.WriteUShort(attackspeed);

                packet.WriteByte(4);
                packet.WriteByte(100);
                from.MapSector.Broadcast(packet);
            }
        }

        public static void SendAttackDamage(MapObject from, ushort objectID, ushort damage, bool crit, uint hpleft, ushort counter)
        {
            using (var packet = new Packet(SH9Type.AttackDamage))
            {
                packet.WriteUShort(from.MapObjectID);
                packet.WriteUShort(objectID);
                packet.WriteBool(crit);
                packet.WriteUShort(damage);
                packet.WriteUInt(hpleft);
                packet.WriteUShort(counter);
                packet.WriteByte(4);
                packet.WriteByte(100);

                from.MapSector.Broadcast(packet);
            }
        }

        public static void SendDieAnimation(MapObject from, ushort objectID)
        {
            // DO NOT SEND THIS TO THE DYING PLAYER, LOL
            using (var packet = new Packet(SH9Type.DieAnimation))
            {
                packet.WriteUShort(objectID);
                packet.WriteUShort(from.MapObjectID);
                from.MapSector.Broadcast(packet, objectID);
            }
        }

        public static void SendGainEXP(ZoneCharacter who, uint amount, ushort mobid = 0xFFFF)
        {
            using (var packet = new Packet(SH9Type.GainEXP))
            {
                packet.WriteUInt(amount);
                packet.WriteUShort(mobid);
                who.Client.SendPacket(packet);
            }
        }

        public static void SendLevelUPAnim(ZoneCharacter who, ushort mobid = 0xFFFF)
        {
            using (var packet = new Packet(SH9Type.LevelUPAnimation))
            {
                packet.WriteUShort(who.MapObjectID);
                packet.WriteUShort(mobid);
                who.MapSector.Broadcast(packet);
            }
        }

        public static void SendLevelUPData(ZoneCharacter who, ushort mobid = 0xFFFF)
        {
            using (var packet = new Packet(SH9Type.LevelUP))
            {
                packet.WriteByte(who.Level);
                packet.WriteUShort(mobid);
                who.WriteDetailedInfoExtra(packet, true);

                who.Client.SendPacket(packet);
            }
        }

        /*
        public static void SendResetStance(MapObject obj)
        {
            using (var packet = new Packet(SH9Type.ResetStance))
            {
                packet.WriteUShort(obj.MapObjectID);
                obj.MapSector.Broadcast(packet);
            }
        }*/
        public static void SendUpdateHP(ZoneCharacter character)
        {
            using (var p = new Packet(SH9Type.HealHP))
            {
                p.WriteUInt(character.HP);
                p.WriteUShort(character.UpdateCounter);
                character.Client.SendPacket(p);
            }
        }

        public static void SendUpdateSP(ZoneCharacter character)
        {
            using (var p = new Packet(SH9Type.HealSP))
            {
                p.WriteUInt(character.SP);
                p.WriteUShort(character.UpdateCounter);
                character.Client.SendPacket(p);
            }
        }

        public static void SendStatsUpdate(MapObject pObject, ZoneClient to, bool selectedby)
        {
            using (var packet = new Packet(SH9Type.StatUpdate))
            {
                packet.WriteBool(selectedby);
                packet.WriteUShort(pObject.MapObjectID);
                if (pObject is ZoneCharacter)
                {
                    ((ZoneCharacter)pObject).WriteUpdateStats(packet);
                }
                else
                {
                    ((Mob)pObject).WriteUpdateStats(packet);
                }
                to.SendPacket(packet);
            }
        }

        public static void SendSkillStartSelf(ZoneCharacter user, ushort skillid, ushort victim, ushort animid)
        {
            // 9 78 | [04 06] [8A 27] [E5 3F]
            using (var packet = new Packet(SH9Type.SkillUsePrepareSelf))
            {
                packet.WriteUShort(skillid);
                packet.WriteUShort(victim);
                packet.WriteUShort(animid);
                user.Client.SendPacket(packet);
            }
        }

        public static void SendSkillStartOthers(MapObject user, ushort skillid, ushort victim, ushort animid)
        {
            // 9 79 | [9A 26] [06 06] [8A 27] [97 2D] 
            using (var packet = new Packet(SH9Type.SkillUsePrepareOthers))
            {
                packet.WriteUShort(user.MapObjectID);
                packet.WriteUShort(skillid);
                packet.WriteUShort(victim);
                packet.WriteUShort(animid);
                user.MapSector.Broadcast(packet, user.MapObjectID);
            }
        }

        public static void SendSkillOK(ZoneCharacter user)
        {
            // 9 53 |
            using (var packet = new Packet(SH9Type.SkillAck))
            {
                user.Client.SendPacket(packet);
            }
        }

        public static void SendSkillAnimationForPlayer(MapObject user, ushort skillid, ushort animid)
        {
            // 9 87 | [E5 3F] [8A 27] [04 06]
            // 9 87 | [97 2D] [9A 26] [06 06]
            using (var packet = new Packet(SH9Type.SkillAnimation))
            {
                packet.WriteUShort(animid);
                packet.WriteUShort(user.MapObjectID);
                packet.WriteUShort(skillid);
                user.MapSector.Broadcast(packet);
            }
        }

        public static void SendSkill(MapObject user, ushort animid, ushort victimid, uint damage, uint newhp, ushort counter, byte special1 = 0x10, byte special2 = 0x00)
        {
            // 9 82 | [E5 3F] [8A 27] [01] [8A 27] [10 00] [09 00 00 00] [5E 00 00 00] [A7 4C]
            // 9 82 | [9A 35] [8A 27] [01] [C2 05] [10 00] [0A 00 00 00] [1D 01 00 00] [73 37]
            // 9 82 | [43 3C] [42 15] [01] [AC 4C] [01 01] [7A 02 00 00] [00 00 00 00] [35 09]
            // 9 82 | [0E 39] [42 15] [01] [00 4A] [21 01] [1C 03 00 00] [00 00 00 00] [8C 0E]
            using (var packet = new Packet(SH9Type.SkillAnimationTarget))
            {
                packet.WriteUShort(animid);
                packet.WriteUShort(user.MapObjectID);
                packet.WriteBool(true);
                packet.WriteUShort(victimid);
                packet.WriteByte(special1);
                packet.WriteByte(special2);
                packet.WriteUInt(damage);
                packet.WriteUInt(newhp);
                packet.WriteUShort(counter);
                user.MapSector.Broadcast(packet);
            }
        }

        public static void SendSkillNoVictim(MapObject user, ushort animid)
        {
            // 9 82 | [75 70] [32 29] [00]
            using (var packet = new Packet(SH9Type.SkillAnimationTarget))
            {
                packet.WriteUShort(animid);
                packet.WriteUShort(user.MapObjectID);
                packet.WriteBool(false);
                user.MapSector.Broadcast(packet);
            }
        }

        public static void SendSkill(MapObject user, ushort animid, List<SkillVictim> victims)
        {
            using (var packet = new Packet(SH9Type.SkillAnimationTarget))
            {
                packet.WriteUShort(animid);
                packet.WriteUShort(user.MapObjectID);
                packet.WriteByte((byte)(victims.Count > 255 ? 255 : victims.Count));
                for (byte i = 0; i < victims.Count && i != 255; i++)
                {
                    var victim = victims[i];
                    packet.WriteUShort(victim.MapObjectID);
                    packet.WriteByte(victim.Stance1);
                    packet.WriteByte(victim.Stance2);
                    packet.WriteUInt(victim.Damage);
                    packet.WriteUInt(victim.HPLeft);
                    packet.WriteUShort(victim.HPCounter);
                }
                user.MapSector.Broadcast(packet);
            }
        }

        public static void SendSkillPosition(MapObject user, ushort animid, ushort skillid, uint x, uint y)
        {
            // 9 81 | [32 29] [B8 10] [56 0A 00 00] [3A 27 00 00] [75 70]
            using (var packet = new Packet(SH9Type.SkillAnimationPosition))
            {
                packet.WriteUShort(user.MapObjectID);
                packet.WriteUShort(skillid);
                packet.WriteUInt(x);
                packet.WriteUInt(y);
                packet.WriteUShort(animid);
                user.MapSector.Broadcast(packet);
            }
        }
    }
}
