using System.Net;
using System.Runtime.InteropServices;
using skyscraper5.Skyscraper.IO;

namespace skyscraper8.GSE;

public class BbfUdpDecap : IBbframeDeencapsulator
{
    public IUdpDecapOutput Sink { get; set; }
    public long NumPushed { get; private set; }

    public void SetTargetIp(IPAddress ip)
    {
        HasTargetIp = ip.GetAddressBytes();
    }

    public void SetTargetPort(int tmp)
    {
        HasTargetPort = new byte[4];
        HasTargetPort[0] = (byte)(tmp >> 8);
        HasTargetPort[1] = (byte)(tmp & 0xff);
    }

    private byte? HasMis;
    private byte[] HasSourceIp;
    private byte[] HasTargetIp;
    private byte[] HasSourcePort;
    private byte[] HasTargetPort;
    
    private byte[] inData;

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    struct bbheader
    {
        public byte MaType1;
        public byte MaType2;
        public byte Upl1;
        public byte Upl2;
        public byte Dfl1;
        public byte Dfl2;
        public byte Sync;
        public byte SyncD1;
        public byte SyncD2;
        public byte Crc8;
    }
    
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    unsafe struct Layer3
    {
        public byte l3sync;
        public bbheader header;
        public fixed byte Data[7254-10];
    }
    public unsafe void PushPacket(byte[] bbframe)
    {
        NumPushed++;
        
        if (inData == null)
        {
            inData = new byte[8100];
        }

        if (fragmentor == null)
        {
            fragmentor = new byte[256][];
            fragmentorLength = new int[256];
            fragmentorPos = new  int[256];
        }
        MemoryStream inbuf = new MemoryStream(bbframe);
        Layer3 myLayer;
        while (true)
        {
            if (inbuf.Peek() == 0xb8)
                break;
            inbuf.ReadByte();
        }

        while (true)
        {
            int fail = inbuf.Read(inData, 0, 11);
            if (fail != 11)
                break;
            myLayer = MemoryMarshal.Read<Layer3>(inData);
            if (myLayer.l3sync != 0xb8)
            {
                while (true)
                {
                    if (inbuf.Peek() == 0xb8)
                        break;
                    fail = inbuf.Read(inData, 0, 1);
                    if (fail != 1)
                        break;
                }
            }

            int bblength = myLayer.header.Dfl1 << 8 | myLayer.header.Dfl2;
            bblength >>= 3;
            fail = inbuf.Read(inData, 11, bblength);
            if (fail != bblength) 
                break;
            myLayer = MemoryMarshal.Read<Layer3>(inData);
            if (HasMis.HasValue && (myLayer.header.MaType2 != HasMis.Value))
                continue;

            int pos = 0;
            while (pos < bblength - 4)
            {
                int gseLength = ((myLayer.Data[pos] & 0x0f) << 8) | (myLayer.Data[pos + 1]);
                if ((myLayer.Data[pos] & 0xf0) == 0) break;
                if (gseLength + 2 > bblength - pos) break;
                if (!ProcessBbFrame(&myLayer.Data[pos], gseLength))
                    break;
                pos += gseLength += 2;
            }
        }
    }

    private byte[][] fragmentor;
    private int[] fragmentorLength;
    private int[] fragmentorPos;

