using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using PSO2SERVER.Models;
using PSO2SERVER.Object;
using PSO2SERVER.Protocol;
using PSO2SERVER.Protocol.Packets;
using static PSO2SERVER.Zone.Map;

namespace PSO2SERVER.Zone
{
    public struct PSOLocation
    {
        public float RotX, RotY, RotZ, RotW, PosX, PosY, PosZ; // RotX, RotY, RotZ, and RotW make up a Quaternion

        public PSOLocation(float RotX, float RotY, float RotZ, float RotW, float PosX, float PosY, float PosZ)
        {
            this.RotX = RotX;
            this.RotY = RotY;
            this.RotZ = RotZ;
            this.RotW = RotW;

            this.PosX = PosX;
            this.PosY = PosY;
            this.PosZ = PosZ;
        }

        public override string ToString()
        {
            return String.Format("Rot: ({0}, {1}, {2}, {3}) Loc: ({4}, {5}, {6})"
                , RotX, RotY, RotZ, RotW, PosX, PosY, PosZ);
        }
    }

    public class Map
    {
        public string Name { get; set; }
        public MapType Type { get; set; }
        public PSOObject[] Objects { get; set; }
        public PSONPC[] NPCs { get; set; }
        public ObjectHeader MapHeader { get; set; }

        public int MapID { get; set; }
        public int VariantID { get; set; }

        public MapFlags Flags { get; set; }

        public List<Client> Clients { get; set; }

        public GenParam GenerationArgs { get; set; }

        public string InstanceName { get; set; }

        public enum MapType : int
        {
            Lobby = 4,
            Casino = 11,
            MyRoom = 8,
            TeamRoom = 7,
            ChallengeLobby = 5,
            Campship = 1,
            Quest = 0,
            Other = ~0
        };

        [Flags]
        public enum MapFlags : uint
        {
            None = 0,
            MultiPartyArea = 1,
            Unknown1 = 2,
            EnableMap = 4
        }

        public Map(string name, int id, int variant, MapType type, MapFlags flags)
        {
            Name = name;
            MapID = id;
            VariantID = variant;
            Type = type;
            Flags = flags;
            Clients = new List<Client>();
            GenerationArgs = new GenParam();

            Objects = ObjectManager.Instance.GetObjectsForZone(Name);
            NPCs = ObjectManager.Instance.GetNpcSForZone(Name);
        }


        public PSOLocation GetDefaultLocation()
        {
            PSOLocation location;

            switch (Type)
            {
                case MapType.Lobby:
                    location = new PSOLocation(0f, 1f, 0f, 0f, -0.417969f, 0f, 137.375f);
                    break;

                case MapType.Casino:
                    location = new PSOLocation(0f, 1f, 0f, 0f, 2f, 6f, 102f);
                    break;

                default:
                    location = new PSOLocation(0f, 1f, 0f, 0f, 0f, 0f, 0f);
                    break;
            }

            return location;
        }

        /// <summary>
        /// Spawns a client into a map at a given location
        /// </summary>
        /// <param name="c">Client to spawn into map</param>
        /// <param name="location">Location to spawn client at</param>
        /// <param name="questOveride">If this also sets the quest data, specify the quest name here to load the spawn from the bin rather then generate it.</param>
        public void SpawnClient(Client c, PSOLocation location, string questOveride = "")
        {
            if (Clients.Contains(c))
            {
                return;
            }

            // Set area
            if (questOveride != "") // TODO: This is a temporary hack, fix me!!
            {
                //var setAreaPacket = File.ReadAllBytes("Resources\\quests\\" + questOveride + ".bin");
                //c.SendPacket(0x03, 0x24, 0x04, setAreaPacket);

                c.SendPacket(new LoadingLevelPacket(questOveride));
            }
            else
            {
                var _map = new Map("", MapID, VariantID, Type, Flags);
                _map.GenerationArgs.seed = GenerationArgs.seed;
                _map.GenerationArgs.xsize = GenerationArgs.xsize;
                _map.GenerationArgs.ysize = GenerationArgs.ysize;

                c.SendPacket(new MapTransferPacket(_map, c._account.AccountId));
            }

            if (c.CurrentZone != null)
            {
                c.CurrentZone.RemoveClient(c);
            }

            // 设置客户端的账户ID
            c.SendPacket(new SetAccountIDPacket(c._account.AccountId));

            // Spawn Character
            c.SendPacket(new CharacterSpawnPacket(c.Character, location, true, true));
            c.CurrentLocation = location;
            c.CurrentZone = this;

            // Objects
            foreach (PSOObject obj in Objects)
            {
                c.SendPacket(new ObjectSpawnPacket(obj));
            }

            // NPCs
            foreach (PSONPC npc in NPCs)
            {
                c.SendPacket(new NPCSpawnPacket(npc));
            }

            // Spawn for others, Spawn others for me
            CharacterSpawnPacket spawnMe = new CharacterSpawnPacket(c.Character, location, false, true);
            foreach (Client other in Clients)
            {
                other.SendPacket(spawnMe);
                c.SendPacket(new CharacterSpawnPacket(other.Character, other.CurrentLocation, false, true));
            }

            Clients.Add(c);

            Logger.Write("[MAP] {0} 已生成至 {1}.", c._account.Username, Name);

            if (InstanceName != null && ZoneManager.Instance.playerCounter.ContainsKey(InstanceName))
            {
                ZoneManager.Instance.playerCounter[InstanceName] += 1;
            }

        }

        public void RemoveClient(Client c)
        {
            if (!Clients.Contains(c))
                return;

            c.CurrentZone = null;
            Clients.Remove(c);

            foreach (Client other in Clients)
            {
                other.SendPacket(new DespawnPlayerPacket(other._account.AccountId, c._account.AccountId));
            }

            if (InstanceName != null && ZoneManager.Instance.playerCounter.ContainsKey(InstanceName))
            {
                ZoneManager.Instance.playerCounter[InstanceName] -= 1;
                if (ZoneManager.Instance.playerCounter[InstanceName] <= 0)
                {
                    ZoneManager.Instance.playerCounter.Remove(InstanceName);
                    ZoneManager.Instance.instances.Remove(InstanceName);
                }
            }
        }

        public class GenParam
        {
            public GenParam()
            {
            }

            public GenParam(uint seed, uint x, uint y)
            {
                this.seed = seed;
                this.xsize = x;
                this.ysize = y;
            }
            public uint seed, xsize, ysize;
        }
    }

}