using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Dvb.Subtitling.Model;
using skyscraper5.Mpeg2;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Dvb.Subtitling
{
    class SubtitleDecoder : IPesProcessor
    {
        private readonly ISubtitleEventHandler _eventHandler;
        private DisplayDefinitionSegment _displayDefinition;
        private Page[] _pages;

        public SubtitleDecoder(ISubtitleEventHandler eventHandler)
        {
            _eventHandler = eventHandler;
        }

        private bool PayloadMissing(byte[] buffer)
        {
            if (buffer == null)
                return true;
            if (buffer.Length < 8)
                return true;
            byte cmp = buffer[0];
            for (int i = 1; i < buffer.Length; i++)
            {
                if (buffer[i] != cmp)
                    return false;
            }
            return true;
        }

        public void ProcessPesPacket(PesPacket packet)
        {
            if (_pages == null)
            {
                _pages = new Page[ushort.MaxValue];
            }

            byte[] payload = packet.Payload;
            if (PayloadMissing(payload))
                return;
            MemoryStream ms = new MemoryStream(payload, false);

            byte data_identifier = ms.ReadUInt8();
            if (data_identifier != 0x20)
                return;

            byte subtitle_stream_id = ms.ReadUInt8();
            if (subtitle_stream_id != 0x00)
                return;

            while (true)
            { 
                byte syncByte = ms.ReadUInt8();
                if (syncByte != 0x0f)
                {
                    return;
                }

                byte segmentType = ms.ReadUInt8();
                ushort pageId = ms.ReadUInt16BE();
                ushort segmentLength = ms.ReadUInt16BE();
                if (segmentLength > ms.GetAvailableBytes())
                    return;
                byte[] segmentData = ms.ReadBytes(segmentLength);
                HandleSegment(segmentType, pageId, segmentData);
            }
        }

        private void HandleSegment(byte segmentType, ushort pageId, byte[] segmentData)
        {
            switch (segmentType)
            {
                case 0x10:
                    PageCompositionSegment pcs = new PageCompositionSegment(segmentData);
                    _pages[pageId] = new Page();
                    if (_displayDefinition == null)
                    {
                        _displayDefinition = new DisplayDefinitionSegment();
                    }
                    _pages[pageId].DisplayDefinition = _displayDefinition;
                    _pages[pageId].PageComposition = pcs;
                    break;
                case 0x11:
                    RegionCompositionSegment rcs = new RegionCompositionSegment(segmentData);
                    _pages[pageId].Regions[rcs.RegionId] = rcs;
                    break;
                case 0x12:
                    ClutDefinitionSegment cds = new ClutDefinitionSegment(segmentData);
                    ClutFamily clutFamiliy = _pages[pageId].GetClutFamiliy(cds.ClutId);
                    ApplyClutPatches(cds, clutFamiliy);
                    break;
                case 0x13:
                    ObjectDataSegment ods = new ObjectDataSegment(segmentData, _pages[pageId]);
                    if (ods.ObjectData != null)
                    {
                        _pages[pageId].ObjectData.Add(ods);
                    }
                    break;
                case 0x14:
                    this._displayDefinition = new DisplayDefinitionSegment(segmentData);
                    break;
                case 0x80:
                    _eventHandler.NotifyOfSubtitleLine(_pages[pageId]);
                    break;
                case 0xff:
                    //stuffing
                    break;
                default:
                    throw new NotImplementedException(String.Format("{0} 0x{1:X2}", nameof(segmentType), segmentType));
            }
        }

        private void ApplyClutPatches(ClutDefinitionSegment input, ClutFamily output)
        {
            foreach (ClutDefinitionSegment.ClutEntry inputClutEntry in input.ClutEntries)
            {
                if (inputClutEntry._2bitEntryClutFlag)
                {
                    if (inputClutEntry.ClutEntryId > 3)
                        break;
                    output._2bitClut[inputClutEntry.ClutEntryId] = new Color(inputClutEntry.Y, inputClutEntry.Cb, inputClutEntry.Cr, inputClutEntry.T);
                }
                else if (inputClutEntry._4bitEntryClutFlag)
                {
                    if (inputClutEntry.ClutEntryId > output._4bitClut.Length)
                        continue;
                    output._4bitClut[inputClutEntry.ClutEntryId] = new Color(inputClutEntry.Y, inputClutEntry.Cb,
                        inputClutEntry.Cr, inputClutEntry.T);
                }
                else if (inputClutEntry._8bitEntryClutFlag)
                    output._8bitClut[inputClutEntry.ClutEntryId] = new Color(inputClutEntry.Y, inputClutEntry.Cb, inputClutEntry.Cr, inputClutEntry.T);
                else
                    continue;
            }
        }
    }
}
