//#define ENABLE_DECODER

using System;
using System.IO;
using skyscraper5.Dvb.Subtitling.Model;
using skyscraper5.Skyscraper.Drawing;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Dvb.Subtitling
{
    public class ObjectDataSegment
    {
        
        public ObjectDataSegment(byte[] segmentData,Page page)
        {
            if (page == null)
            {
                return;
            }

            if (segmentData.Length == 0)
            {
                return;
            }

            MemoryStream ms = new MemoryStream(segmentData, false);
            Id = ms.ReadUInt16BE();
            ObjectDimensions = page.GenerateObjectDimensions(Id);
            if (ObjectDimensions == null)
                return;
            byte readUInt8 = ms.ReadUInt8();
            VersionNumber = (readUInt8 & 0xf0) >> 4;
            CodingMethod = (readUInt8 & 0x0c) >> 2;
            NonModifyingColorFlag = (readUInt8 & 0x02) != 0;
#if ENABLE_DECODER
            switch (CodingMethod)
            {
                case 0:
                    DecodePixels(ms);
                    break;
                default:
                    throw new NotImplementedException(String.Format("{0} {1}", nameof(CodingMethod), CodingMethod));
            }
#else
            //TODO: Maybe have another go at decoding this weird RLE variant?
            ObjectData = ms.ReadBytes(ms.GetAvailableBytes());
#endif
        }

#if ENABLE_DECODER
        private void DecodePixels(MemoryStream ms)
        {
            ushort TopFieldDataBlockLength = ms.ReadUInt16BE();
            if (TopFieldDataBlockLength > ms.Length)
                return;
            ushort BottomFieldDataBlockLength = ms.ReadUInt16BE();
            if (BottomFieldDataBlockLength > ms.Length)
                return;
            byte[] topFieldBuffer = ms.ReadBytes(TopFieldDataBlockLength);
            byte[] bottomFieldBuffer = ms.ReadBytes(BottomFieldDataBlockLength);

            MemoryStream output = new MemoryStream(new byte[ObjectDimensions.Width * ObjectDimensions.Height]);
            DecodePixelData(topFieldBuffer, output);

            output.Position = ObjectDimensions.Width;
            DecodePixelData(bottomFieldBuffer, output);
            ObjectData = output.ToArray();
            output.Dispose();
        }
        
        private void DecodePixelData(byte[] buffer,MemoryStream output)
        {
            MemoryStream ms = new MemoryStream(buffer, false);
            while (ms.GetAvailableBytes() > 0)
            {
                byte dataType = ms.ReadUInt8();
                //Refer to EN 300 743 V1.6.1, page 41
                switch (dataType)
                {
                    case 0x10:
                        throw new NotImplementedException(String.Format("{0} 0x{1:X2}", nameof(dataType), dataType));
                    case 0x11:
                        try
                        {
                            InBitStream bitstream = new InBitStream(ms);
                            CodeStringSignal signal = CodeStringSignal.Continue;
                            do
                            {
                                signal = Decode4bitPixelCodeString(bitstream, output);
                            } while (signal == CodeStringSignal.Continue);

                            if (!bitstream.IsByteAligned)
                                bitstream.ReadBitsAsByte(4);
                        }
                        catch (EndOfStreamException eose)
                        {
                            Console.WriteLine("Couldn't decode a subtitle frame. Note that this decoder is not yet working.");
                        }
                        break;
                    case 0x12:
                    case 0x20:
                    case 0x21:
                    case 0x22:
                        throw new NotImplementedException(String.Format("{0} 0x{1:X2}", nameof(dataType), dataType));
                    case 0xf0:
                        long currentLine = output.Position / ObjectDimensions.Width;
                        long currentLineOffset = currentLine * ObjectDimensions.Width;
                        output.Position = currentLineOffset;
                        output.Position += ObjectDimensions.Width;
                        output.Position += ObjectDimensions.Width;
                        break;
                    default:
                        break;
                }
            }
            output.Flush();
        }

        enum CodeStringSignal
        {
            Continue,
            EndOfString
        }

        private CodeStringSignal Decode4bitPixelCodeString(InBitStream input, MemoryStream output)
        {
            byte _4bitPixelCode = input.ReadBitsAsByte(4);
            if (_4bitPixelCode != 0)
            {
                _4bitPixelCode = input.ReadBitsAsByte(4);
                output.WriteByte(_4bitPixelCode);
            }
            else
            {
                bool switch1 = input.ReadBitAsBoolean();
                if (!switch1)
                {
                    byte runLength39  = input.ReadBitsAsByte(3);
                    if (runLength39 != 0)
                    {
                        runLength39 += 2;
                        output.WriteUInt8Repeat(0, runLength39);
                    }
                    else
                    {
                        return CodeStringSignal.EndOfString;
                    }
                }
                else
                {
                    bool switch2 = input.ReadBitAsBoolean();
                    if (!switch2)
                    {
                        byte runLength47 = input.ReadBitsAsByte(2);
                        runLength47 += 4;
                        _4bitPixelCode = input.ReadBitsAsByte(4);
                        output.WriteUInt8Repeat(_4bitPixelCode, runLength47);
                    }
                    else
                    {
                        byte switch3 = input.ReadBitsAsByte(2);
                        switch (switch3)
                        {
                            case 0b00:
                                output.WriteByte(0);
                                break;
                            case 0b01:
                                output.WriteByte(0);
                                output.WriteByte(0);
                                break;
                            case 0b10:
                                byte runLength9_24 = input.ReadBitsAsByte(4);
                                runLength9_24 += 9;
                                _4bitPixelCode = input.ReadBitsAsByte(4);
                                output.WriteUInt8Repeat(_4bitPixelCode, runLength9_24);
                                break;
                            case 0b11:
                                ushort runLength25_280 = input.ReadBitsAsByte(8);
                                runLength25_280 += 25;
                                _4bitPixelCode = input.ReadBitsAsByte(4);
                                output.WriteUInt8Repeat(_4bitPixelCode, runLength25_280);
                                break;
                        }
                    }
                }
            }
            return CodeStringSignal.Continue;
        }

#endif

        private ObjectDimensions ObjectDimensions;
        public bool NonModifyingColorFlag { get; private set; }

        public int CodingMethod { get; private set; }

        public int VersionNumber { get; private set; }

        public ushort Id { get; private set; }

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

    }
}