using System;
using System.Diagnostics;
using System.Text;
using skyscraper5.Dvb;
using skyscraper5.Skyscraper.Plugins;
using skyscraper8.Skyscraper.Text;

namespace skyscraper5.Skyscraper.Text
{
    public class En300468AnnexATextDecoder
    {
        private En300468AnnexATextDecoder()
        {
            Encoding.RegisterProvider(SkyscraperEncodingProvider.GetInstance());
            
            tables = new Encoding[0x20];
            tables[0x00] = Encoding.GetEncoding("dvb-iso-6937");                //ISO 6397
            tables[0x01] = Encoding.GetEncoding("dvb-iso-8859-5");              //iso 8859-5
            tables[0x02] = Encoding.GetEncoding("dvb-iso-8859-6");              //iso 8859-6
            tables[0x03] = Encoding.GetEncoding("dvb-iso-8859-7");              //iso 8859-7
            tables[0x04] = Encoding.GetEncoding("dvb-iso-8859-8");              //iso 8859-8
            tables[0x05] = Encoding.GetEncoding("dvb-iso-8859-9");              //iso 8859-9
            tables[0x06] = Encoding.GetEncoding("dvb-iso-8859-10");             //ISO/IEC 8859-10 
            tables[0x07] = Encoding.GetEncoding("dvb-iso-8859-11");             //ISO/IEC 8859-11 
            tables[0x09] = Encoding.GetEncoding("dvb-iso-8859-13");             //ISO/IEC 8859-13
            tables[0x0a] = Encoding.GetEncoding("dvb-iso-8859-14");             //ISO/IEC 8859-14
            tables[0x0b] = Encoding.GetEncoding("dvb-iso-8859-15");             //iso 8859-15
            tables[0x11] = Encoding.GetEncoding("dvb-iso-10646");               //iso 10646
            tables[0x12] = Encoding.GetEncoding("dvb-ksx1001-2004");            //KSX1001-2004
            tables[0x13] = Encoding.GetEncoding("dvb-gb-2312-1980");            //gb-2312
            tables[0x14] = Encoding.GetEncoding("dvb-big5");                    //Big5 subset of ISO/IEC 10646 
            tables[0x15] = Encoding.GetEncoding("dvb-utf8");                    //UTF-8 encoding of ISO/IEC 10646 

            iso8859Mapping = new Encoding[0x10];
            iso8859Mapping[0x01] = Encoding.GetEncoding("dvb-iso-8859-1");      //iso 8859-1
            iso8859Mapping[0x02] = Encoding.GetEncoding("dvb-iso-8859-2");      //iso 8859-2
            iso8859Mapping[0x03] = Encoding.GetEncoding("dvb-iso-8859-3");      //iso 8859-3
            iso8859Mapping[0x04] = Encoding.GetEncoding("dvb-iso-8859-4");      //iso 8859-4
            iso8859Mapping[0x05] = Encoding.GetEncoding("dvb-iso-8859-5");      //iso 8859-5
            iso8859Mapping[0x06] = Encoding.GetEncoding("dvb-iso-8859-6");      //iso 8859-6
            iso8859Mapping[0x07] = Encoding.GetEncoding("dvb-iso-8859-7");      //iso 8859-7
            iso8859Mapping[0x08] = Encoding.GetEncoding("dvb-iso-8859-8");      //iso 8859-8
            iso8859Mapping[0x09] = Encoding.GetEncoding("dvb-iso-8859-9");      //iso 8859-9
            iso8859Mapping[0x0a] = Encoding.GetEncoding("dvb-iso-8859-10");     //ISO/IEC 8859-10 
            iso8859Mapping[0x0b] = Encoding.GetEncoding("dvb-iso-8859-11");     //ISO/IEC 8859-11 
            iso8859Mapping[0x0c] = Encoding.GetEncoding("dvb-iso-8859-12");     //ISO/IEC 8859-12 
            iso8859Mapping[0x0d] = Encoding.GetEncoding("dvb-iso-8859-13");     //ISO/IEC 8859-13
            iso8859Mapping[0x0e] = Encoding.GetEncoding("dvb-iso-8859-14");     //ISO/IEC 8859-14
            iso8859Mapping[0x0f] = Encoding.GetEncoding("dvb-iso-8859-15");     //iso 8859-15

        }

        private static En300468AnnexATextDecoder _instance;
        private static Encoding[] tables;
        private static Encoding[] iso8859Mapping;
        private static TextDecoder[] _textDecoders;

        public static En300468AnnexATextDecoder GetInstance()
        {
            if (_instance == null)
                _instance = new En300468AnnexATextDecoder();
            return _instance;
        }

        public string Decode(byte[] buffer, int offset, int length)
        {
            byte[] copy = new byte[length];
            int endOffset = offset + length;
            if (endOffset > buffer.Length)
                return null;
            Array.Copy(buffer, offset, copy, 0, length);
            return Decode(copy);
        }

        private Encoding lastUsedEncoding;
        public string Decode(byte[] buffer)
        {
            if (buffer.Length == 0)
                return null;
            if (buffer.Length == 1)
                return null;
            
            if (buffer[0] >= 0x20)
            {
                lastUsedEncoding = tables[0];
                return tables[0].GetString(buffer, 0, buffer.Length);
            }
            else if ((buffer[0] >= 0x01 && buffer[0] <= 0x07) || (buffer[0] >= 0x09 && buffer[0] <= 0x0b) || (buffer[0] >= 0x11 && buffer[0] <= 0x14))
            {
                lastUsedEncoding = tables[buffer[0]];
                return tables[buffer[0]].GetString(buffer, 1, buffer.Length - 1);
            }
            else if (buffer[0] == 0x10)
            {
                if (buffer.Length == 2)
                    return null;
                ushort sb = buffer[1];
                ushort tb = buffer[2];
                if (sb != 0x00)
                {
                    Debug.WriteLine(String.Format("Found invalid character coding: 0x{0:X2}{1:X2}", buffer[0], sb));
                    return null;
                }
                if (tb >= 0x10)
                    throw new NotImplementedException("reserved");
                lastUsedEncoding = iso8859Mapping[tb];
                return iso8859Mapping[tb].GetString(buffer, 3, buffer.Length - 3);
            }
            else if (buffer[0] == 0x15)
            {
                lastUsedEncoding = tables[0x15];
                return tables[0x15].GetString(buffer, 1, buffer.Length - 1);
            }
            else if (buffer[0] == 0x1f)
            {
                //It shall be coded according to ETSI TS 101 162
                if (_textDecoders == null)
	                _textDecoders = PluginManager.GetInstance().GetTextDecoders();
                TextDecoder matchingTextDecoder = _textDecoders[buffer[1]];
                if (matchingTextDecoder == null)
                { 
                    //TODO: Add logger message here.
                    return null;
                }
                return matchingTextDecoder.Decode(buffer);
            }
            else if (buffer[0] >= 0x16 && buffer[0] <= 0x1e)
            {
                return null;    //ETSI EN 300468, Table A.3 "reserved for future use", therefore invalid.
            }

            if (lastUsedEncoding != null)
            {
                return lastUsedEncoding.GetString(buffer);
            }
            throw new DvbException("failed to detect encoding");
        }

        public byte[] Encode(string serviceName)
        {
	        byte[] utf8buffer = Encoding.UTF8.GetBytes(serviceName);

            byte[] result = new byte[utf8buffer.Length + 1];
            result[0] = 0x15;
            Array.Copy(utf8buffer, 0, result, 1, utf8buffer.Length);
            return result;
        }
    }
}
