using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace MatrixIO.IO.MpegTs
{
    public enum ScramblingControl : byte
    {
        NotScambled = 0,
        Reserved = 1,
        ScrambledWithEvenKey = 2,
        ScambledWithOddKey = 3,
    }

    public enum PacketIdentifier : ushort
    {
        ProgramAssociationTable         = 0x0000, // TID 0x00
        ConditionalAccessTable          = 0x0001, // TID 0x01
        TsDescriptionTable              = 0x0002, // TID 0x03
        // 0x03 to 0x0F RESERVED
        NetworkInformationTable         = 0x0010, // TID 0x40 (current) & 0x41 (other)
        ServiceDescriptionTable         = 0x0011, // TID 0x42,0x46 (ServiceDescription) & 0x4A (BouquetAssociation)
        EventInformationTable           = 0x0012,
        RunningStatusTable              = 0x0013,
        TimeTables                      = 0x0014, // TID 0x70 (TimeAndDate) & 0x73 (TimeOffset)
        NetworkSynchronization          = 0x0015,
        ResolutionNotificationTable     = 0x0016,
        // 0x0017 to 0x001B RESERVED
        InbandSignalling                = 0x001C,
        Measurement                     = 0x001D,
        DiscontinuityInformation        = 0x001E,
        SectionIformation               = 0x001F,
        // USER DEFINED
        NullPacket                      = 0x1FFF,
    }

    public class TsPacket
    {
        public const int Length = 188;
        internal const byte SyncByte = 0x47;

        private ushort _header1;
        private byte _header2;

        public bool TransportErrorIndicator
        {
            get { return (_header1 & 0x8000) != 0; }
            set { if (value) _header1 |= 0x8000; else _header1 &= 0x7FFF; }
        }

        public bool PayloadUnitStartIndicator
        {
            get { return (_header1 & 0x4000) != 0; }
            set { if (value) _header1 |= 0x4000; else _header1 &= 0xBFFF; }
        }

        public bool TransportPriority
        {
            get { return (_header1 & 0x2000) != 0; }
            set { if (value) _header1 |= 0x2000; else _header1 &= 0xDFFF; }
        }

        /// <summary>
        /// 13 bit identifier.  Valid values are 0 to 8191.
        /// </summary>
        public ushort PacketIdentifier
        {
            get { return (ushort)(_header1 & 0x1FFF); }
            set
            {
                Debug.Assert(value < 8192, "PacketIdentifier must be in the range of 0 to 8191.");
                _header1 = (ushort)((_header1 & 0xE000) | (value & 0x1FFF));
            }
        }

        public ScramblingControl ScramblingControl
        {
            get { return (ScramblingControl)((_header2 & 0xC0) >> 6); }
            set 
            { 
                Debug.Assert((int)value < 4, "ScramblingControl must be in the range of 0 to 3.");
                _header2 = (byte)((_header2 & 0x3F) | (((byte)value << 6) & 0xC0));
            }
        }

        private bool HasAdaptationField
        {
            get { return (_header2 & 0x20) != 0; }
            set { if (value) _header2 |= 0x20; else _header2 &= 0xDF; }
        }
        private bool HasPayload
        {
            get { return (_header2 & 0x10) != 0; }
            set { if (value) _header2 |= 0x10; else _header2 &= 0xEF; }
        }

        /// <summary>
        /// 4 bit counter. Valid values are 0 to 15.
        /// </summary>
        public byte ContinuityCounter
        {
            get { return (byte)(_header2 & 0x0F); }
            set
            {
                Debug.Assert((int) value < 16, "ContinuityCounter must be in the range of 0 to 15.");
                _header2 = (byte)((_header2 & 0xF0) | (value & 0x0F));
            }
        }

        private AdaptationField _adaptationField;
        public AdaptationField AdaptationField
        {
            get
            {
                return HasAdaptationField ? _adaptationField : null;    
            }
            set
            {
                _adaptationField = value;
                HasAdaptationField = (value != null);
            }
        }

        private byte[] _payload;
        public byte[] Payload
        {
            get { return HasPayload ? _payload : null; }
            set
            {
                _payload = value;
                HasPayload = (value != null);
            }
        }

        public byte[] Stuffing { get; set; }

        public TsPacket(byte[] buffer, int offset=0)
        {
            int position = offset;

            if (buffer.Length - offset < Length) 
                throw new ArgumentOutOfRangeException("Buffer must contain at least " + Length + " bytes.");
            if (buffer[position++] != SyncByte) 
                throw new ArgumentException("Packet does not begin with 0x" + SyncByte.ToString("X2") + " sync byte.");
            _header1 = (ushort)((buffer[position++] << 8) | buffer[position++]);
            _header2 = buffer[position++];

            if(HasAdaptationField)
            {
                _adaptationField = new AdaptationField(buffer, position);
                position += AdaptationField.Length + 1;
            }

            if (HasPayload)
            {
                int payloadLength = Length - (position - offset);
                _payload = new byte[payloadLength];
                Buffer.BlockCopy(buffer, position, _payload, 0, payloadLength);
                position += payloadLength;
            }
            int stuffingLength = Length - (position - offset);
            if (stuffingLength > 0)
            {
                Debug.WriteLine("Has " + stuffingLength + " bytes of Transport Packet stuffing.");
                Stuffing = new byte[stuffingLength];
            }
        }
    }
}