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

namespace skyscraper5.Mpeg2
{
    class PesPacket
    {
        private const byte PROGRAM_STREAM_MAP = 0b10111100;
        private const byte PRIVATE_STREAM_1 = 0b10111101;
        private const byte PADDING_STREAM = 0b10111110;
        private const byte PRIVATE_STREAM_2 = 0b10111111;
        private const byte ECM_STREAM = 0b11110000;
        private const byte EMM_STREAM = 0b11110001;
        private const byte DSMCC_STREAM = 0b11110010;
        private const byte ISO13522_STREAM = 0b11110011;
        private const byte H222_1_TYPE_A = 0b11110100;
        private const byte H222_1_TYPE_B = 0b11110101;
        private const byte H222_1_TYPE_C = 0b11110110;
        private const byte H222_1_TYPE_D = 0b11110111;
        private const byte H222_1_TYPE_E = 0b11111000;
        private const byte ANCILLARY_STREAM = 0b11111001;
        private const byte ISO14496_1_SL = 0b11111010;
        private const byte ISO14496_1_FLEXMUX = 0b11111011;
        private const byte PROGRAM_STREAM_DIRECTORY = 0b11111111;

        private const byte FAST_FORWARD = 0b00000000;
        private const byte SLOW_MOTION =  0b00000001;
        private const byte FREEZE_FRAME = 0b00000010;
        private const byte FAST_REVERSE = 0b00000011;
        private const byte SLOW_REVERSE = 0b00000100;

