using System;
using System.Text;

namespace skyscraper5.Skyscraper.Text
{
    abstract class SkyscraperBaseEncoding8 : Encoding
    {
        protected abstract char[] Decrypt(byte[] preprocessed);

        private char[] lastDecrypted;
        private byte[] lastBuffer;
        private bool IsSameStringAsLast(byte[] buffer, int index, int count)
        {
            if (lastBuffer == null)
            {
                //Dies ist der erste call.
                lastBuffer = new byte[count];
                Array.Copy(buffer, index, lastBuffer, 0, count);
                return false;
            }

            if (lastBuffer.Length != count)
            {
                //string länge anders als beim letzten mal, kann also nicht der selbe sein.
                lastBuffer = new byte[count];
                Array.Copy(buffer, index, lastBuffer, 0, count);
                return false;
            }

            for (int i = 0; i < count; i++)
            {
                if (lastBuffer[i] != buffer[i + index])
                {
                    //String anders als beim letzen mal
                    lastBuffer = new byte[count];
                    Array.Copy(buffer, index, lastBuffer, 0, count);
                    return false;
                }
            }
            return true;
        }

        protected virtual int GetPreProcessLength(byte[] buffer, int index, int count)
        {
            int result = 0;
            byte t;
            for (int i = 0; i < count; i++)
            {
                t = buffer[i + index];
                if (t >= 0x80 && t <= 0x85)
                    continue;       //reserved for future use
                if (t == 0x86)
                    continue;       //character emphasis on
                if (t == 0x87)
                    continue;       //character emphasis off
                if (t >= 0x88 && t <= 0x89)
                    continue;       //reserved for future use
                if (t == 0x8a)
                    result += 2;    //CRLF
                if (t >= 0x8b && t <= 0x9f)
                    continue;       //user defined
                result++;
            }
            return result;
        }

        protected virtual byte[] PreProcess(byte[] buffer, int index, int count, int predictedLength)
        {
            byte[] result = new byte[predictedLength];
            int outputOffset = 0;
            byte t;
            for (int inputOffset = 0; inputOffset < count; inputOffset++)
            {
                t = buffer[inputOffset + index];
                if (t >= 0x80 && t <= 0x85)
                    continue;       //reserved for future use
                if (t == 0x86)
                    continue;       //character emphasis on
                if (t == 0x87)
                    continue;       //character emphasis off
                if (t >= 0x88 && t <= 0x89)
                    continue;       //reserved for future use
                if (t == 0x8a)
                {
                    result[outputOffset++] = 0x10;
                    result[outputOffset++] = 0x13;
                    continue;
                }

                if (t >= 0x8b && t <= 0x9f)
                    continue;       //user defined

                result[outputOffset++] = t;
            }

            return result;
        }
        
        public override int GetByteCount(char[] chars, int index, int count)
        {
            throw new NotImplementedException();
        }

        public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
        {
            throw new NotImplementedException();
        }

        public override int GetCharCount(byte[] bytes, int index, int count)
        {
            if (!IsSameStringAsLast(bytes, index, count))
            {
                int preProcessLength = GetPreProcessLength(bytes, index, count);
                byte[] preProcess = PreProcess(bytes, index, count, preProcessLength);
                lastDecrypted = Decrypt(preProcess);
            }
            return lastDecrypted.Length;
        }

        private object getCharsLocker;
        public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
        {
            if (getCharsLocker == null)
                getCharsLocker = new object();

            lock (getCharsLocker)
            {
                if (!IsSameStringAsLast(bytes, byteIndex, byteCount))
                {
                    int preProcessLength = GetPreProcessLength(bytes, byteIndex, byteCount);
                    byte[] preProcess = PreProcess(bytes, byteIndex, byteCount, preProcessLength);
                    int maxAllowedSize = lastDecrypted.Length;
                    char[] overwriting = Decrypt(preProcess);
                    if (overwriting.Length > maxAllowedSize)
                    {
                        throw new AccessViolationException("Oh no, I guess the multithreading went haywire...");
                    }
                    lastDecrypted = overwriting;
                }

                int result = Math.Min(chars.Length - charIndex, lastDecrypted.Length);
                Array.Copy(lastDecrypted, 0, chars, charIndex, result);
                return result;
            }
        }

        public override int GetMaxByteCount(int charCount)
        {
            throw new NotImplementedException();
        }

        public override int GetMaxCharCount(int byteCount)
        {
            throw new NotImplementedException();
        }
    }
}
