using FSO.Files.Utils;
using System;
using System.IO;
using System.Linq;

namespace FSO.Files.Formats.IFF.Chunks
{
    public class PIFF : IffChunk
    {
        public static ushort CURRENT_VERSION = 2;
        public ushort Version = CURRENT_VERSION;
        public string SourceIff;
        public string Comment = "";
        public PIFFEntry[] Entries;

        public PIFF()
        {
            ChunkType = "PIFF";
        }

        public void AppendAddedChunks(IffFile file)
        {
            foreach (var chunk in file.SilentListAll())
            {
                if (chunk == this) continue;
                var entries = Entries.ToList();
                entries.Add(new PIFFEntry()
                {
                    ChunkID = chunk.ChunkID,
                    ChunkLabel = chunk.ChunkLabel,
                    ChunkFlags = chunk.ChunkFlags,
                    EntryType = PIFFEntryType.Add,
                    NewDataSize = (uint)(chunk.ChunkData?.Length ?? 0),
                    Type = chunk.ChunkType
                });
                Entries = entries.ToArray();
            }
        }

        public override void Read(IffFile iff, Stream stream)
        {
            using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
            {
                Version = io.ReadUInt16();
                SourceIff = io.ReadVariableLengthPascalString();
                if (Version > 1) Comment = io.ReadVariableLengthPascalString();
                Entries = new PIFFEntry[io.ReadUInt16()];
                for (int i=0; i<Entries.Length; i++)
                {
                    var e = new PIFFEntry();
                    e.Type = io.ReadCString(4);
                    e.ChunkID = io.ReadUInt16();
                    if (Version > 1) e.Comment = io.ReadVariableLengthPascalString();
                    e.EntryType = (PIFFEntryType)io.ReadByte();

                    if (e.EntryType == PIFFEntryType.Patch)
                    {
                        e.ChunkLabel = io.ReadVariableLengthPascalString();
                        e.ChunkFlags = io.ReadUInt16();
                        if (Version > 0) e.NewChunkID = io.ReadUInt16();
                        else e.NewChunkID = e.ChunkID;
                        e.NewDataSize = io.ReadUInt32();

                        var size = io.ReadUInt32();
                        e.Patches = new PIFFPatch[size];
                        uint lastOff = 0;
                        
                        for (int j=0; j<e.Patches.Length; j++)
                        {
                            var p = new PIFFPatch();

                            p.Offset = lastOff + io.ReadVarLen();
                            lastOff = p.Offset;
                            p.Size = io.ReadVarLen();
                            p.Mode = (PIFFPatchMode)io.ReadByte();

                            if (p.Mode == PIFFPatchMode.Add) p.Data = io.ReadBytes(p.Size);
                            e.Patches[j] = p;
                        }
                    }
                    Entries[i] = e;
                }
            }
        }

        public override bool Write(IffFile iff, Stream stream)
        {
            using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
            {
                io.WriteUInt16(CURRENT_VERSION);
                io.WriteVariableLengthPascalString(SourceIff);
                io.WriteVariableLengthPascalString(Comment);
                io.WriteUInt16((ushort)Entries.Length);
                foreach (var ent in Entries)
                {
                    io.WriteCString(ent.Type, 4);
                    io.WriteUInt16(ent.ChunkID);
                    io.WriteVariableLengthPascalString(ent.Comment);
                    io.WriteByte((byte)(ent.EntryType));

                    if (ent.EntryType == PIFFEntryType.Patch)
                    {
                        io.WriteVariableLengthPascalString(ent.ChunkLabel); //0 length means no replacement
                        io.WriteUInt16(ent.ChunkFlags);
                        io.WriteUInt16(ent.NewChunkID);
                        io.WriteUInt32(ent.NewDataSize);
                        io.WriteUInt32((uint)ent.Patches.Length);

                        uint lastOff = 0;
                        foreach (var p in ent.Patches)
                        {
                            io.WriteVarLen(p.Offset-lastOff);
                            lastOff = p.Offset;
                            io.WriteVarLen(p.Size);
                            io.WriteByte((byte)p.Mode);
                            if (p.Mode == PIFFPatchMode.Add) io.WriteBytes(p.Data);
                        }
                    }
                }
            }
            return true;
        }
    }
    
    public class PIFFEntry
    {
        public string Type;
        public ushort ChunkID;
        public ushort NewChunkID;
        public PIFFEntryType EntryType;
        public string Comment = "";

        public string ChunkLabel;
        public ushort ChunkFlags;
        public uint NewDataSize;

        public PIFFPatch[] Patches;

        public byte[] Apply(byte[] src)
        {
            var result = new byte[NewDataSize];
            uint srcPtr = 0;
            uint destPtr = 0;
            int i = 0;
            foreach (var p in Patches)
            {
                var copyCount = p.Offset - destPtr;
                Array.Copy(src, srcPtr, result, destPtr, copyCount);
                srcPtr += copyCount; destPtr += copyCount;
                if (p.Mode == PIFFPatchMode.Add)
                {
                    Array.Copy(p.Data, 0, result, destPtr, p.Size);
                    destPtr += p.Size;
                } else
                {
                    srcPtr += p.Size;
                }
                i++;
            }
            var remainder = NewDataSize - destPtr;
            if (remainder != 0) Array.Copy(src, srcPtr, result, destPtr, remainder);

            return result;
        }
    }

    public enum PIFFEntryType : byte
    {
        Patch,
        Remove,
        Add
    }

    public struct PIFFPatch
    {
        public uint Offset;
        public uint Size;
        public PIFFPatchMode Mode;
        public byte[] Data;
    }

    public enum PIFFPatchMode : byte
    {
        Remove = 0,
        Add = 1
    }
}