using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.DsmCc.Descriptors;
using skyscraper5.DsmCc.Message;
using skyscraper5.Dvb.Descriptors;
using skyscraper5.Mpeg2;
using skyscraper5.Mpeg2.Descriptors;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.DsmCc
{
    class DsmCcSection
    {
        public DsmCcSection(MemoryStream ms, byte expectedTableId)
        {
            byte tableId = ms.ReadUInt8();
            if (tableId != expectedTableId)
                throw new DsmCcException("{0} != {1}", nameof(tableId) != nameof(expectedTableId));

            ushort readUInt16Be = ms.ReadUInt16BE();
            bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0;
            bool privateIndicator = (readUInt16Be & 0x4000) != 0;
            int dsmSectionLength = readUInt16Be & 0x0fff;

            if (ms.GetAvailableBytes() < 9)
                return;
            ushort tableIdExtension = ms.ReadUInt16BE();

            byte readUInt8 = ms.ReadUInt8();
            int versionNumber = (readUInt8 & 0x3e) >> 1;
            bool currentNextIndicator = (readUInt8 & 0x01) != 0;

            byte sectionNumber = ms.ReadUInt8();

            byte lastSectionNumber = ms.ReadUInt8();

            long availableBytes = ms.GetAvailableBytes() - 4;
            byte[] remainingBytes = ms.ReadBytes(availableBytes);

            switch (expectedTableId)
            {
                case 0x3a:
                    throw new NotImplementedException("DecodeLLCSNAP");
                case 0x3b:
                    DecodeUserNetworkMessage(remainingBytes);
                    break;
                case 0x3c:
                    DecodeDownloadDataMessage(remainingBytes);
                    break;
                case 0x3d:
                    DecodeDsmCcDescriptorList(remainingBytes);
                    break;
                case 0x3e:
                    PrivateData = remainingBytes;
                    break;
                default:
                    File.WriteAllBytes("dsmcc.bin", remainingBytes);
                    throw new NotImplementedException();
            }

            uint crc32 = ms.ReadUInt32BE();
        }

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

        private void DecodeDsmCcDescriptorList(byte[] buffer)
        {
            DsmCcDescriptorList result = new DsmCcDescriptorList();

            try
            {
                IEnumerable<TsDescriptor> unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(buffer, "DSM-CC");
                foreach (TsDescriptor tsDescriptor in unpackDescriptors)
                {
                    string name = tsDescriptor.GetType().Name;
                    switch (name)
                    {
                        case nameof(StreamEventDescriptor):
                            result.StreamEventDescriptors.Add((StreamEventDescriptor)tsDescriptor);
                            break;
                        case nameof(UserDefinedDescriptor):
                            break;
                        default:
                            throw new NotImplementedException(name);
                    }
                }
            }
            catch (Exception e)
            {
                result.Error = e;
            }
            
            DsmCcMessage = result;
        }

        private void DecodeDownloadDataMessage(byte[] buffer)
        {
            MemoryStream ms = new MemoryStream(buffer, false);
            DsmCcMessageHeader header = ReadDsmccHeader(ms);
            if (header == null)
                return;
            uint downloadId = header.TransactionId;

            if (header.MessageLength > ms.GetAvailableBytes())
                return;

            byte[] messageContent = ms.ReadBytes(header.MessageLength);
            this.DsmCcMessage = DsmCcMessageUnpacker.GetInstance().UnpackMessage(header, messageContent);

        }

        private void DecodeUserNetworkMessage(byte[] buffer)
        {
            MemoryStream ms = new MemoryStream(buffer, false);
            DsmCcMessageHeader childHeader = ReadDsmccHeader(ms);
            if (childHeader == null)
            {
                Debug.WriteLine("DSM-CC Child Header not present...");
                return;
            }
            if (childHeader.MessageLength > ms.GetAvailableBytes())
            {
                Debug.WriteLine("DSM-CC Message too long...");
                return;
            }
            byte[] messageContent = ms.ReadBytes(childHeader.MessageLength);
            this.DsmCcMessage = DsmCcMessageUnpacker.GetInstance().UnpackMessage(childHeader, messageContent);
        }

        private DsmCcMessageHeader ReadDsmccHeader(MemoryStream ms)
        {
            if (ms.GetAvailableBytes() < 12)
                return null;
            byte protocolDiscriminator = ms.ReadUInt8();
            byte dsmccType = ms.ReadUInt8();
            ushort messageId = ms.ReadUInt16BE();
            uint transactionId = ms.ReadUInt32BE();
            byte reserved = ms.ReadUInt8();
            byte adaptionLength = ms.ReadUInt8();
            ushort messageLength = ms.ReadUInt16BE();
            DsmCcMessageHeader childHeader = new DsmCcMessageHeader(protocolDiscriminator, dsmccType, messageId, transactionId);
            if (adaptionLength > 2)
            {
                if (adaptionLength > ms.GetAvailableBytes())
                {
                    Debug.WriteLine("DSM-CC Adaption too long...");
                    return childHeader;
                }
                childHeader.AdaptionHeader = new DsmCcAdaptionHeader(ms.ReadBytes(adaptionLength));
            }
            messageLength -= adaptionLength;
            childHeader.MessageLength = messageLength;

            return childHeader;
        }

        public DsmCcMessage DsmCcMessage { get; private set; }
        
    }
}