    private unsafe void Copy(byte* src, byte[] dst, int srcOffset, int dstOffset, int len)
    {
        for (int i = 0; i < len; i++)
        {
            dst[dstOffset + i] = src[srcOffset + i];
        }
    }
    private unsafe bool ProcessBbFrame(byte* payload, int gseLength)
    {
        uint offset = 0;
        uint fragId = 0;

        if ((payload[0] & 0xc0) == 0x80)
        {
            fragId = payload[2];
            int length=(payload[3]<<8) | payload[4];
            if(fragmentor[fragId]!=null)
                fragmentor[fragId] = null;
            fragmentor[fragId] = new byte[length + 2];
            fragmentorLength[fragId] = length + 2;
            fragmentor[fragId][0] = payload[0];
            fragmentor[fragId][1] = payload[1];
            fragmentor[fragId][0] |= 0xc0;
            Copy(payload, fragmentor[fragId], 5, 2, gseLength - 3);
            fragmentorPos[fragId] = gseLength - 1;
        }
        else if ((payload[0] & 0xc0) == 0x00)
        {
            throw new NotImplementedException("inbetween packet");
        }
        else if ((payload[0] & 0xc0) == 0x40)
        {
            fragId = payload[2];
            if (fragmentor[fragId] == null)
                return true;

            Copy(payload, fragmentor[fragId], 3, fragmentorPos[fragId], gseLength - 5);
            fragmentorPos[fragId] += gseLength - 1;
            fixed (byte* p = fragmentor[fragId])
            {
                ProcessBbFrame(p, fragmentorLength[fragId]);
            }
            fragmentor[fragId] = null;

        }
        else if ((payload[0] & 0xc0) == 0xc0)
        {
            if (payload[offset + 2] == 0x00 && payload[offset + 3] == 0x04)
            {
                throw new NotImplementedException("ethertype 0x0400");
            }
            else if (payload[offset + 2] == 0x00 && payload[offset + 3] == 0x00)
            {
                throw new  NotImplementedException("ethertype 0x0000");
            }
            else if (payload[offset + 2] == 0x08 && payload[offset + 3] == 0x00)
            {
                if((payload[0]&0x30)==0x01) {
                    offset += 3;    //3-byte label
                }
                else if((payload[0] & 0x30) == 0x00) {
                    offset += 6;    //MAC address
                }

                offset += 0x10;     //IPv4 header 
                if (IsSelected(&payload[offset]))
                {
                    offset += 0x10;
                }
                else
                {
                    return true;
                }
                
                long sinkSize = gseLength + 2 -offset;
                byte[] sinkMe = new byte[sinkSize];
                Copy(payload,sinkMe,(int)offset,0, sinkMe.Length);
                Sink.Write(sinkMe);
                return true;
            }
            
            //throw new NotImplementedException("complete packet");
        }
        else if ((payload[0] & 0x0f0) == 0x00)
        {
            throw new NotImplementedException("padding packet");
        }
        return true;
    }

    private unsafe bool IsSelected(byte* buf)
    {
        byte zero = buf[0];
        byte one = buf[1];
        byte two = buf[2];
        byte three = buf[3];
        byte four = buf[4];
        byte five = buf[5];
        byte six = buf[6];
        byte seven = buf[7];
        
        if(HasSourceIp != null && (!(zero==HasSourceIp[0] && one==HasSourceIp[1] && two==HasSourceIp[2] && three==HasSourceIp[3])))
            return false;
        if(HasTargetIp != null && (!(four==HasTargetIp[0] && five==HasTargetIp[1] && six==HasTargetIp[2] && seven==HasTargetIp[3])))
            return false;
        if(HasSourcePort != null  &&(!(buf[0x8]==HasSourcePort[0] && buf[0x9]==HasSourcePort[1])))
            return false;
        if(HasTargetPort != null &&(!(buf[0xa]==HasTargetPort[0] && buf[0xb]==HasTargetPort[1])))
            return false;
        return true;
    }

    public interface IUdpDecapOutput
    {
        public void Write(byte[] buffer);
    }

    internal class UdpDecapFileOutput : IUdpDecapOutput
    {
        public UdpDecapFileOutput(FileInfo file)
        {
            file.Directory.EnsureExists();
            ourStream = file.OpenWrite();
        }

        private FileStream ourStream;
    
        public void Write(byte[] udpPacket)
        {
            ourStream.Write(udpPacket, 0, udpPacket.Length);
        }

        public void Dispose()
        {
            ourStream.Flush();
            ourStream.Close();
            ourStream.Dispose();
        }
    }
}