using System;
using FFXIVClassic.Common;
using System.IO;

namespace FFXIVClassic_Lobby_Server.dataobjects
{
    class CharaInfo
    {
        public Appearance appearance;

        public struct FaceInfo
        {
            [BitfieldLength(5)]
            public uint characteristics;
            [BitfieldLength(3)]
            public uint characteristicsColor;
            [BitfieldLength(6)]
            public uint type;
            [BitfieldLength(2)]
            public uint ears;
            [BitfieldLength(2)]
            public uint mouth;
            [BitfieldLength(2)]
            public uint features;
            [BitfieldLength(3)]
            public uint nose;
            [BitfieldLength(3)]
            public uint eyeShape;
            [BitfieldLength(1)]
            public uint irisSize;
            [BitfieldLength(3)]
            public uint eyebrows;
            [BitfieldLength(2)]
            public uint unknown;
        }

        public uint guardian = 0;
        public uint birthMonth = 0;
        public uint birthDay = 0;
        public uint currentClass = 0;
        public uint currentJob = 0;
        public uint initialTown = 0;
        public uint tribe = 0;

        public ushort zoneId;
        public float x, y, z, rot;

        public uint currentLevel = 1;

        public uint weapon1;
        public uint weapon2;
        public uint head;
        public uint body;
        public uint hands;
        public uint legs;
        public uint feet;
        public uint belt;

        public static CharaInfo GetFromNewCharRequest(String encoded)
        {
            byte[] data = Convert.FromBase64String(encoded.Replace('-', '+').Replace('_', '/'));

            CharaInfo info = new CharaInfo();
            Appearance appearance = new Appearance();

            using (MemoryStream stream = new MemoryStream(data))
            {
                using (BinaryReader reader = new BinaryReader(stream))
                {
                    uint version = reader.ReadUInt32();
                    uint unknown1 = reader.ReadUInt32();
                    info.tribe = reader.ReadByte();
                    appearance.size = reader.ReadByte();
                    appearance.hairStyle = reader.ReadUInt16();
                    appearance.hairHighlightColor = reader.ReadByte();
                    appearance.hairVariation = reader.ReadByte();
                    appearance.faceType = reader.ReadByte();
                    appearance.characteristics = reader.ReadByte();
                    appearance.characteristicsColor = reader.ReadByte();

                    reader.ReadUInt32();

                    appearance.faceEyebrows = reader.ReadByte();
                    appearance.faceIrisSize = reader.ReadByte();
                    appearance.faceEyeShape = reader.ReadByte();
                    appearance.faceNose = reader.ReadByte();
                    appearance.faceFeatures = reader.ReadByte();
                    appearance.faceMouth = reader.ReadByte();
                    appearance.ears = reader.ReadByte();
                    appearance.hairColor = reader.ReadUInt16();

                    reader.ReadUInt32();

                    appearance.skinColor = reader.ReadUInt16();
                    appearance.eyeColor = reader.ReadUInt16();

                    appearance.voice = reader.ReadByte();
                    info.guardian = reader.ReadByte();
                    info.birthMonth = reader.ReadByte();
                    info.birthDay = reader.ReadByte();
                    info.currentClass = reader.ReadUInt16();

                    reader.ReadUInt32();
                    reader.ReadUInt32();
                    reader.ReadUInt32();

                    reader.BaseStream.Seek(0x10, SeekOrigin.Current);

                    info.initialTown = reader.ReadByte();

                }
            }

            info.appearance = appearance;

            return info;
        }

