using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Dvb;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.IO;
using skyscraper5.src.Teletext;
using skyscraper5.Teletext.Vps;
using skyscraper5.Teletext.Wss;

namespace skyscraper5.Teletext
{
    class TeletextPesProcessor : IPesProcessor
    {
        public ITeletextPageHandler PageHandler { get; }
        public int NetworkId { get; }
        public int TransportStreamId { get; }
        public ushort ProgramNumber { get; }
		public int ElementaryPid { get; }

		public TeletextPesProcessor(ITeletextPageHandler pageHandler, int networkId, int transportStreamId, ushort programNumber, int elementaryPid)
        {
            PageHandler = pageHandler;
            NetworkId = networkId;
            TransportStreamId = transportStreamId;
            ProgramNumber = programNumber;
			ElementaryPid = elementaryPid;
			Magazines = new TeletextMagazine[8];
        }

        public TeletextMagazine[] Magazines { get; private set; }
        public uint? PrivateDataSpecifier { get; set; }

        public void ProcessPesPacket(PesPacket packet)
        {
            if (packet.Payload == null)
                return;
            if (packet.Payload.Length == 0)
                return;
            MemoryStream ms = new MemoryStream(packet.Payload, false);
            byte dataIdentifier = ms.ReadUInt8();
            if (dataIdentifier <= 0x0f)
            {
                //reserved for future use
                return;
            }

            if (dataIdentifier >= 0x20 && dataIdentifier <= 0x7f)
            {
                //reserved for future use
                return;
            }
            if (dataIdentifier >= 0x80)
            {
                //user defined
                return;
            }

            while (ms.GetAvailableBytes() > 0)
            {
                byte dataUnitId = ms.ReadUInt8();
                if (ms.GetAvailableBytes() < 1)
                    return;
                byte dataUnitLength = ms.ReadUInt8();
                if (ms.GetAvailableBytes() < dataUnitLength)
                    return;
                byte[] dataFieldContent = ms.ReadBytes(dataUnitLength);
                switch (dataUnitId)
                {
                    case 0x00:  //reserved for future use, discard according to en_301775v010201p.pdf, page 9, so discard
                    case 0x01:
                        break;
                    case 0x02:  //EBU Teletext non-subtitle data
                    case 0x03:  //EBU Teletext subtitle data
                        EbuTeletext(dataFieldContent);
                        break;
                    case 0xc0:  //Inverted Teletext (ETSI EN 301775, page 9)
                    case 0xc1:
                        EbuTeletext(dataFieldContent);
                        break;
                    case 0xc3:  //VPS (ETSI EN 301775)
                        Vps(dataFieldContent);
                        break;
                    case 0xc4:  //WSS (ETSI EN 301775)
                        Wss(dataFieldContent);
                        break;
                    case 0xc5:  //Closed Captioning (ETSI EN 301775)
                        throw new NotImplementedException("closed captioning");
                    case 0xc6:  //monochrome data (etsi en 301775)
                        MonochromeData(dataFieldContent);
                        break;
                    case 0xff:  //stuffing
                        break;
                    default:
                        if ((dataUnitId >= 0x04 && dataUnitId <= 0x7f))
                            continue;   //reserved according to en_301775v010201p.pdf, page 9, so discard
                        if ((dataUnitId >= 0xc7 && dataUnitId <= 0xfe) && !this.PrivateDataSpecifier.HasValue)
                            continue;
                        if ((dataUnitId >= 0x80 && dataUnitId <= 0xbf) && !this.PrivateDataSpecifier.HasValue)
                            continue;
                        throw new NotImplementedException(String.Format("Teletext {0} {1:X2}", nameof(dataUnitId), dataUnitId));
                }
            }
        }

		private void MonochromeData(byte[] dataFieldContent)
		{
            MemoryStream ms = new MemoryStream(dataFieldContent, false);
            byte byte1 = ms.ReadUInt8();
            MonochromeDataField result = new MonochromeDataField();
            result.FirstSegmentFlag = (byte1 & 0x80) != 0;
            result.LastSegmentFlag = (byte1 & 0x40) != 0;
            result.FieldParity = (byte1 & 0x20) != 0;
            result.LineOffset = (byte1 & 0x1f);
            result.FirstPixelPosition = ms.ReadUInt16BE();
            byte nPixels = ms.ReadUInt8();
            if (nPixels > ms.GetAvailableBytes())
                return;
            result.Pixels = ms.ReadBytes(nPixels);
            PageHandler.OnMonochromeData(NetworkId, TransportStreamId, ProgramNumber, result);
		}

		private void Wss(byte[] buffer)
        {
            WssDataField wssDataField = new WssDataField(buffer);
            PageHandler.OnWssData(NetworkId, TransportStreamId, ProgramNumber, wssDataField.WssDataBlock, ElementaryPid);
        }

        private void Vps(byte[] buffer)
        {
            VpsDataField vpsDataField = new VpsDataField(buffer);
            PageHandler.OnVpsData(NetworkId, TransportStreamId, ProgramNumber, vpsDataField.VpsDataBlock, ElementaryPid);
        }

        private void EbuTeletext(byte[] buffer)
        {
            EbuTeletextDataField df = new EbuTeletextDataField(buffer);
            if (!df.Valid)
            {
                return;
            }
            if (df.PacketNumber == 0)
            {
                if (Magazines[df.Magazine] != null)
                {
                    PageHandler.OnTeletextPage(NetworkId, TransportStreamId, ProgramNumber, Magazines[df.Magazine]);
                }
                Magazines[df.Magazine] = new TeletextMagazine(df.Magazine);
                Magazines[df.Magazine].Lines[0] = df.CharacterCodes;
                Magazines[df.Magazine].Header = df.Header;
                Magazines[df.Magazine].PageNumber = df.PageNumber;
                return;
            }

            if (Magazines[df.Magazine] == null)
                return;

            Magazines[df.Magazine].Lines[df.PacketNumber] = df.CharacterCodes;
        }
    }
}