        public PesPacket(byte[] toArray)
        {
            int suspicious = 0;
            byte msStartOffset;
            if (toArray[0] == 0x00 & toArray[1] == 0x00 && toArray[2] == 0x00 && toArray[3] == 0x01)
                msStartOffset = 1;
            else if (toArray[0] == 0x00 & toArray[1] == 0x00 && toArray[2] == 0x01)
                msStartOffset = 0;
            else
                return;
            
            MemoryStream ms = new MemoryStream(toArray, msStartOffset, toArray.Length - msStartOffset, false);
            if (ms.ReadByte() != 0)
                throw new DvbException("invalid pes packet");
            if (ms.ReadByte() != 0)
                throw new DvbException("invalid pes packet");
            if (ms.ReadByte() != 1)
                throw new DvbException("invalid pes packet");

            StreamId = ms.ReadUInt8();
            PesPacketLength = ms.ReadUInt16BE();

            if (PesPacketLength > ms.GetAvailableBytes())
                return;

            toArray = ms.ReadBytes(PesPacketLength);
            if (PesPacketLength == 0)
                return;
            ms = new MemoryStream(toArray);
            if (StreamId != PROGRAM_STREAM_MAP &&
                StreamId != PADDING_STREAM &&
                StreamId != PRIVATE_STREAM_2 &&
                StreamId != ECM_STREAM &&
                StreamId != EMM_STREAM &&
                StreamId != PROGRAM_STREAM_DIRECTORY &&
                StreamId != DSMCC_STREAM &&
                StreamId != H222_1_TYPE_E)
            {
                byte readUInt8 = ms.ReadUInt8();
                int magic10 = readUInt8 & (0xc0) >> 6;
                if (magic10 != 1 && magic10 != 3)
                    suspicious++;
                PesScramblingControl = (readUInt8 & 0x030);
                PesPriority = (readUInt8 & 0x08) != 0;
                DataAlignmentIndicator = (readUInt8 & 0x04) != 0;
                Copyright = (readUInt8 & 0x02) != 0;
                OriginalOrCopy = (readUInt8 & 0x01) != 0;

                readUInt8 = ms.ReadUInt8();
                PtsDtsFlags = (readUInt8 & 0xc0) >> 6;
                EscrFlag = (readUInt8 & 0x20) != 0;
                EsRateFlag = (readUInt8 & 0x10) != 0;
                DsmTrickModeFlag = (readUInt8 & 0x08) != 0;
                AdditionalCopyInfoFlag = (readUInt8 & 0x04) != 0;
                PesCrcFlag = (readUInt8 & 0x02) != 0;
                PesExtensionFlag = (readUInt8 & 0x01) != 0;

                PesHeaderDataLength = ms.ReadUInt8();
                if (PesHeaderDataLength > ms.GetAvailableBytes())
                    return;
                byte[] PesHeaderData = ms.ReadBytes(PesHeaderDataLength);
                MemoryStream ms2 = new MemoryStream(PesHeaderData, false);

                if (PtsDtsFlags == 0b10)
                {
                    //PTS
                    readUInt8 = ms2.ReadUInt8();
                    int magic0010 = ((readUInt8 & 0b0010));
                    if (magic0010 == 0)
                        suspicious++;

                    PTS = (readUInt8 & 0xE0) >> 1;
                    PTS <<= 3;
                    bool markerBit = ((readUInt8 & 0x01) != 0);
                    if (!markerBit)
                    {
	                    //throw new DvbException("invalid pes packet");
                        //invalid pes packet
                        return;
                    }

                    ushort readUInt16Be = ms2.ReadUInt16BE();
                    int i = (readUInt16Be & 0xFFFE) >> 1;
                    PTS += i;
                    PTS <<= 15;
                    markerBit = ((readUInt16Be & 0x0001) != 0);
                    if (!markerBit)
                    {
						//throw new DvbException("invalid pes packet");
						//invalid pes packet
						return;
					}

                    if (ms2.GetAvailableBytes() < 2)
                    {
	                    return;
                    }
                    readUInt16Be = ms2.ReadUInt16BE();
                    i = (readUInt16Be & 0xFFFE) >> 1;
                    PTS += i;
                    markerBit = ((readUInt16Be & 0x0001) != 0);
                    if (!markerBit)
                    {
                        //broken packet, discard it.
                        return;
                    }
                }

                if (PtsDtsFlags == 0b11)
                {
                    //PTS
                    readUInt8 = ms2.ReadUInt8();
                    int magic0010 = ((readUInt8 & 0b0010) >> 4);
                    if (magic0010 == 0)
                        return;

                    PTS = (readUInt8 & 0xE0) >> 1;
                    PTS <<= 3;
                    bool markerBit = ((readUInt8 & 0x01) != 0);
                    if (!markerBit)
                        throw new DvbException("invalid pes packet");

                    ushort readUInt16Be = ms2.ReadUInt16BE();
                    int i = (readUInt16Be & 0xFFFE) >> 1;
                    PTS += i;
                    PTS <<= 15;
                    markerBit = ((readUInt16Be & 0x0001) != 0);
                    if (!markerBit)
                        throw new DvbException("invalid pes packet");

                    readUInt16Be = ms2.ReadUInt16BE();
                    i = (readUInt16Be & 0xFFFE) >> 1;
                    PTS += i;
                    markerBit = ((readUInt16Be & 0x0001) != 0);
                    if (!markerBit)
                        throw new DvbException("invalid pes packet");

                    //DTS
                    readUInt8 = ms2.ReadUInt8();
                    int magic0001 = ((readUInt8 & 0b0001) >> 4);
                    if (magic0001 == 0)
                        throw new DvbException("invalid pes packet");

                    DTS = (readUInt8 & 0xE0) >> 1;
                    DTS <<= 3;
                    markerBit = ((readUInt8 & 0x01) != 0);
                    if (!markerBit)
                        throw new DvbException("invalid pes packet");

                    readUInt16Be = ms2.ReadUInt16BE();
                    i = (readUInt16Be & 0xFFFE) >> 1;
                    DTS += i;
                    DTS <<= 15;
                    markerBit = ((readUInt16Be & 0x0001) != 0);
                    if (!markerBit)
                        throw new DvbException("invalid pes packet");

                    readUInt16Be = ms2.ReadUInt16BE();
                    i = (readUInt16Be & 0xFFFE) >> 1;
                    DTS += i;
                    markerBit = ((readUInt16Be & 0x0001) != 0);
                    if (!markerBit)
                        throw new DvbException("invalid pes packet");
                }

                if (EscrFlag)
                {
                    ulong escrBinary = ms2.ReadUInt32BE();
                    escrBinary <<= 16;
                    escrBinary += ms2.ReadUInt16BE();

                    //ESCR Base 32...30
                    EscrBase = (escrBinary & 0x0000380000000000) >> 40;
                    EscrBase <<= 3;

                    //Marker Flag
                    bool markerBit = (escrBinary & 0x0000040000000000) != 0;
                    if (!markerBit)
                        throw new DvbException("invalid pes packet");

                    //ESCR Base 29...15
                    ulong temp = (escrBinary & 0x000003FFF8000000) >> 27;
                    EscrBase += temp;
                    EscrBase <<= 15;

                    //Marker Flag
                    markerBit = (escrBinary & 0x0000000004000000) != 0;
                    if (!markerBit)
                        throw new DvbException("invalid pes packet");

                    //ESCR Base 14...0
                    temp = (escrBinary & 0x0000000003FFF800);
                    EscrBase += temp;

                    //Marker Flag
                    markerBit = (escrBinary & 0x0000000000000400) != 0;
                    if (!markerBit)
                        throw new DvbException("invalid pes packet");

                    //ESCR Extension
                    EscrExtension = (escrBinary & 0x00000000000003FE) >> 1;

                    //Marker Flag
                    markerBit = (escrBinary & 0x0000000000000001) != 0;
                    if (!markerBit)
                    {
                        //invalid PES
                        return;
                    }
                }

                if (EsRateFlag)
                {
                    uint esBinary = ms2.ReadUInt16BE();
                    esBinary <<= 8;
                    esBinary += ms.ReadUInt8();

                    //Marker Flag
                    bool markerFlag = (esBinary & 0x00800000) != 0;
                    if (!markerFlag)
                        throw new DvbException("invalid pes packet");

                    //ES Rate
                    EsRate = (esBinary & 0x007FFFFE) >> 1;

                    markerFlag = (esBinary & 0x00000001) != 0;
                    if (!markerFlag)
                    {
                        //Broken packet, so discard it?
                        if (ms.GetAvailableBytes() == 0)
                            return;
                    }
                }

                if (DsmTrickModeFlag)
                {
                    readUInt8 = ms2.ReadUInt8();
                    TrickModeControl = (readUInt8 & 0xE0) >> 5;
                    if (TrickModeControl == FAST_FORWARD)
                    {
                        FieldId = (readUInt8 & 0x18) >> 3;
                        IntraSliceRefresh = ((readUInt8 & 0x04) >> 2) != 0;
                        FrequencyTruncation = ((readUInt8 & 0x03));
                    }
                    if (TrickModeControl == SLOW_MOTION)
                    {
                        RepCntrl = (readUInt8 & 0x1F);
                    }
                    if (TrickModeControl == FREEZE_FRAME)
                    {
                        FieldId = (readUInt8 & 0x18) >> 3;
                    }
                    if (TrickModeControl == FAST_REVERSE)
                    {
                        FieldId = (readUInt8 & 0x18) >> 3;
                        IntraSliceRefresh = ((readUInt8 & 0x04) >> 2) != 0;
                        FrequencyTruncation = ((readUInt8 & 0x03));
                    }
                    if (TrickModeControl == SLOW_REVERSE)
                    {
                        RepCntrl = (readUInt8 & 0x1F);
                    }
                }

                if (AdditionalCopyInfoFlag)
                {
                    readUInt8 = ms2.ReadUInt8();
                    if ((readUInt8 & 0x80) == 0)
                        throw new DvbException("invalid pes packet");
                    AdditionalCopyInfo = readUInt8 & 0x7f;
                }

                if (PesCrcFlag)
                {
	                if (ms2.GetAvailableBytes() < 2)
		                return;
                    PreviousPesPacketCrc = ms2.ReadUInt16BE();
                }

                if (PesExtensionFlag)
                {
                    readUInt8 = ms2.ReadUInt8();
                    PesPrivateDataFlag = (readUInt8 & 0x80) != 0;
                    PackHeaderFieldFlag = (readUInt8 & 0x40) != 0;
                    ProgramPacketSequenceCounterFlag = (readUInt8 & 0x20) != 0;
                    PstdBufferFlag = (readUInt8 & 0x10) != 0;
                    PesExtensionFlag2 = (readUInt8 & 0x01) != 0;

                    if (PesPrivateDataFlag)
                    {
                        PesPrivateData = ms2.ReadBytes(16);
                    }

                    if (PackHeaderFieldFlag)
                    {
                        PackFieldLength = ms2.ReadUInt8();
                        if (ms2.GetAvailableBytes() < PackFieldLength)
                        {
                            return;
                        }
                        PackHeader = new ProgramStreamPackHeader(ms2.ReadBytes(PackFieldLength));
                    }

                    if (ProgramPacketSequenceCounterFlag)
                    {
                        readUInt8 = ms2.ReadUInt8();
                        if ((readUInt8 & 0x80) == 0)
                            throw new DvbException("invalid pes packet");

                        ProgramPacketSequenceCounter = readUInt8 & 0x7f;

                        readUInt8 = ms2.ReadUInt8();
                        if ((readUInt8 & 0x80) == 0)
                            throw new DvbException("invalid pes packet");
                        Mpeg1Mpeg2Identifier = (readUInt8 & 0x40) != 0;
                        OriginalStuffLength = readUInt8 & 0x3f;
                    }

                    if (PstdBufferFlag)
                    {
                        readUInt8 = ms2.ReadUInt8();
                        int magic01 = (readUInt8 & 0xc0) >> 6;
                    }

                    if (PesExtensionFlag2)
                    {
                        readUInt8 = ms2.ReadUInt8();
                        if ((readUInt8 & 0x80) == 0)
                            throw new DvbException("invalid pes packet");

                        PesExtensionFieldLength = readUInt8 & 0x7f;
                        ms2.Position += PesExtensionFieldLength;    //all these bytes are reserved!
                    }
                }

                long packetLen = PesPacketLength - PesHeaderDataLength;
                packetLen -= 3;
                if (packetLen > ms.GetAvailableBytes())
                    return;
                Payload = ms.ReadBytes(packetLen);

            }
            else if (StreamId == PROGRAM_STREAM_MAP ||
                     StreamId == PRIVATE_STREAM_2 ||
                     StreamId == ECM_STREAM ||
                     StreamId == EMM_STREAM ||
                     StreamId == PROGRAM_STREAM_DIRECTORY ||
                     StreamId == DSMCC_STREAM ||
                     StreamId == H222_1_TYPE_E)
            {
                long packetLen = PesPacketLength - PesHeaderDataLength;
                Payload = ms.ReadBytes(packetLen);
            }
            else if (StreamId == PADDING_STREAM)
            {
                Payload = ms.ReadBytes(ms.GetAvailableBytes());
            }

        }

