using Serilog;

namespace KeepersCompound.LGS.Database.Chunks;

public record LinkId
{
    private readonly uint _data;

    public LinkId(uint data)
    {
        _data = data;
    }

    public uint GetId()
    {
        return _data & 0xFFFF;
    }

    public bool IsConcrete()
    {
        return (_data & 0xF0000) != 0;
    }

    public uint GetRelation()
    {
        return (_data >> 20) & 0xFFF;
    }

    public uint GetRaw()
    {
        return _data;
    }

    public void Write(BinaryWriter writer)
    {
        writer.Write(_data);
    }
}

public class LinkChunk : IChunk, IMergable
{
    public record Link
    {
        public LinkId linkId;
        public int source;
        public int destination;
        public ushort relation;

        public Link(BinaryReader reader)
        {
            linkId = new LinkId(reader.ReadUInt32());
            source = reader.ReadInt32();
            destination = reader.ReadInt32();
            relation = reader.ReadUInt16();
        }

        public void Write(BinaryWriter writer)
        {
            linkId.Write(writer);
            writer.Write(source);
            writer.Write(destination);
            writer.Write(relation);
        }
    }

    public ChunkHeader Header { get; set; }
    public List<Link> links;

    public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
    {
        links = new List<Link>();
        while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
        {
            links.Add(new Link(reader));
        }
    }

    public void WriteData(BinaryWriter writer)
    {
        foreach (var link in links)
        {
            link.Write(writer);
        }
    }

    public void Merge(IMergable other)
    {
        // !HACK: We always merge into gamesys so we can pre-trim garbage here
        var count = links.Count;
        for (var i = count - 1; i >= 0; i--)
        {
            var link = links[i];
            if (link.linkId.IsConcrete())
            {
                links.RemoveAt(i);
            }
        }
        
        if (links.Count != count)
        {
            Log.Information("Trimming excess Links in GAM: {StartCount} -> {EndCount}", count, links.Count);
        }
        
        links.AddRange(((LinkChunk)other).links);
    }
}

// TODO: This should be generic like Property
public class LinkDataMetaProp : IChunk, IMergable
{
    public record LinkData
    {
        public LinkId linkId;
        public int priority;

        public LinkData(BinaryReader reader)
        {
            linkId = new LinkId(reader.ReadUInt32());
            priority = reader.ReadInt32();
        }

        public void Write(BinaryWriter writer)
        {
            linkId.Write(writer);
            writer.Write(priority);
        }
    }

    public ChunkHeader Header { get; set; }
    public int DataSize;
    public List<LinkData> linkData;

    public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
    {
        DataSize = reader.ReadInt32();
        linkData = new List<LinkData>();
        while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
        {
            linkData.Add(new LinkData(reader));
        }
    }

    public void WriteData(BinaryWriter writer)
    {
        writer.Write(DataSize);
        foreach (var data in linkData)
        {
            data.Write(writer);
        }
    }

    public void Merge(IMergable other)
    {
        // !HACK: We always merge into gamesys so we can pre-trim garbage here
        var count = linkData.Count;
        for (var i = count - 1; i >= 0; i--)
        {
            var link = linkData[i];
            if (link.linkId.IsConcrete())
            {
                linkData.RemoveAt(i);
            }
        }

        if (linkData.Count != count)
        {
            Log.Information("Trimming excess LinkData in GAM: {StartCount} -> {EndCount}", count, linkData.Count);
        }
        
        linkData.AddRange(((LinkDataMetaProp)other).linkData);
    }
}