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

namespace GostCryptography.Asn1.Ber
{
    [Serializable]
    public class Asn1GeneralizedTime : Asn1Time
    {
        public static readonly Asn1Tag Tag = new Asn1Tag(0, 0, GeneralTimeTypeCode);

        public Asn1GeneralizedTime()
            : base(GeneralTimeTypeCode, false)
        {
        }

        public Asn1GeneralizedTime(bool useDerRules)
            : base(GeneralTimeTypeCode, useDerRules)
        {
        }

        public Asn1GeneralizedTime(string data)
            : base(data, GeneralTimeTypeCode, false)
        {
        }

        public Asn1GeneralizedTime(string data, bool useDerRules)
            : base(data, GeneralTimeTypeCode, useDerRules)
        {
        }

        public virtual int Century
        {
            get
            {
                var yearValue = Year;

                if (yearValue < 0)
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidYearValue, yearValue);
                }

                return (yearValue / 100);
            }
            set
            {
                if ((value < 0) || (value > 0x63))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidCenturyValue, value);
                }

                SafeParseString();
                YearValue = (value * 100) + (YearValue % 100);
                CompileString();
            }
        }

        protected override bool CompileString()
        {
            int minuteValue;

            if (((YearValue < 0) || (MonthValue <= 0)) || ((DayValue <= 0) || (HourValue < 0)))
            {
                return false;
            }

            Value = "";

            if (StringBuffer == null)
            {
                StringBuffer = new StringBuilder();
            }
            else
            {
                StringBuffer.Length = 0;
            }

            if ((DerRules || UtcFlag) && ((DiffHourValue != 0) || (DiffMinValue != 0)))
            {
                var time = GetTime();
                time.AddMinutes(-DiffMinValue);
                time.AddHours(-DiffHourValue);

                PutInteger(4, time.Year);
                PutInteger(2, time.Month);
                PutInteger(2, time.Day);
                PutInteger(2, time.Hour);

                minuteValue = time.Minute;
            }
            else
            {
                PutInteger(4, YearValue);
                PutInteger(2, MonthValue);
                PutInteger(2, DayValue);
                PutInteger(2, HourValue);

                minuteValue = MinuteValue;
            }

            if ((DerRules || (minuteValue > 0)) || ((SecondValue > 0) || (SecFraction.Length > 0)))
            {
                PutInteger(2, minuteValue);

                if ((DerRules || (SecondValue > 0)) || (SecFraction.Length > 0))
                {
                    PutInteger(2, SecondValue);

                    if (SecFraction.Length > 0)
                    {
                        StringBuffer.Append('.');
                        StringBuffer.Append(SecFraction);
                    }
                }
            }

            if (DerRules || UtcFlag)
            {
                StringBuffer.Append('Z');
            }
            else if ((DiffHourValue != 0) || (DiffMinValue != 0))
            {
                StringBuffer.Append((DiffHourValue > 0) ? '+' : '-');

                if (DiffMinValue != 0)
                {
                    PutInteger(2, Math.Abs(DiffHourValue));
                    PutInteger(2, Math.Abs(DiffMinValue));
                }
                else
                {
                    PutInteger(2, Math.Abs(DiffHourValue));
                }
            }

            Value = StringBuffer.ToString();

            return true;
        }

        public override void Decode(Asn1BerDecodeBuffer buffer, bool explicitTagging, int implicitLength)
        {
            Decode(buffer, explicitTagging, implicitLength, Tag);
        }

        public override int Encode(Asn1BerEncodeBuffer buffer, bool explicitTagging)
        {
            return Encode(buffer, explicitTagging, Tag);
        }

        public override void Encode(Asn1BerOutputStream outs, bool explicitTagging)
        {
            Encode(outs, explicitTagging, Tag);
        }

        public override void ParseString(string data)
        {
            if (data == null)
            {
                throw ExceptionUtility.ArgumentNull("data");
            }

            Clear();

            var off = new IntHolder(0);

            try
            {
                YearValue = ParseInt(data, off, 4);
                MonthValue = ParseInt(data, off, 2);
                DayValue = ParseInt(data, off, 2);

                if (YearValue < 0)
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidYearValue, YearValue);
                }

                if ((MonthValue < 1) || (MonthValue > 12))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidMonthValue, MonthValue);
                }

                int num = DaysInMonth[MonthValue];

                if (((MonthValue == 2) && ((YearValue % 4) == 0)) && (((YearValue % 100) != 0) || ((YearValue % 400) == 0)))
                {
                    num++;
                }

                if ((DayValue < 1) || (DayValue > num))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDayValue, DayValue);
                }

                var num2 = 0;

                if (!char.IsDigit(CharAt(data, off.Value)))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1HoursExpected);
                }

                HourValue = ParseInt(data, off, 2);
                num2++;

                if (char.IsDigit(CharAt(data, off.Value)))
                {
                    MinuteValue = ParseInt(data, off, 2);
                    num2++;

                    if (char.IsDigit(CharAt(data, off.Value)))
                    {
                        SecondValue = ParseInt(data, off, 2);
                        num2++;
                    }
                }

                if ((num2 >= 1) && ((HourValue < 0) || (HourValue > 0x17)))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidHourValue, HourValue);
                }

                if ((num2 >= 2) && ((MinuteValue < 0) || (MinuteValue > 0x3b)))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidMinuteValue, MinuteValue);
                }

                if ((num2 == 3) && ((SecondValue < 0) || (SecondValue > 0x3b)))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidSecondValue, SecondValue);
                }

                var ch = CharAt(data, off.Value);

                if (DerRules && (ch == ','))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDecimalMark);
                }

                if ((ch == '.') || (ch == ','))
                {
                    off.Value++;

                    if (num2 != 3)
                    {
                        throw ExceptionUtility.CryptographicException(Resources.Asn1UnexpectedSymbol);
                    }

                    var length = 0;

                    while (char.IsDigit(CharAt(data, off.Value + length)))
                    {
                        length++;
                    }

                    if (length == 0)
                    {
                        throw ExceptionUtility.CryptographicException(Resources.Asn1UnexpectedSymbol);
                    }

                    SecFraction = data.Substring(off.Value, length);
                    off.Value += length;
                }

                if (CharAt(data, off.Value) == 'Z')
                {
                    off.Value++;
                    UtcFlag = true;

                    if (off.Value != data.Length)
                    {
                        throw ExceptionUtility.CryptographicException(Resources.Asn1UnexpectedValuesAtEndOfString);
                    }
                }
                else
                {
                    if (DerRules)
                    {
                        throw ExceptionUtility.CryptographicException(Resources.Asn1UnexpectedZoneOffset);
                    }

                    UtcFlag = false;

                    var ch2 = CharAt(data, off.Value);

                    switch (ch2)
                    {
                        case '-':
                        case '+':
                            off.Value++;

                            if (!char.IsDigit(CharAt(data, off.Value)))
                            {
                                throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDiffHour);
                            }

                            DiffHourValue = ParseInt(data, off, 2);

                            if (char.IsDigit(CharAt(data, off.Value)))
                            {
                                DiffMinValue = ParseInt(data, off, 2);
                            }

                            if ((DiffHourValue < 0) || (DiffHourValue > 12))
                            {
                                throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDiffHourValue, DiffHourValue);
                            }

                            if ((DiffMinValue < 0) || (DiffMinValue > 0x3b))
                            {
                                throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDiffMinuteValue, DiffMinValue);
                            }

                            if (ch2 == '-')
                            {
                                DiffHourValue = -DiffHourValue;
                                DiffMinValue = -DiffMinValue;
                            }
                            break;
                    }
                }

                Parsed = true;

                if (data != Value)
                {
                    CompileString();
                }
            }
            catch (IndexOutOfRangeException)
            {
                throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDateFormat);
            }
            catch (FormatException)
            {
                throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidNumberFormat);
            }
            catch (ArgumentException)
            {
                throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDateFormat);
            }
        }
    }
}
