using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Mpeg2;
using skyscraper5.Scte35.Descriptors;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Scraper;

namespace skyscraper5.Scte35
{
    class Scte35SiDecoder : IPsiProcessor
    {
        public ushort ProgramNumber { get; }
        public IScte35EventHandler EventHandler { get; }

        public Scte35SiDecoder(ushort _programNumber, IScte35EventHandler _eventHandler)
        {
            ProgramNumber = _programNumber;
            EventHandler = _eventHandler;
        }


        public const byte IMPLEMENTED_VERSION = 0;
        public void GatherPsi(PsiSection section, int sourcePid)
        {
            MemoryStream ms = new MemoryStream(section.GetDataCopy(), false);
            byte tableId = ms.ReadUInt8();
            if (tableId != 0xfc)
                return;

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

            byte protocolVersion = ms.ReadUInt8();
            if (protocolVersion > IMPLEMENTED_VERSION)
                return;

            byte readUInt8 = ms.ReadUInt8();
            bool encryptedPacket = (readUInt8 & 0x80) != 0;
            int encryptionAlgorithm = (readUInt8 & 0x7e) >> 1;
            if (encryptedPacket && encryptionAlgorithm != 0)
                return;
            
            long ptsAdjustment = (readUInt8 & 0x01);
            ptsAdjustment <<= 1;
            ptsAdjustment += ms.ReadUInt32BE();

            byte cwIndex = ms.ReadUInt8();

            uint readUInt32Be = ms.ReadUInt32BE();
            uint tier = (readUInt32Be & 0xfff00000) >> 20;
            uint spliceCommandLength = (readUInt32Be & 0x000fff00) >> 8;
            uint spliceCommandType = (readUInt32Be & 0x000000ff);
            if (ms.GetAvailableBytes() < spliceCommandLength)
                return;
            byte[] spliceCommand = ms.ReadBytes(spliceCommandLength);

            TimeSignal timeSignal = null;
            SpliceInsert spliceInsert = null;
            switch (spliceCommandType)
            {
                case 0:
                    SpliceNull(spliceCommand);
                    return;
                case 5:
                    spliceInsert = new SpliceInsert(spliceCommand);
                    break;
                case 6:
                    timeSignal = this.TimeSignal(spliceCommand);
                    break;
                case 7:
                    BandwidthReservation(spliceCommand);
                    return;
                case 0xff:
                    PrivateCommand(spliceCommand);
                    return;
                default:
                    throw new NotImplementedException(String.Format("SCTE35 Command {0:X2}", spliceCommandType));
            }

            int descriptorLoopLength = ms.ReadUInt16BE() & 0x0fff;
            if (ms.GetAvailableBytes() < descriptorLoopLength)
                return;
            byte[] descriptorLoop = ms.ReadBytes(descriptorLoopLength);
            Scte35DescriptorCollection descriptorOutput = new Scte35DescriptorCollection();
            IEnumerable<TsDescriptor> descriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(descriptorLoop, "SCTE35");
            foreach (TsDescriptor descriptor in descriptors)
            {
                string name = descriptor.GetType().Name;
                switch (name)
                {
                    case nameof(AvailDescriptor):
                        descriptorOutput.availDescriptor = (AvailDescriptor)descriptor;
                        break;
                    case nameof(SegmentationDescriptor):
                        descriptorOutput.segmentationDescriptor = (SegmentationDescriptor)descriptor;
                        break;
                    case nameof(_0x03_TimeDescriptor):
                        descriptorOutput.timeDescriptor = (_0x03_TimeDescriptor)descriptor;
                        break;
                    default:
                        throw new NotImplementedException(name);
                }
            }

            long stuffingLength = ms.GetAvailableBytes();
            stuffingLength -= 4;        //for crc_32
            if (encryptedPacket)
                stuffingLength -= 4;    //for e_crc_32
            if (stuffingLength < 0)
                return;

            byte[] alignmentStuffing = ms.ReadBytes(stuffingLength);
            if (encryptedPacket)
            {
                uint eCrc32 = ms.ReadUInt32BE();
                throw new NotImplementedException(String.Format("{0} {1}", nameof(encryptedPacket), nameof(eCrc32)));
            }

            uint crc32 = ms.ReadUInt32BE();

            if (spliceInsert != null)
            {
                spliceInsert.Descriptors = descriptorOutput;
                EventHandler.NotifySpliceInsert(ProgramNumber, spliceInsert);
            }
            else if (timeSignal != null)
            {
                timeSignal.Descriptors = descriptorOutput;
                EventHandler.NotifyTimeSignal(ProgramNumber, timeSignal);
            }
            else
            {
                throw new NotImplementedException("No idea how to handle this SCTE-35 data.");
            }
        }

        private TimeSignal TimeSignal(byte[] spliceCommand)
        {
            MemoryStream ms = new MemoryStream(spliceCommand, false);
            TimeSignal result = new TimeSignal();
            result.Value = ms.ReadSpliceTime();
            ms.Close();
            return result;
        }

        private void BandwidthReservation(byte[] spliceCommand)
        {
        }

        private void SpliceNull(byte[] buffer)
        {

        }

        private void PrivateCommand(byte[] buffer)
        {
            //We have no idea what could be in there, so just skip it.
        }
        
    }
    
}
