using GostCryptography.Properties;
using System;
using System.Collections;
using System.Text;

namespace GostCryptography.Asn1.Ber
{
    [Serializable]
    public class Asn1BitString : Asn1Type
    {
        public static readonly Asn1Tag Tag = new Asn1Tag(0, 0, BitStringTypeCode);

        [NonSerialized]
        public byte[] Value;

        [NonSerialized]
        public int NumBits;

        public Asn1BitString()
        {
            NumBits = 0;
            Value = null;
        }

        public Asn1BitString(bool[] bitValues)
        {
            AllocBitArray(bitValues.Length);

            var index = 0;
            var num4 = 0x80;
            var num = 0;
            var num2 = 0;

            while (num < bitValues.Length)
            {
                if (bitValues[num])
                {
                    num2 |= num4;
                }

                num4 = num4 >> 1;

                if (num4 == 0)
                {
                    Value[index++] = (byte)num2;
                    num4 = 0x80;
                    num2 = 0;
                }

                num++;
            }

            if (num4 != 0x80)
            {
                Value[index] = (byte)num2;
            }
        }

        public Asn1BitString(BitArray bitArray)
        {
            AllocBitArray(bitArray.Length);

            var index = 0;
            var num4 = 0x80;
            var num = 0;
            var num2 = 0;

            while (num < bitArray.Length)
            {
                if (bitArray.Get(num))
                {
                    num2 |= num4;
                }

                num4 = num4 >> 1;

                if (num4 == 0)
                {
                    Value[index++] = (byte)num2;
                    num4 = 0x80;
                    num2 = 0;
                }

                num++;
            }

            if (num4 != 0x80)
            {
                Value[index] = (byte)num2;
            }
        }

        public Asn1BitString(string value)
        {
            var numbits = new IntHolder();
            Value = Asn1Value.ParseString(value, numbits);

            NumBits = numbits.Value;
        }

        public Asn1BitString(int numBits, byte[] data)
        {
            NumBits = numBits;
            Value = data;
        }

        private void AllocBitArray(int numbits)
        {
            NumBits = numbits;

            var num = (NumBits + 7) / 8;

            if ((Value == null) || (Value.Length < num))
            {
                Value = new byte[num];
            }
        }

        public virtual void Clear(int bitIndex)
        {
            this[bitIndex] = false;
        }

        public override void Decode(Asn1BerDecodeBuffer buffer, bool explicitTagging, int implicitLength)
        {
            var elemLength = explicitTagging ? MatchTag(buffer, Tag) : implicitLength;
            var lastTag = buffer.LastTag;

            if ((lastTag == null) || !lastTag.Constructed)
            {
                if (elemLength < 0)
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidLengthException);
                }

                if (elemLength != 0)
                {
                    var num8 = elemLength - 1;
                    var num7 = buffer.Read();

                    if (num7 < 0)
                    {
                        throw ExceptionUtility.CryptographicException(Resources.Asn1EndOfBufferException, buffer.ByteCount);
                    }

                    if ((num7 < 0) || (num7 > 7))
                    {
                        throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidFormatOfBitString, num7);
                    }

                    if ((num8 == 0) && (num7 != 0))
                    {
                        throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidLengthException);
                    }

