using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Text;
using Toolbox.Library.IO;
using Toolbox.Library;
using System.IO;
using Syroot.BinaryData;
using System.Windows.Forms;
using Toolbox.Library.Forms;
using Bfres.Structs;

namespace FirstPlugin
{
    public class PTCL_3DS
    {
        public class Header
        {
            public List<EmitterSet> emitterSets = new List<EmitterSet>();
            public List<TextureInfo> Textures = new List<TextureInfo>();

            public bool IsSPBD = false;

            public uint EffectNameTableOffset;
            public uint TextureBlockTableOffset;
            public uint TextureBlockTableSize;
            public uint ShaderGtxTableOffset;
            public uint ShaderGtxTableSize;

            public uint Version;

            public void Read(FileReader reader, PTCL pctl)
            {
                uint Position = (uint)reader.Position; //Offsets are relative to this

                reader.ByteOrder = Syroot.BinaryData.ByteOrder.LittleEndian;
                string Signature = reader.ReadString(4, Encoding.ASCII);

                if (Signature == "SPBD")
                    IsSPBD = true;

                Version = reader.ReadUInt32();
                uint EmitterCount = reader.ReadUInt32();
                uint Padding = reader.ReadUInt32();
                EffectNameTableOffset = reader.ReadUInt32();
                TextureBlockTableOffset = reader.ReadUInt32();
                TextureBlockTableSize = reader.ReadUInt32();

                if (Version > 11)
                {
                    ShaderGtxTableOffset = reader.ReadUInt32();
                    ShaderGtxTableSize = reader.ReadUInt32();
                    uint KeyAnimationTableOffset = reader.ReadUInt32();
                    uint KeyAnimationTableSize = reader.ReadUInt32();
                    uint PrimativeTableOffset = reader.ReadUInt32();
                    uint PrimativeTableSize = reader.ReadUInt32();
                    uint ShaderParamTableOffset = reader.ReadUInt32();
                    uint ShaderParamTableSize = reader.ReadUInt32();
                    uint TotalTextureTableSize = reader.ReadUInt32();
                    uint Unknown1 = reader.ReadUInt32();
                    uint Unknown2 = reader.ReadUInt32();
                }


                var groupEmitterSets = new TreeNode("Emitter Sets");
                var textureFolder = new TreeNode("Textures");
                pctl.Nodes.Add(textureFolder);
                pctl.Nodes.Add(groupEmitterSets);

                for (int i = 0; i < EmitterCount; i++)
                {
                    EmitterSet emitterSet = new EmitterSet();
                    emitterSet.Read(reader, this);
                    emitterSets.Add(emitterSet);
                    groupEmitterSets.Nodes.Add(emitterSet);
                }

                int index = 0;
                foreach (var tex in Textures)
                {
                    tex.Text = $"Texture{index++}";
                    textureFolder.Nodes.Add(tex);
                }
            }
            public void Write(FileWriter writer, PTCL ptcl)
            {
                writer.ByteOrder = ByteOrder.LittleEndian;
                writer.Write(ptcl.data.ToArray());

                foreach (TextureInfo tex in ptcl.header3DS.Textures)
                {
                    //write texture blocks. Some slots are empty so check if it exists
                    tex.Write(writer, this);
                }

                foreach (EmitterSet emitterSets in emitterSets)
                {
                    foreach (Emitter3DS emitter in emitterSets.Nodes)
                    {
                        writer.Seek(((Emitter3DS)emitter).ColorPosition, SeekOrigin.Begin);
                        foreach (var color0 in emitter.Color0Array)
                        {
                            writer.Write(color0.R);
                            writer.Write(color0.G);
                            writer.Write(color0.B);
                            writer.Write(color0.A);
                        }
                        foreach (var color1 in emitter.Color1Array)
                        {
                            writer.Write(color1.R);
                            writer.Write(color1.G);
                            writer.Write(color1.B);
                            writer.Write(color1.A);
                        }
                    }
                }

                writer.Flush();
                writer.Close();
                writer.Dispose();
            }
        }