        public ProgramStreamPackHeader PackHeader { get; private set; }

        public byte[] Payload { get; set; }


        public int PesExtensionFieldLength { get; private set; }

        public int OriginalStuffLength { get; private set; }

        public bool Mpeg1Mpeg2Identifier { get; private set; }

        public int ProgramPacketSequenceCounter { get; private set; }
        
        public int PackFieldLength { get; private set; }

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

        public bool PesExtensionFlag2 { get; private set; }

        public bool PstdBufferFlag { get; private set; }

        public bool ProgramPacketSequenceCounterFlag { get; private set; }

        public bool PackHeaderFieldFlag { get; private set; }

        public bool PesPrivateDataFlag { get; private set; }

        public ushort PreviousPesPacketCrc { get; private set; }

        public int AdditionalCopyInfo { get; private set; }

        public int RepCntrl { get; private set; }

        public int FrequencyTruncation { get; private set; }

        public bool IntraSliceRefresh { get; private set; }

        public int FieldId { get; private set; }

        public int TrickModeControl { get; private set; }

        public uint EsRate { get; private set; }

        public ulong EscrExtension { get; private set; }

        public ulong EscrBase { get; set; }

        public long DTS { get; private set; }

        public long PTS { get; private set; }

        public byte PesHeaderDataLength { get; private set; }

        public bool PesExtensionFlag { get; private set; }

        public bool PesCrcFlag { get; set; }

        public bool AdditionalCopyInfoFlag { get; private set; }

        public bool DsmTrickModeFlag { get; private set; }

        public bool EsRateFlag { get; private set; }

        public bool EscrFlag { get; private set; }

        public int PtsDtsFlags { get; private set; }

        public bool OriginalOrCopy { get; private set; }

        public bool Copyright { get; private set; }

        public bool DataAlignmentIndicator { get; private set; }

        public bool PesPriority { get; private set; }

        public int PesScramblingControl { get; private set; }

        public ushort PesPacketLength { get; private set; }

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