using System;
using Microsoft.SPOT;

namespace CPF
{
    public class ByteString
    {
        public const int DefaultCapacity = 512;
        protected byte[] _internalBuffer;
        protected int _head;
        protected int _tail;

        private static bool strcmp(ByteString lhs, ByteString rhs)
        {
            if (lhs.Length == rhs.Length)
            {
                int pos = 0;
                while (lhs._internalBuffer[pos] == rhs._internalBuffer[pos] && pos < lhs.Length)
                    pos++;
                return (pos == lhs.Length) ? true : false;
            }
            else
                return false;
        }

        public class NotEnoughCapacity : Exception
        {
            public NotEnoughCapacity()
                : base()
            {
            }
        }

        public class IndexOutOfRange : Exception
        {
            public IndexOutOfRange()
                : base()
            {
            }
        }

        public int Length
        {
            get
            {
                return _tail - _head;
            }
        }

        public int Available
        {
            get
            {
                return _internalBuffer.Length - Length;
            }
        }

        public int Capacity { get; private set; }

        public ByteString Clear()
        {
            _head = 0;
            _tail = 0;
            _internalBuffer = new byte[DefaultCapacity];
            return this;
        }

        public ByteString()
        {
            Clear();
        }

        public ByteString(byte ch)
        {
            Clear();
            _internalBuffer[_tail++] = ch;
        }

        public ByteString(byte[] bytes)
        {
            Clear();
            CheckCapacity(bytes.Length);
            for (int pos = 0; _tail < bytes.Length; )
                _internalBuffer[_tail++] = (byte)bytes[pos++];
        }

        public ByteString(string s)
        {
            Clear();
            CheckCapacity(s.Length);
            char[] chars = s.ToCharArray();
            for (int pos = 0; _tail < s.Length; )
                _internalBuffer[_tail++] = (byte)chars[pos++];
        }

        public byte this[int i]
        {
            get
            {
                if (i < 0 || i > Length)
                    throw new IndexOutOfRange();
                return _internalBuffer[i];
            }
            set
            {
                if (i < 0 || i > Length)
                    throw new IndexOutOfRange();
                _internalBuffer[i] = value;
            }
        }

        public byte[] AddressOf()
        {
            return _internalBuffer;
        }

        public byte Deref()
        {
            if (_head < 0 || _head > _tail)
                throw new IndexOutOfRange();
            return _internalBuffer[_head];
        }

        public byte DerefPostInc()
        {
            if (_head < 0 || _head > _tail)
                throw new IndexOutOfRange();
            return _internalBuffer[_head++];
        }

        public byte DerefPreInc()
        {
            if (_head < 0 || _head > _tail)
                throw new IndexOutOfRange();
            return _internalBuffer[++_head];
        }

        public void Advance(int amount)
        {
            _head += amount;
        }

        public static bool operator ==(ByteString lhs, ByteString rhs)
        {
            return strcmp(lhs, rhs);
        }

        public static bool operator !=(ByteString lhs, ByteString rhs)
        {
            return !strcmp(lhs, rhs);
        }

        public override bool Equals(object obj)
        {
            if (obj is ByteString)
                return this == (ByteString)obj;
            return false;
        }

        public override int GetHashCode()
        {
            return GetHashCode();
            // unique hash for each area
        }

        public ByteString Append(string s)
        {
            CheckCapacity(s.Length);
            char[] chars = s.ToCharArray();
            foreach (char t in chars)
                _internalBuffer[_tail++] = (byte)t;
            return this;
        }

        public ByteString Append(byte[] bytes, int length)
        {
            //check if space is available for additional data
            CheckCapacity(length);
            for (int i = 0; i < length; i++)
                _internalBuffer[_tail++] = bytes[i];
            return this;
        }

        public ByteString Append(byte ch)
        {
            //check if space is available for additional data
            CheckCapacity(1);
            _internalBuffer[_tail++] = ch;
            return this;
        }

        public ByteString Append(char ch)
        {
            //check if space is available for additional data
            CheckCapacity(1);
            _internalBuffer[_tail++] = (byte)ch;
            return this;
        }

        public ByteString Append(ByteString s)
        {
            CheckCapacity(s.Length);
            for (int i = 0; i < s.Length; i++)
                this._internalBuffer[_tail++] = s._internalBuffer[i];
            return this;
        }

        public byte[] GetByteArray()
        {
            return _internalBuffer;
        }

        public override string ToString()
        {
            char[] chars = new char[Length];
            for (int pos = 0; pos < Length; pos++)
                chars[pos] = (char)_internalBuffer[_head + pos];
            return new string(chars);
        }

        public int Begin()
        {
            return _head;
        }

        public int End()
        {
            return _tail;
        }

        private void InternalExpandBuffer()
        {
            //double capacity by default
            Capacity *= 2;

            //copy to new array
            var tmpBuffer = new byte[Capacity];
            for (int i = 0; i < _internalBuffer.Length; i++)
            {
                byte c = _internalBuffer[i];
                tmpBuffer[i] = c;
            }
            _internalBuffer = tmpBuffer;
        }

        private void CheckCapacity(int additionalLength)
        {
            if (Length + additionalLength > DefaultCapacity)  // Allow room for null terminator
                throw new NotEnoughCapacity();
        }

    }
}