        public class EmitterSet : TreeNodeCustom
        {
            public void Read(FileReader reader, Header header)
            {
                uint NameOffset = 0;
                uint EmitterCount = 0;
                uint EmitterTableOffset = 0;

                if (header.IsSPBD)
                {
                    uint Description = reader.ReadUInt32();
                    uint Unknown = reader.ReadUInt32();
                    NameOffset = reader.ReadUInt32();
                    uint NamePointer = reader.ReadUInt32();
                    EmitterCount = reader.ReadUInt32();
                    EmitterTableOffset = reader.ReadUInt32();
                    uint Unknown2 = reader.ReadUInt32();
                }
                else
                {
                    uint padding = reader.ReadUInt32();
                    uint padding2 = reader.ReadUInt32();
                    NameOffset = reader.ReadUInt32();
                    uint padding3 = reader.ReadUInt32();
                    EmitterCount = reader.ReadUInt32();
                    EmitterTableOffset = reader.ReadUInt32();
                    uint padding4 = reader.ReadUInt32();
                }

                using (reader.TemporarySeek(header.EffectNameTableOffset + NameOffset, SeekOrigin.Begin))
                {
                    Text = reader.ReadString(BinaryStringFormat.ZeroTerminated);
                }

                long pos = reader.Position;

                reader.Seek(EmitterTableOffset, SeekOrigin.Begin);
                for (int i = 0; i < EmitterCount; i++)
                {
                    EmitterTable table = new EmitterTable();
                    table.Read(reader, header);
                    Nodes.Add(table.emitter);
                }

                reader.Seek(pos, SeekOrigin.Begin);
            }
            public void Write(FileWriter writer)
            {

            }
        }
        public class EmitterTable
        {
            public Emitter3DS emitter;
            public uint EmitterOffset;

            public void Read(FileReader reader, Header header)
            {
                if (header.IsSPBD)
                {
                    EmitterOffset = reader.ReadUInt32();
                    uint padding = reader.ReadUInt32();
                }
                else
                {
                    EmitterOffset = reader.ReadUInt32();
                    uint padding = reader.ReadUInt32();
                    uint padding2 = reader.ReadUInt32();
                    uint padding3 = reader.ReadUInt32();
                }

                long pos = reader.Position;

                reader.Seek(EmitterOffset, SeekOrigin.Begin);
                emitter = new Emitter3DS();
                emitter.Read(reader, header);

                reader.Seek(pos, SeekOrigin.Begin);
            }
            public void Write(FileWriter writer)
            {

            }
        }
        public class Emitter3DS : PTCL.Emitter
        {
            public long ColorPosition;

            public override void OnClick(TreeView treeview)
            {
                EmitterEditorNX editor = (EmitterEditorNX)LibraryGUI.GetActiveContent(typeof(EmitterEditorNX));
                if (editor == null)
                {
                    editor = new EmitterEditorNX();
                    LibraryGUI.LoadEditor(editor);
                }
                editor.Text = Text;
                editor.Dock = DockStyle.Fill;
                editor.LoadEmitter(this);
            }

            public void Read(FileReader reader, Header header)
            {
                long pos = reader.Position;

                if (header.Version <= 11)
                {
                    reader.Seek(12);
                }
                else
                {
                    reader.Seek(56);
                }

                uint NameOffset = reader.ReadUInt32();
                uint padding = reader.ReadUInt32();

                if (NameOffset != PTCL.NullOffset)
                {
                    using (reader.TemporarySeek(header.EffectNameTableOffset + NameOffset, SeekOrigin.Begin))
                    {
                        Text = reader.ReadString(BinaryStringFormat.ZeroTerminated);
                    }
                }

                int TextureCount = 2;
                if (header.Version <= 11)
                    TextureCount = 1;

                for (int i = 0; i < TextureCount; i++) //Max of 2 textures. Any more and it'll overlap some data
                {
                    TextureInfo textureInfo = new TextureInfo();
                    textureInfo.Read(reader, header, Text);

                    if (!textureInfo.IsEmpty())
                    {
                        DrawableTex.Add(textureInfo);

                        bool HasImage = header.Textures.Any(item => item.DataPos == textureInfo.DataPos);
                        if (!HasImage)
                        {
                            header.Textures.Add(textureInfo);
                        }
                    }
                }

                if (header.Version <= 11)
                {
                    reader.Seek(pos + 120, SeekOrigin.Begin); //This is a guess
                }
                else
                {
                    reader.Seek(pos + 1072, SeekOrigin.Begin);
                }

                Color0KeyCount = 8;
                Alpha0KeyCount = 8;
                Color1KeyCount = 8;
                Alpha1KeyCount = 8;

                Color0Type = ColorType.Random;
                Color1Type = ColorType.Random;
                Alpha0Type = ColorType.Random;
                Alpha1Type = ColorType.Random;

                Color0Array = new STColor[8];
                Color1Array = new STColor[8];
                Color0AlphaArray = new STColor[8];
                Color1AlphaArray = new STColor[8];

                ColorPosition = reader.Position;
                for (int i = 0; i < 8; i++)
                {
                    STColor clr = new STColor();
                    clr.R = reader.ReadSingle();
                    clr.G = reader.ReadSingle();
                    clr.B = reader.ReadSingle();
                    float A = reader.ReadSingle();
                    STColor alpha = new STColor();
                    alpha.R = A;
                    alpha.G = A;
                    alpha.B = A;
                    Color0AlphaArray[i] = alpha;
                    Color0Array[i] = clr;
                }
                for (int i = 0; i < 8; i++)
                {
                    STColor clr = new STColor();
                    clr.R = reader.ReadSingle();
                    clr.G = reader.ReadSingle();
                    clr.B = reader.ReadSingle();
                    float A = reader.ReadSingle();
                    STColor alpha = new STColor();
                    alpha.R = A;
                    alpha.G = A;
                    alpha.B = A;
                    Color1AlphaArray[i] = alpha;
                    Color1Array[i] = clr;
                }
            }

