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

namespace skyscraper5.Mpeg2
{
    public class TsPacket
    {
        public TsPacket(byte[] buffer)
        {
            Serial = serialGen++;

            RawPacket = (byte[])buffer.Clone();
            if (buffer.Length != 188)
                throw new ArgumentException("buffer.Length != 188");

            MemoryStream br = new MemoryStream(buffer, false);
            uint anInt = br.ReadUInt32BE();
            uint syncBytes = (anInt & 0xff000000) >> 24;
            if (syncBytes != 'G')
            {
                throw new InvalidTsPacketException();
            }

            TEI = (((anInt & 0x800000)) >> 23) != 0;
            PayloadUnitStart = (((anInt & 0x400000)) >> 22) != 0;
            Priority = ((anInt & 0x200000)) >> 21;
            PID = ((anInt & 0x1fff00)) >> 8;
            TSC = ((anInt & 0xc0)) >> 6;
            AdaptionFieldControl = ((anInt & 0x30)) >> 4;
            Continuity = ((anInt & 0xf));
            if (PID == 0x1FFF)
            {
                return;
            }

            if (TEI)
                return;

            if ((buffer[3] & 0x20) != 0)
            {
                //Skip adaption field if present
            }

            if ((buffer[1] & 0x40) != 0)
            {
                //Unit start
            }

            int payloadOffset = 4;
            if (PayloadUnitStart)
            {
                PayloadStartOffset = buffer[4] & 0xff;
                payloadOffset++;
            }
            
            switch (AdaptionFieldControl)
            {
                case 1:
                    br.Position = payloadOffset;
                    long PayloadLengthB = 188 - payloadOffset;
                    Payload = br.ReadBytes(PayloadLengthB);
                    break;
                case 2:
                    Adaption = new DvbAdaptionField(br, TEI);
                    if (!Adaption.Valid)
                        TEI = true;
                    break;
                case 3:
                    Adaption = new DvbAdaptionField(br, TEI);
                    if (!Adaption.Valid)
                    {
                        TEI = true;
                        break;
                    }
                    payloadOffset += Adaption.Length;
                    br.Position = payloadOffset;
                    long PayloadLength = (188 - payloadOffset);
                    Payload = br.ReadBytes((int)PayloadLength);
                    break;
                default:
                    TEI = true;
                    break;
            }
        }

        private static ulong serialGen;
        public DvbAdaptionField Adaption { get; private set; }

        public bool TEI { get; private set; }
        public bool PayloadUnitStart { get; private set; }
        public uint Priority { get; private set; }
        public uint PID { get; private set; }

        /// <summary>
        /// 0 = not scrambled
        /// 1 = reserved
        /// 2 = scrambled, even key
        /// 3 = scrambled, odd key
        /// </summary>
        public uint TSC { get; private set; }
        public uint AdaptionFieldControl { get; private set; }
        public uint Continuity { get; private set; }
        public int PayloadStartOffset { get; private set; }
        public byte[] Payload { get; private set; }
        public bool PayloadFlagSet => AdaptionFieldControl == 1 || AdaptionFieldControl == 3;
        public byte[] RawPacket { get; private set; }
        public ulong Serial { get; private set; }

        /// <summary>
        /// Retrieving the raw packet, including adaption field and Payload, but without the four byte header.
        /// </summary>
        /// <returns></returns>
        public byte[] GetBody()
        {
            byte[] buffer = new byte[184];
            Array.Copy(RawPacket, 4, buffer, 0, 184);
            return buffer;
        }
    }
}