        public static String BuildForCharaList(Character chara, Appearance appearance)
        {
            byte[] data;
            
            using (MemoryStream stream = new MemoryStream())
            {
                using (BinaryWriter writer = new BinaryWriter(stream))
                {
                    //Build faceinfo for later
                    FaceInfo faceInfo = new FaceInfo();
                    faceInfo.characteristics = appearance.characteristics;
                    faceInfo.characteristicsColor = appearance.characteristicsColor;
                    faceInfo.type = appearance.faceType;
                    faceInfo.ears = appearance.ears;
                    faceInfo.features = appearance.faceFeatures;
                    faceInfo.eyebrows = appearance.faceEyebrows;
                    faceInfo.eyeShape = appearance.faceEyeShape;
                    faceInfo.irisSize = appearance.faceIrisSize;
                    faceInfo.mouth = appearance.faceMouth;
                    faceInfo.nose = appearance.faceNose;

                    string location1 = "prv0Inn01\0";
                    string location2 = "defaultTerritory\0";

                    writer.Write((UInt32)0x000004c0);
                    writer.Write((UInt32)0x232327ea);
                    writer.Write((UInt32)System.Text.Encoding.UTF8.GetBytes(chara.name + '\0').Length);
                    writer.Write(System.Text.Encoding.UTF8.GetBytes(chara.name + '\0'));
                    writer.Write((UInt32)0x1c);
                    writer.Write((UInt32)0x04);
                    writer.Write((UInt32)GetTribeModel(chara.tribe));
                    writer.Write((UInt32)appearance.size);
                    uint colorVal = appearance.skinColor | (uint)(appearance.hairColor << 10) | (uint)(appearance.eyeColor << 20);
                    writer.Write((UInt32)colorVal);

                    var bitfield = PrimitiveConversion.ToUInt32(faceInfo);

                    writer.Write((UInt32)bitfield); //FACE, Figure this out!
                    uint hairVal = appearance.hairHighlightColor | (uint)(appearance.hairStyle << 10) | (uint)(appearance.characteristicsColor << 20);
                    writer.Write((UInt32)hairVal);
                    writer.Write((UInt32)appearance.voice);
                    writer.Write((UInt32)appearance.mainHand);
                    writer.Write((UInt32)appearance.offHand);

                    writer.Write((UInt32)0);
                    writer.Write((UInt32)0);
                    writer.Write((UInt32)0);
                    writer.Write((UInt32)0);
                    writer.Write((UInt32)0);

                    writer.Write((UInt32)appearance.head);
                    writer.Write((UInt32)appearance.body);
                    writer.Write((UInt32)appearance.legs);
                    writer.Write((UInt32)appearance.hands);
                    writer.Write((UInt32)appearance.feet);
                    writer.Write((UInt32)appearance.waist);

                    writer.Write((UInt32)0);

                    writer.Write((UInt32)appearance.rightEar);
                    writer.Write((UInt32)appearance.leftEar);

                    writer.Write((UInt32)0);
                    writer.Write((UInt32)0);

                    writer.Write((UInt32)appearance.rightFinger);
                    writer.Write((UInt32)appearance.leftFinger);

                    for (int i = 0; i < 0x8; i++)
                        writer.Write((byte)0);

                    writer.Write((UInt32)1);
                    writer.Write((UInt32)1);

                    writer.Write((byte)chara.currentClass);
                    writer.Write((UInt16)chara.currentLevel);
                    writer.Write((byte)chara.currentJob);
                    writer.Write((UInt16)1);
                    writer.Write((byte)chara.tribe);

                    writer.Write((UInt32)0xe22222aa);

                    writer.Write((UInt32)System.Text.Encoding.UTF8.GetBytes(location1).Length);
                    writer.Write(System.Text.Encoding.UTF8.GetBytes(location1));
                    writer.Write((UInt32)System.Text.Encoding.UTF8.GetBytes(location2).Length);
                    writer.Write(System.Text.Encoding.UTF8.GetBytes(location2));

                    writer.Write((byte)chara.guardian);
                    writer.Write((byte)chara.birthMonth);
                    writer.Write((byte)chara.birthDay);

                    writer.Write((UInt16)0x17);
                    writer.Write((UInt32)4);
                    writer.Write((UInt32)4);

                    writer.BaseStream.Seek(0x10, SeekOrigin.Current);

                    writer.Write((UInt32)chara.initialTown);
                    writer.Write((UInt32)chara.initialTown);
                }

                data = stream.GetBuffer();
            }

            return Convert.ToBase64String(data).Replace('+', '-').Replace('/', '_');
        }

        public static String Debug()
        {
            byte[] bytes = File.ReadAllBytes("./packets/charaappearance.bin");

            Program.Log.Debug(Utils.ByteArrayToHex(bytes));

            return Convert.ToBase64String(bytes).Replace('+', '-').Replace('/', '_');
        }

        public static UInt32 GetTribeModel(byte tribe)
        {
            switch (tribe)
            {
                //Hyur Midlander Male
                case 1:
                default:
                    return 1;

                //Hyur Midlander Female
                case 2:
                    return 2;

                //Elezen Male
                case 4:
                case 6:
                    return 3;

                //Elezen Female
                case 5:
                case 7:
                    return 4;

                //Lalafell Male
                case 8:
                case 10:
                    return 5;

                //Lalafell Female
                case 9:
                case 11:
                    return 6;

                //Miqo'te Female
                case 12:
                case 13:
                    return 8;

                //Roegadyn Male
                case 14:
                case 15:
                    return 7;

                //Hyur Highlander Male
                case 3:
                    return 9;
            }
        }
    }
}