            public void Write(FileWriter writer)
            {

            }
        }
        public class TextureInfo : STGenericTexture
        {
            public const uint Alignment = 8192;

            public TextureInfo()
            {
                ImageKey = "Texture";
                SelectedImageKey = "Texture";
            }

            public override void OnClick(TreeView treeView)
            {
                UpdateEditor();
            }

            public override string ExportFilter => FileFilters.GTX;
            public override string ReplaceFilter => FileFilters.GTX;

            public override void Export(string FileName)
            {
                Export(FileName, false, false, 0, 0);
            }

            public override void Replace(string FileName)
            {
                int size = data.Length;

                FTEX ftex = new FTEX();
                ftex.ReplaceTexture(FileName, Format, MipCount, 0, SupportedFormats, true, true, true);
                if (ftex.texture != null)
                {
                    byte[] ImageData = ftex.texture.Data;


                    if (ftex.texture.MipData != null)
                        ImageData = Utils.CombineByteArray(ftex.texture.Data, ftex.texture.MipData);

                    //  if (ImageData.Length != size)
                    // MessageBox.Show($"Image size does not match! Make sure mip map count, format, height and width are all the same! Original Size {size} Import {ImageData.Length}", );

                    Swizzle = (byte)ftex.texture.Swizzle;

                    byte[] NewData = new byte[size];
                    Array.Copy(ImageData, 0, NewData, 0, size);

                    data = NewData;

                    UpdateEditor();
                }
            }

            public byte[] AlignData(byte[] data)
            {
                using (var mem = new MemoryStream(data))
                {
                    using (var writer = new FileWriter(mem))
                    {
                        writer.Write(data);
                        writer.Align((int)Alignment);

                        return mem.ToArray();
                    }
                }
            }

            public void UpdateEditor()
            {
                ImageEditorBase editor = (ImageEditorBase)LibraryGUI.GetActiveContent(typeof(ImageEditorBase));
                if (editor == null)
                {
                    editor = new ImageEditorBase();
                    editor.Dock = DockStyle.Fill;
                    LibraryGUI.LoadEditor(editor);
                }

                editor.Text = Text;
                editor.LoadProperties(GenericProperties);
                editor.LoadImage(this);
            }

            public override TEX_FORMAT[] SupportedFormats
            {
                get
                {
                    return new TEX_FORMAT[]
                    {

                            TEX_FORMAT.BC1_UNORM,
                            TEX_FORMAT.BC1_UNORM_SRGB,
                            TEX_FORMAT.BC2_UNORM,
                            TEX_FORMAT.BC2_UNORM_SRGB,
                            TEX_FORMAT.BC3_UNORM,
                            TEX_FORMAT.BC3_UNORM_SRGB,
                            TEX_FORMAT.BC4_UNORM,
                            TEX_FORMAT.BC4_SNORM,
                            TEX_FORMAT.BC5_UNORM,
                            TEX_FORMAT.BC5_SNORM,
                            TEX_FORMAT.R8_UNORM,
                            TEX_FORMAT.R8G8_UNORM,
                            TEX_FORMAT.R8G8B8A8_UNORM,
                            TEX_FORMAT.R8G8B8A8_UNORM_SRGB,
                            TEX_FORMAT.R8G8_SNORM,
                            TEX_FORMAT.B4G4R4A4_UNORM,
                            TEX_FORMAT.ETC1_UNORM,
                            TEX_FORMAT.ETC1_A4,
                            TEX_FORMAT.HIL08,
                            TEX_FORMAT.L4,
                            TEX_FORMAT.A4,
                            TEX_FORMAT.L8,
                            TEX_FORMAT.A8_UNORM,
                            TEX_FORMAT.LA8,
                            TEX_FORMAT.LA4,
                            TEX_FORMAT.B5G5R5A1_UNORM,
                    };
                }
            }

