using System;
using System.IO;
using System.Text;

namespace CapnProto.net
{
    class Textizer : IRecyclable, IDisposable
    {
        static Textizer Create()
        {
            return Cache<Textizer>.Pop() ?? new Textizer();
        }

        void IDisposable.Dispose()
        {
            Cache<Textizer>.Push(this);
        }

        internal static readonly Encoding Encoding = new UTF8Encoding(false);

        void IRecyclable.Reset(bool reusing)
        {
            if (!reusing) return;

            _encoder?.Reset();
            _decoder?.Reset();
        }

        readonly char[] _chars;
        readonly byte[] _bytes;

        Encoder _encoder;
        Encoder Encoder => _encoder ?? (_encoder = Encoding.GetEncoder());
        Decoder _decoder;
        Decoder Decoder => _decoder ?? (_decoder = Encoding.GetDecoder());

        public Textizer()
        {
            _chars = new char[CharLength];
            _bytes = new byte[ByteLength];
        }
        const int CharLength = 512, MaxBytesToDecode = CharLength, MaxCharsToEncode = CharLength;
        static readonly int ByteLength = Encoding.GetMaxByteCount(CharLength) + 8; // leave space for the leftovers


        public static int AppendTo(Pointer pointer, TextWriter destination)
        {
            pointer = pointer.Dereference();
            if (!pointer.IsValid) return 0;
            var len = pointer.SingleByteLength;
            if (--len <= 0 || pointer.GetListByte(len) != 0) return 0;
            using (var text = Create())
            {
                var decoder = text.Decoder;
                var bytes = text._bytes;
                var chars = text._chars;
                int wordOffset = 0, totalChars = 0;
                do
                {
                    var bytesThisPass = Math.Min(len, MaxBytesToDecode);
                    var wordsThisPass = ((bytesThisPass - 1) >> 3) + 1;
                    pointer.ReadWords(wordOffset, bytes, 0, wordsThisPass);
                    var charCount = decoder.GetChars(bytes, 0, bytesThisPass, chars, 0, false);
                    totalChars += charCount;
                    wordOffset += wordsThisPass;
                    len -= bytesThisPass;
                    if (charCount != 0) destination.Write(chars, 0, charCount);
                } while (len > 0);
                return totalChars;
            }
        }
        public static int Count(Pointer pointer)
        {
            pointer = pointer.Dereference();
            if (!pointer.IsValid) return 0;
            var len = pointer.SingleByteLength;
            if (--len <= 0 || pointer.GetListByte(len) != 0) return 0;
            using (var text = Create())
            {
                var decoder = text.Decoder;
                var bytes = text._bytes;
                int wordOffset = 0, totalChars = 0;
                do
                {
                    var bytesThisPass = Math.Min(len, MaxBytesToDecode);
                    var wordsThisPass = ((bytesThisPass - 1) >> 3) + 1;
                    pointer.ReadWords(wordOffset, bytes, 0, wordsThisPass);
                    var charCount = decoder.GetCharCount(bytes, 0, bytesThisPass, false);
                    totalChars += charCount;
                    wordOffset += wordsThisPass;
                    len -= bytesThisPass;
                } while (len > 0);
                return totalChars;
            }
        }
        public static int AppendTo(Pointer pointer, StringBuilder destination)
        {
            pointer = pointer.Dereference();
            if (!pointer.IsValid) return 0;
            var len = pointer.SingleByteLength;
            if (--len <= 0 || pointer.GetListByte(len) != 0) return 0;
            using (var text = Create())
            {
                var decoder = text.Decoder;
                var bytes = text._bytes;
                var chars = text._chars;
                int wordOffset = 0, totalChars = 0;
                do
                {
                    var bytesThisPass = Math.Min(len, MaxBytesToDecode);
                    var wordsThisPass = ((bytesThisPass - 1) >> 3) + 1;
                    pointer.ReadWords(wordOffset, bytes, 0, wordsThisPass);
                    var charCount = decoder.GetChars(bytes, 0, bytesThisPass, chars, 0, false);
                    totalChars += charCount;
                    wordOffset += wordsThisPass;
                    len -= bytesThisPass;
                    if (charCount != 0) destination.Append(chars, 0, charCount);
                } while (len > 0);
                return totalChars;
            }
        }

        internal static void Write(Pointer pointer, char[] value, int offset, int count)
        {
            pointer = pointer.Dereference();
            if (!pointer.IsValid) throw new InvalidOperationException();
            var len = pointer.SingleByteLength;
            if(--len == 0) throw new InvalidOperationException();
            if (len == 0)
            {   // empty string
                pointer.SetListByte(len, 0);
                return;
            }

            
            using (var text = Create())
            {
                var encoder = text.Encoder;                    
                var bytes = text._bytes;
                int byteOffset = 0, wordOffset = 0, totalBytes = 0;
                do
                {
                    var charsThisPass = Math.Min(count, MaxCharsToEncode);
                    var newBytes = encoder.GetBytes(value, offset, charsThisPass, bytes, byteOffset, false);

                    // note: we could have some complete words, and some partial words; need to be careful

                    var availableBytes = newBytes + byteOffset;
                    var fullWords = availableBytes >> 3;
                    if(fullWords != 0) pointer.WriteWords(wordOffset, bytes, 0, fullWords);
                    totalBytes += newBytes;
                    wordOffset += fullWords;
                    byteOffset = availableBytes & 7;
                    if(byteOffset != 0)
                    {
                        // copy down those spare bytes
                        Buffer.BlockCopy(bytes, fullWords << 3, bytes, 0, byteOffset);
                    }
                    offset += charsThisPass;
                    count -= charsThisPass;
                } while (count > 0);

                // copy in any spare trailing bytes
                var origin = wordOffset << 3;
                for (var i = 0; i < byteOffset;i++ )
                {
                    pointer.SetListByte(origin++, bytes[i]);
                }
                pointer.SetListByte(len, 0);

                if (totalBytes != len) throw new InvalidOperationException("String encoded length mismatch");
            }
            
        }

        internal static Text Create(Pointer pointer, System.Data.IDataRecord reader, int fieldIndex)
        {
            // TODO: replace with GetChars work

            return Text.Create(pointer, reader.GetString(fieldIndex));
        }
    }

}