                    NumBits = (num8 * 8) - num7;
                    Value = new byte[num8];
                    buffer.Read(Value);
                }
                else
                {
                    NumBits = 0;
                    Value = null;
                }
            }
            else
            {
                var num3 = 0;
                var offset = 0;
                var index = -1;
                var num6 = 0;

                var context = new Asn1BerDecodeContext(buffer, elemLength);

                while (!context.Expired())
                {
                    var nbytes = MatchTag(buffer, Tag);

                    if (nbytes <= 0)
                    {
                        throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidFormatOfConstructedValue, buffer.ByteCount);
                    }

                    num3 += nbytes;

                    if (offset == 0)
                    {
                        AllocBitArray(num3 * 8);
                    }
                    else
                    {
                        ReallocBitArray(num3 * 8);
                    }

                    index = offset;
                    buffer.Read(Value, offset, nbytes);
                    offset = num3;
                }

                if (index >= 0)
                {
                    num6 = Value[index];

                    if (((offset - index) - 1) > 0)
                    {
                        Array.Copy(Value, index + 1, Value, index, (offset - index) - 1);
                    }

                    num3--;
                }

                if ((num6 < 0) || (num6 > 7))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidFormatOfBitString, num6);
                }

                ReallocBitArray((num3 * 8) - num6);

                if (elemLength == Asn1Status.IndefiniteLength)
                {
                    MatchTag(buffer, Asn1Tag.Eoc);
                }
            }

            buffer.TypeCode = 3;
        }

        public override int Encode(Asn1BerEncodeBuffer buffer, bool explicitTagging)
        {
            var length = (NumBits + 7) / 8;
            var num2 = NumBits % 8;

            if (num2 != 0)
            {
                num2 = 8 - num2;
                Value[length - 1] = (byte)(Value[length - 1] & ((byte)~((1 << num2) - 1)));
            }

            if (length != 0)
            {
                buffer.Copy(Value, 0, length);
            }

            buffer.Copy((byte)num2);
            length++;

            if (explicitTagging)
            {
                length += buffer.EncodeTagAndLength(Tag, length);
            }

            return length;
        }

        public override void Encode(Asn1BerOutputStream outs, bool explicitTagging)
        {
            outs.EncodeBitString(Value, NumBits, explicitTagging, Tag);
        }

        public override bool Equals(object value)
        {
            var str = value as Asn1BitString;

            if (str == null)
            {
                return false;
            }

            return Equals(str.NumBits, str.Value);
        }

        public virtual bool Equals(int nbits, byte[] value)
        {
            if (nbits != NumBits)
            {
                return false;
            }

            var num = ((nbits - 1) / 8) + 1;

            for (var i = 0; i < num; ++i)
            {
                if (value[i] != Value[i])
                {
                    return false;
                }
            }

            return true;
        }

        public virtual bool Get(int bitno)
        {
            var index = bitno / 8;
            var num2 = 1 << (7 - (bitno % 8));

            if ((Value != null) && (Value.Length >= index))
            {
                int num3 = Value[index];
                return ((num3 & num2) != 0);
            }

            return false;
        }

        public override int GetHashCode()
        {
            return (Value != null) ? Value.GetHashCode() : base.GetHashCode();
        }

        private void ReallocBitArray(int numbits)
        {
            NumBits = numbits;
            var num = (NumBits + 7) / 8;

            if (Value.Length != num)
            {
                var value = Value;
                Value = new byte[num];

                if (value != null)
                {
                    Array.Copy(value, 0, Value, 0, Math.Min(value.Length, num));
                }
            }
        }

        public virtual void Set(int bitIndex)
        {
            Set(bitIndex, true);
        }

        public virtual void Set(int bitIndex, bool value)
        {
            var index = bitIndex / 8;
            var num2 = 1 << (7 - (bitIndex % 8));
            var num3 = index + 1;

            if (Value == null)
            {
                Value = new byte[num3];
            }
            else if (Value.Length < num3)
            {
                var destinationArray = new byte[num3];
                Array.Copy(Value, 0, destinationArray, 0, Value.Length);
                Value = destinationArray;
            }

            int num4 = Value[index];
            num4 = value ? (num4 | num2) : (num4 & ~num2);
            Value[index] = (byte)num4;

            if ((bitIndex + 1) > NumBits)
            {
                NumBits = bitIndex + 1;
            }
        }

        public virtual bool[] ToBoolArray()
        {
            var flagArray = new bool[NumBits];

            var num4 = 0;
            var numbits = NumBits;

            foreach (var num3 in Value)
            {
                var num5 = 0x80;
                var num = (numbits < 8) ? numbits : 8;

                for (var j = 0; j < num; ++j)
                {
                    flagArray[num4++] = (num3 & num5) != 0;
                    num5 = num5 >> 1;
                }

                numbits -= 8;
            }

            return flagArray;
        }

        public virtual string ToHexString()
        {
            var str = new StringBuilder("").ToString();

            foreach (var b in Value)
            {
                str = str + Asn1Util.ToHexString(b);
            }

            return str;
        }

        public override string ToString()
        {
            var str = new StringBuilder("").ToString();

            if (NumBits <= 0x10)
            {
                if (NumBits != 0)
                {
                    var flagArray = ToBoolArray();

                    foreach (bool b in flagArray)
                    {
                        str = str + (b ? "1" : "0");
                    }
                }

                return str;
            }

            var num2 = 4;
            var capacity = (NumBits + 3) / 4;
            var builder = new StringBuilder(capacity);

            if (Value != null)
            {
                var num4 = 0;
                var index = 0;

                while (num4 < capacity)
                {
                    var num6 = (Value[index] >> num2) & 15;
                    builder.Append((char)(num6 + ((num6 >= 10) ? 0x57 : 0x30)));
                    num2 -= 4;

                    if (num2 < 0)
                    {
                        num2 = 4;
                        index++;
                    }

                    num4++;
                }
            }

            return builder.ToString();
        }

        public virtual bool this[int bitIndex]
        {
            get { return Get(bitIndex); }
            set { Set(bitIndex, value); }
        }

        public override int Length
        {
            get { return NumBits; }
        }
    }
}
