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

namespace GostCryptography.Asn1.Ber
{
    public class Asn1BerEncodeBuffer : Asn1EncodeBuffer
    {
        public Asn1BerEncodeBuffer()
        {
            ByteIndex = SizeIncrement - 1;
        }

        public Asn1BerEncodeBuffer(int sizeIncrement)
            : base(sizeIncrement)
        {
            ByteIndex = SizeIncrement - 1;
        }

        public virtual MemoryStream ByteArrayInputStream
        {
            get
            {
                var index = ByteIndex + 1;
                return new MemoryStream(Data, index, Data.Length - index);
            }
        }

        public override byte[] MsgCopy
        {
            get
            {
                var sourceIndex = ByteIndex + 1;
                var length = Data.Length - sourceIndex;
                var destinationArray = new byte[length];

                Array.Copy(Data, sourceIndex, destinationArray, 0, length);

                return destinationArray;
            }
        }

        public override int MsgLength
        {
            get
            {
                var num = ByteIndex + 1;
                return (Data.Length - num);
            }
        }

        public virtual void BinDump()
        {
            BinDump(null);
        }

        public override void BinDump(StreamWriter outs, string varName)
        {
            var buffer = new Asn1BerDecodeBuffer(ByteArrayInputStream);

            try
            {
                buffer.Parse(new Asn1BerMessageDumpHandler(outs));
            }
            catch (Exception exception)
            {
                Console.Out.WriteLine(exception.Message);
                Console.Error.Write(exception.StackTrace);
                Console.Error.Flush();
            }
        }

        protected internal override void CheckSize(int bytesRequired)
        {
            if (bytesRequired > (ByteIndex + 1))
            {
                var num = ((bytesRequired - 1) / SizeIncrement) + 1;
                var num2 = num * SizeIncrement;
                var destinationArray = new byte[Data.Length + num2];
                var destinationIndex = (ByteIndex + num2) + 1;
                var length = Data.Length - (ByteIndex + 1);

                Array.Copy(Data, ByteIndex + 1, destinationArray, destinationIndex, length);

                Data = destinationArray;
                ByteIndex = destinationIndex - 1;
            }
        }

        public override void Copy(byte data)
        {
            if (ByteIndex < 0)
            {
                CheckSize(1);
            }

            Data[ByteIndex--] = data;
        }

        public override void Copy(byte[] data)
        {
            CheckSize(data.Length);
            ByteIndex -= data.Length;

            Array.Copy(data, 0, Data, ByteIndex + 1, data.Length);
        }

        public virtual void Copy(string data)
        {
            var length = data.Length;
            CheckSize(length);
            ByteIndex -= length;

            for (var i = 0; i < length; ++i)
            {
                Data[(ByteIndex + i) + 1] = (byte)data[i];
            }
        }

        public virtual void Copy(byte[] data, int startOffset, int length)
        {
            CheckSize(length);
            ByteIndex -= length;

            Array.Copy(data, startOffset, Data, ByteIndex + 1, length);
        }

        public virtual int EncodeIdentifier(int ident)
        {
            var flag = true;
            var num = 0;
            var num2 = ident;

            do
            {
                if (ByteIndex < 0)
                {
                    CheckSize(1);
                }

                Data[ByteIndex] = (byte)(num2 % 0x80);

                if (!flag)
                {
                    Data[ByteIndex] = (byte)(Data[ByteIndex] | 0x80);
                }
                else
                {
                    flag = false;
                }

                ByteIndex--;
                num2 /= 0x80;
                num++;

            }
            while (num2 > 0);

            return num;
        }

        public virtual int EncodeIntValue(long ivalue)
        {
            long num2;
            long num = ivalue;

            var num3 = 0;

            do
            {
                num2 = num % 0x100L;
                num /= 0x100L;

                if ((num < 0L) && (num2 != 0L))
                {
                    num -= 1L;
                }

                Copy((byte)num2);

                num3++;
            }
            while ((num != 0L) && (num != -1L));

            if ((ivalue > 0L) && ((num2 & 0x80L) == 0x80L))
            {
                Copy(0);
                num3++;
                return num3;
            }

            if ((ivalue < 0L) && ((num2 & 0x80L) == 0L))
            {
                Copy(0xff);
                num3++;
            }

            return num3;
        }

        public virtual int EncodeLength(int len)
        {
            var num = 0;

            bool flag;

            if (len >= 0)
            {
                flag = len > 0x7f;

                var num2 = len;

                do
                {
                    if (ByteIndex < 0)
                    {
                        CheckSize(1);
                    }

                    Data[ByteIndex--] = (byte)(num2 % 0x100);
                    num++;
                    num2 /= 0x100;
                }
                while (num2 > 0);
            }
            else
            {
                flag = len == Asn1Status.IndefiniteLength;
            }

            if (flag)
            {
                if (ByteIndex < 0)
                {
                    CheckSize(1);
                }

                Data[ByteIndex--] = (byte)(num | 0x80);
                num++;
            }

            return num;
        }

        public virtual int EncodeTag(Asn1Tag tag)
        {
            var num = (byte)(((byte)tag.Class) | ((byte)tag.Form));
            var num2 = 0;

            if (tag.IdCode < 0x1f)
            {
                Copy((byte)(num | tag.IdCode));
                num2++;
                return num2;
            }

            num2 += EncodeIdentifier(tag.IdCode);
            Copy((byte)(num | 0x1f));
            num2++;

            return num2;
        }

        public virtual int EncodeTagAndLength(Asn1Tag tag, int len)
        {
            return (EncodeLength(len) + EncodeTag(tag));
        }

        public virtual int EncodeTagAndLength(short tagClass, short tagForm, int tagIdCode, int len)
        {
            var tag = new Asn1Tag(tagClass, tagForm, tagIdCode);
            return EncodeTagAndLength(tag, len);
        }

        public override Stream GetInputStream()
        {
            return ByteArrayInputStream;
        }

        public override void Reset()
        {
            ByteIndex = Data.Length - 1;
        }

        public override string ToString()
        {
            var num = ByteIndex + 1;
            var num2 = Data.Length - num;
            var str = new StringBuilder("").ToString();

            for (var i = 0; i < num2; ++i)
            {
                str = str + Asn1Util.ToHexString(Data[i + num]);
            }

            return str;
        }

        public override void Write(Stream outs)
        {
            var offset = ByteIndex + 1;
            outs.Write(Data, offset, Data.Length - offset);
        }
    }
}
