using System.IO;
using skyscraper5.Dvb;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Mpeg2
{
    public class TsAdaptionField : Validatable
    {
        public TsAdaptionField(Span<byte> adaptionField, bool TEI)
        {
            byte adaptionFieldLength = (byte)adaptionField.Length;
            if (adaptionFieldLength > 183)
            {
                Valid = false;
                return;
            }
            
            if (adaptionFieldLength == 0)
            {
                Valid = true;
                return; //Seems valid according to ISO 13818-1
            }

            byte bitmask = adaptionField[0];
            Discontinuity = ((bitmask & 0x80) >> 7) != 0;
            RandomAccess = ((bitmask & 0x40) >> 6) != 0;
            PriorityIndicator = ((bitmask & 0x20) >> 5) != 0;
            PcrPresent = ((bitmask & 0x10) >> 4) != 0;
            OpcrPresent = ((bitmask & 0x08) >> 3) != 0;
            SplicingPointPresent = ((bitmask & 0x04) >> 2) != 0;
            TransportPrivateDataPresent = ((bitmask & 0x02) >> 1) != 0;
            AdaptionFieldExtensionPresent = ((bitmask & 0x01)) != 0;

            MemoryStream binaryReader = new MemoryStream(adaptionField.ToArray(), 1, adaptionFieldLength - 1);
            if (PcrPresent)
            {
                if (binaryReader.GetAvailableBytes() < 6)
                {
                    Valid = false;
                    return;
                }
                uint pcrA = binaryReader.ReadUInt32BE();
                ushort pcrB = binaryReader.ReadUInt16BE();
                ulong pcr_base = ((ulong)pcrA << 1) | ((ulong)pcrB >> 15);
                ulong pcr_ext = (ulong)pcrB & 0x01ff;
                PCR = pcr_base * 300 + pcr_ext;
            }
            if (OpcrPresent)
            {
                if (binaryReader.GetAvailableBytes() < 6)
                {
                    Valid = false;
                    return;
                }
                uint opcrA = binaryReader.ReadUInt32BE();
                ushort opcrB = binaryReader.ReadUInt16BE();
                ulong pcr_base = ((ulong)opcrA << 1) | ((ulong)opcrB >> 15);
                ulong pcr_ext = (ulong)opcrB & 0x01ff;
                OPCR = pcr_base * 300 + pcr_ext;
            }
            if (SplicingPointPresent)
            {
                if (binaryReader.GetAvailableBytes() == 0)
                {
                    Valid = false;
                    return;
                }    
                SplicingPoint = binaryReader.ReadUInt8();
            }

            if (TransportPrivateDataPresent)
            {
                if (binaryReader.GetAvailableBytes() == 0)
                {
                    Valid = false;
                    return;
                }
                byte TransportPrivateDataLength = binaryReader.ReadUInt8();
                long maxLen = binaryReader.Length - binaryReader.Position;
                if (TransportPrivateDataLength <= maxLen)
                {
                    TransportPrivateData = binaryReader.ReadBytes(TransportPrivateDataLength);
                }
                else
                {
                    Valid = false;
                    return;
                }
            }

            if (AdaptionFieldExtensionPresent)
            {
                AdaptionFieldExtension = new TsAdaptionFieldExtension();

                if (binaryReader.GetAvailableBytes() == 0)
                {
                    Valid = false;
                    return;
                }

                byte extensionFlags1 = binaryReader.ReadUInt8();
                AdaptionFieldExtension.LtwFlag = (extensionFlags1 & 0x80) != 0;
                AdaptionFieldExtension.PiecewiseRateFlag = (extensionFlags1 & 0x40) != 0;
                AdaptionFieldExtension.SeamlessSpliceFlag = (extensionFlags1 & 0x20) != 0;
                
                if (AdaptionFieldExtension.LtwFlag)
                {
                    if (binaryReader.GetAvailableBytes() < 2)
                    {
                        Valid = false;
                        return;
                    }

                    ushort ltwRaw = binaryReader.ReadUInt16BE();
                    AdaptionFieldExtension.LtwValidFlag = (ltwRaw & 0x8000) != 0;
                    AdaptionFieldExtension.LtwOffset = (ltwRaw & 0x7fff);
                }
                
                if (AdaptionFieldExtension.PiecewiseRateFlag)
                {
                    if (binaryReader.GetAvailableBytes() < 3)
                    {
                        Valid = false;
                        return;
                    }

                    int piecewiseRaw = binaryReader.ReadByte() & 0xff;
                    piecewiseRaw += (binaryReader.ReadUInt16BE() & 0xffff) >> 8;
                    piecewiseRaw = piecewiseRaw & 0x3fffff;
                    AdaptionFieldExtension.PiecewiseRate = piecewiseRaw;
                }
                
                if (AdaptionFieldExtension.SeamlessSpliceFlag)
                {
                    if (binaryReader.Length - binaryReader.Position < 5)
                    {
                        Valid = false;
                        return;
                    }

                    byte spliceRawA = binaryReader.ReadUInt8();
                    AdaptionFieldExtension.SpliceType = spliceRawA & 0xf0;
                    AdaptionFieldExtension.DtsNextAccessUnit = (long)(spliceRawA & 0x0f);
                    AdaptionFieldExtension.DtsNextAccessUnit += (((long)binaryReader.ReadUInt32BE() & 0xfffefffeL) >> 4);
                }
            }

            Valid = true;
        }
        
        public bool PriorityIndicator { get; private set; }

        public bool RandomAccess { get; private set; }

        public bool Discontinuity { get; private set; }

        public TsAdaptionFieldExtension AdaptionFieldExtension { get; private set; }

        public bool AdaptionFieldExtensionPresent { get; private set; }

        public byte[] TransportPrivateData { get; private set; }

        public bool TransportPrivateDataPresent { get; private set; }

        public byte SplicingPoint { get; private set; }

        public bool SplicingPointPresent { get; private set; }

        public ulong OPCR { get; set; }

        public bool OpcrPresent { get; private set; }

        public ulong PCR { get; private set; }

        public bool PcrPresent { get; private set; }

        public byte Length { get; private set; }
    }
}