            public override bool CanEdit { get; set; } = false;

            public enum SurfaceFormat
            {
                INVALID = 0x0,
                TCS_R8_G8_B8_A8 = 2,
                T_BC1_UNORM = 3,
                T_BC1_SRGB = 4,
                T_BC2_UNORM = 5,
                T_BC2_SRGB = 6,
                T_BC3_UNORM = 7,
                T_BC3_SRGB = 8,
                T_BC4_UNORM = 9,
                T_BC4_SNORM = 10,
                T_BC5_UNORM = 11,
                T_BC5_SNORM = 12,
                TC_R8_UNORM = 13,
                TC_R8_G8_UNORM = 14,
                TC_R8_G8_B8_A8_SRGB = 15,
                TC_R8_SNORM = 16,
                TC_R4_R4_SNORM = 17,
                ETC1_A4 = 18,
                ETC1 = 19,
                HIL08 = 20,
                L4 = 21,
                A4 = 22,
                L8 = 23,
                A8 = 24,
                LA4 = 25,
                LA8 = 26,
                TCS_R5_G5_B5_A1_UNORM = 27,
                TC_R4_G4_B4_UNORM = 28,
                TC_R8_G8_B8_A8_UNORM = 29,
                TC_R8_G8_B8_UNORM = 30,
                TCS_R5_G6_B5_UNORM = 31,
            };

            public uint TileMode;
            public uint Swizzle;
            public byte WrapMode;
            public uint CompSel;
            public uint ImageSize;
            public uint ImageOffset;
            public SurfaceFormat SurfFormat;
            public CTR_3DS.PICASurfaceFormat SurfFormatOld;

            public byte[] data;
            public uint DataPos;

            public bool IsEmpty()
            {
                if (Width == 0 || Height == 0)
                    return true;
                else
                    return false;
            }

            public void Read(FileReader reader, Header header, string EmitterName)
            {
                CanReplace = true;
                CanRename = false;
                PlatformSwizzle = PlatformSwizzle.Platform_3DS;

                if (header.Version <= 11)
                {
                    Width = reader.ReadUInt16();
                    Height = reader.ReadUInt16();
                    ushort Format = reader.ReadUInt16();
                    ushort unk4 = reader.ReadUInt16();
                    SurfFormatOld = (CTR_3DS.PICASurfaceFormat)Format;
                    uint unk = reader.ReadUInt32();
                    ImageSize = reader.ReadUInt32();
                    DataPos = reader.ReadUInt32();
                    uint unk2 = reader.ReadUInt32();
                    uint unk3 = reader.ReadUInt32();
                }
                else
                {
                    Width = reader.ReadUInt16();
                    Height = reader.ReadUInt16();
                    Swizzle = reader.ReadUInt32();
                    uint Alignment = reader.ReadUInt32();
                    uint Pitch = reader.ReadUInt32();
                    WrapMode = reader.ReadByte(); //11 = repeat, 22 = mirror
                    byte unk = reader.ReadByte();
                    byte unk2 = reader.ReadByte();
                    byte unk3 = reader.ReadByte();
                    uint mipCount = reader.ReadUInt32();
                    CompSel = reader.ReadUInt32();
                    uint[] MipOffsets = reader.ReadUInt32s(13);
                    uint[] unk4 = reader.ReadUInt32s(4);

                    uint originalDataFormat = reader.ReadUInt32();
                    uint originalDataPos = reader.ReadUInt32();
                    uint originalDataSize = reader.ReadUInt32();
                    SurfFormat = reader.ReadEnum<SurfaceFormat>(false);
                    ImageSize = reader.ReadUInt32();
                    DataPos = reader.ReadUInt32();
                    uint handle = reader.ReadUInt32();
                }


                ArrayCount = 1;

                if (Width != 0 && Height != 0)
                {
                    using (reader.TemporarySeek(header.TextureBlockTableOffset + DataPos, SeekOrigin.Begin))
                    {
                        data = reader.ReadBytes((int)ImageSize);
                    }
                }

                if (data != null && data.Length > 0)
                {
                    ConvertFormat(header.Version);
                }
            }

            public void Write(FileWriter writer, Header header)
            {
                if (data != null && data.Length > 0)
                {
                    using (writer.TemporarySeek(header.TextureBlockTableOffset + DataPos, SeekOrigin.Begin))
                    {
                        writer.Write(data);
                    }
                }
            }

