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

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

        public Asn1UtcTime()
            : base(UtcTimeTypeCode, false)
        {
        }

        public Asn1UtcTime(bool useDerRules)
            : base(UtcTimeTypeCode, useDerRules)
        {
        }

        public Asn1UtcTime(string data)
            : base(data, UtcTimeTypeCode, false)
        {
        }

        public Asn1UtcTime(string data, bool useDerRules)
            : base(data, UtcTimeTypeCode, useDerRules)
        {
        }

        public override string Fraction
        {
            get
            {
                return "";
            }
            set
            {
                SecFraction = "";

                throw ExceptionUtility.CryptographicException(Resources.Asn1FractionNotSupportedForUtcTime);
            }
        }

        public override int Year
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }

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

                if (value < 100)
                {
                    if (value >= 50)
                    {
                        Year = value + 0x76c;
                    }
                    else
                    {
                        Year = value + 0x7d0;
                    }
                }

                Year = value;
            }
        }

        public override void Clear()
        {
            Clear();
            HourValue = MinuteValue = -1;
            UtcFlag = true;
        }

        public override int CompareTo(object obj)
        {
            return base.CompareTo(obj);
        }

        protected override bool CompileString()
        {
            Value = "";

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

            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(2, time.Year);
                PutInteger(2, time.Month);
                PutInteger(2, time.Day);
                PutInteger(2, time.Hour);
                PutInteger(2, time.Minute);
            }
            else
            {
                PutInteger(2, YearValue);
                PutInteger(2, MonthValue);
                PutInteger(2, DayValue);
                PutInteger(2, HourValue);
                PutInteger(2, MinuteValue);
            }

            PutInteger(2, SecondValue);

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

            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);
        }

        protected override void Init()
        {
            Init();
            HourValue = MinuteValue = -1;
            UtcFlag = true;
        }

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

            Clear();

            var off = new IntHolder(0);

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

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

                if (YearValue < 100)
                {
                    if (YearValue > 70)
                    {
                        YearValue += 0x76c;
                    }
                    else
                    {
                        YearValue += 0x7d0;
                    }
                }

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

                var 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)))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1MinutesExpected);
                }

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

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

                if ((num2 >= 2) && ((HourValue < 0) || (HourValue > UtcTimeTypeCode)))
                {
                    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);
                }

                CharAt(data, off.Value);

                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 ch = CharAt(data, off.Value);

                    switch (ch)
                    {
                        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)))
                            {
                                throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDiffMinute);
                            }

                            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 (ch == '-')
                            {
                                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);
            }
        }

        public override void SetTime(DateTime time)
        {
            Clear();
            YearValue = time.Year;
            MonthValue = time.Month;
            DayValue = time.Day;
            HourValue = time.Hour;
            MinuteValue = time.Minute;
            SecondValue = time.Second;
            SecFraction = "";
            DiffHourValue = DiffMinValue = 0;
            UtcFlag = true;
            CompileString();
        }
    }
}