            public override void SetImageData(Bitmap bitmap, int ArrayLevel)
            {
                throw new NotImplementedException("Cannot set image data! Operation not implemented!");
            }

            uint GX2Format = 0;

            private void ConvertFormat(uint Version)
            {
                if (Version <= 11)
                {
                    Format = CTR_3DS.ConvertPICAToGenericFormat(SurfFormatOld);
                }
                else
                {

                    switch (SurfFormat)
                    {
                        case SurfaceFormat.T_BC1_UNORM:
                            Format = TEX_FORMAT.BC1_UNORM;
                            break;
                        case SurfaceFormat.T_BC1_SRGB:
                            Format = TEX_FORMAT.BC1_UNORM_SRGB;
                            break;
                        case SurfaceFormat.T_BC2_UNORM:
                            Format = TEX_FORMAT.BC2_UNORM;
                            break;
                        case SurfaceFormat.T_BC2_SRGB:
                            Format = TEX_FORMAT.BC2_UNORM_SRGB;
                            break;
                        case SurfaceFormat.T_BC3_UNORM:
                            Format = TEX_FORMAT.BC3_UNORM;
                            break;
                        case SurfaceFormat.T_BC3_SRGB:
                            Format = TEX_FORMAT.BC3_UNORM_SRGB;
                            break;
                        case SurfaceFormat.T_BC4_UNORM:
                            Format = TEX_FORMAT.BC4_UNORM;
                            break;
                        case SurfaceFormat.T_BC4_SNORM:
                            Format = TEX_FORMAT.BC4_SNORM;
                            break;
                        case SurfaceFormat.T_BC5_UNORM:
                            Format = TEX_FORMAT.BC5_UNORM;
                            break;
                        case SurfaceFormat.T_BC5_SNORM:
                            Format = TEX_FORMAT.BC5_SNORM;
                            break;
                        case SurfaceFormat.TC_R8_G8_UNORM:
                            Format = TEX_FORMAT.R8G8_UNORM;
                            break;
                        case SurfaceFormat.TC_R8_G8_B8_A8_SRGB:
                            Format = TEX_FORMAT.R8G8B8A8_UNORM_SRGB;
                            break;
                        case SurfaceFormat.TCS_R8_G8_B8_A8:
                            Format = TEX_FORMAT.R8G8B8A8_UNORM;
                            break;
                        case SurfaceFormat.TC_R8_UNORM:
                            Format = TEX_FORMAT.R8_UNORM;
                            break;
                        case SurfaceFormat.TCS_R5_G6_B5_UNORM:
                            Format = TEX_FORMAT.B5G6R5_UNORM;
                            break;
                        case SurfaceFormat.ETC1:
                            Format = TEX_FORMAT.ETC1_UNORM;
                            break;
                        case SurfaceFormat.ETC1_A4:
                            Format = TEX_FORMAT.ETC1_A4;
                            break;
                        case SurfaceFormat.L4:
                            Format = TEX_FORMAT.L4;
                            break;
                        case SurfaceFormat.L8:
                            Format = TEX_FORMAT.L8;
                            break;
                        case SurfaceFormat.LA4:
                            Format = TEX_FORMAT.LA4;
                            break;
                        case SurfaceFormat.LA8:
                            Format = TEX_FORMAT.LA8;
                            break;
                        case SurfaceFormat.HIL08:
                            Format = TEX_FORMAT.HIL08;
                            break;
                        case SurfaceFormat.TC_R8_G8_B8_A8_UNORM:
                            Format = TEX_FORMAT.R8G8B8A8_UNORM;
                            break;
                        case SurfaceFormat.TC_R4_G4_B4_UNORM:
                            Format = TEX_FORMAT.B4G4R4A4_UNORM;
                            break;
                        case SurfaceFormat.TC_R8_G8_B8_UNORM:
                            Format = TEX_FORMAT.R8G8B8A8_UNORM;
                            break;
                        case SurfaceFormat.TC_R4_R4_SNORM:
                            Format = TEX_FORMAT.R4G4_UNORM;
                            break;
                        case SurfaceFormat.A4:
                            Format = TEX_FORMAT.A4;
                            break;
                        default:
                            throw new Exception("Format unsupported! " + SurfFormat);
                    }
                }
            }

            public override byte[] GetImageData(int ArrayLevel = 0, int MipLevel = 0, int DepthLevel = 0)
            {
                return data;
            }

            public void Write(FileWriter writer)
            {

            }
        }
    }
}
