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

namespace GostCryptography.Asn1.Ber
{
    [Serializable]
    public abstract class Asn1Time : Asn18BitCharString, IComparable
    {
        public const int January = 1;
        public const int February = 2;
        public const int March = 3;
        public const int April = 4;
        public const int May = 5;
        public const int June = 6;
        public const int July = 7;
        public const int August = 8;
        public const int September = 9;
        public const int October = 10;
        public const int November = 11;
        public const int December = 12;

        public static readonly short[] DaysInMonth = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

        public Asn1Time(short typeCode, bool useDerRules)
            : base(typeCode)
        {
            DerRules = useDerRules;
            Init();
        }

        public Asn1Time(string data, short typeCode, bool useDerRules)
            : base(data, typeCode)
        {
            DerRules = useDerRules;
            Init();
        }

        [NonSerialized]
        protected bool Parsed;

        [NonSerialized]
        protected bool DerRules;

        [NonSerialized]
        protected int DiffHourValue;

        public virtual int DiffHour
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }
                return DiffHourValue;
            }
            set
            {
                if ((value < -12) || (value > 12))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDiffHourValue, value);
                }

                SafeParseString();
                DiffHourValue = value;
                CompileString();
            }
        }

        [NonSerialized]
        protected int DiffMinValue;

        public virtual int DiffMinute
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }

                return DiffMinValue;
            }
        }

        [NonSerialized]
        protected string SecFraction;

        public virtual string Fraction
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }
                return SecFraction;
            }
            set
            {
                SafeParseString();
                SecFraction = value;
                CompileString();
            }
        }

        [NonSerialized]
        protected int YearValue;

        public virtual int Year
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }
                return YearValue;
            }
            set
            {
                if (value < 0)
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidYearValue, value);
                }

                if (!CheckDate(DayValue, MonthValue, value))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidYearValueForDayAndMonth, value, DayValue, MonthValue);
                }

                SafeParseString();
                YearValue = value;
                CompileString();
            }
        }

        [NonSerialized]
        protected int MonthValue;

        public virtual int Month
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }

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

                if (!CheckDate(DayValue, value, YearValue))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidMonthValueForDayAndYear, value, DayValue, YearValue);
                }

                SafeParseString();
                MonthValue = value;
                CompileString();
            }
        }

        [NonSerialized]
        protected int DayValue;

        public virtual int Day
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }
                return DayValue;
            }
            set
            {
                if (((value < 1) || (value > 31)) || !CheckDate(value, MonthValue, YearValue))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDayValueForMonthAndYear, value, MonthValue, YearValue);
                }

                SafeParseString();
                DayValue = value;
                CompileString();
            }
        }

        [NonSerialized]
        protected int HourValue;

        public virtual int Hour
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }
                return HourValue;
            }
            set
            {
                if ((value < 0) || (value > 23))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidHourValue, value);
                }

                SafeParseString();
                HourValue = value;
                CompileString();
            }
        }

        [NonSerialized]
        protected int MinuteValue;

        public virtual int Minute
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }
                return MinuteValue;
            }
            set
            {
                if ((value < 0) || (value > 59))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidMinuteValue, value);
                }

                SafeParseString();
                MinuteValue = value;
                CompileString();
            }
        }

        [NonSerialized]
        protected int SecondValue;

        public virtual int Second
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }
                return SecondValue;
            }
            set
            {
                if ((value < 0) || (value > 59))
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidSecondValue, value);
                }

                SafeParseString();
                SecondValue = value;
                CompileString();
            }
        }

        [NonSerialized]
        protected bool UtcFlag;

        public virtual bool Utc
        {
            get
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }

                return UtcFlag;
            }
            set
            {
                if (!DerRules)
                {
                    SafeParseString();
                    UtcFlag = value;
                    CompileString();
                }
            }
        }

        public virtual int CompareTo(object other)
        {
            if (other is DateTime)
            {
                var time2 = (DateTime)other;
                return (int)(GetTime().Ticks - time2.Ticks);
            }
            return (int)(GetTime().Ticks - ((Asn1Time)other).GetTime().Ticks);
        }

        protected static char CharAt(string s, int index)
        {
            if (index >= s.Length)
            {
                return '\0';
            }

            return s[index];
        }

        private static bool CheckDate(int day, int month, int year)
        {
            if ((day <= 0) || (month <= 0))
            {
                return true;
            }

            if ((year >= 0) && (month > 0))
            {
                int num = DaysInMonth[month];

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

                if ((day >= 1) && (day <= num))
                {
                    return true;
                }
            }
            else if (month > 0)
            {
                if (day <= DaysInMonth[month])
                {
                    return true;
                }

                if ((month == 2) && (day <= (DaysInMonth[month] + 1)))
                {
                    return true;
                }
            }

            return false;
        }

        public virtual void Clear()
        {
            YearValue = MonthValue = DayValue = HourValue = -1;
            MinuteValue = SecondValue = DiffHourValue = DiffMinValue = 0;
            UtcFlag = DerRules;
            Parsed = true;
            SecFraction = "";
            Value = "";
        }

        protected abstract bool CompileString();

        protected override void Decode(Asn1BerDecodeBuffer buffer, bool explicitTagging, int implicitLength, Asn1Tag tag)
        {
            Parsed = false;
            base.Decode(buffer, explicitTagging, implicitLength, tag);
            DerRules = buffer is Asn1DerDecodeBuffer;
        }

        protected override int Encode(Asn1BerEncodeBuffer buffer, bool explicitTagging, Asn1Tag tag)
        {
            SafeParseString();

            var flag = buffer is Asn1DerEncodeBuffer;

            if (DerRules != flag)
            {
                DerRules = flag;

                if (!CompileString())
                {
                    throw ExceptionUtility.CryptographicException(Resources.Asn1TimeStringCouldNotBeGenerated);
                }
            }

            return base.Encode(buffer, explicitTagging, tag);
        }

        public virtual void Encode(Asn1BerOutputStream outs, bool explicitTagging, Asn1Tag tag)
        {
            SafeParseString();
            outs.EncodeCharString(Value, explicitTagging, tag);
        }

        public override bool Equals(object value)
        {
            if (value is Asn1Time)
            {
                return GetTime().Equals(((Asn1Time)value).GetTime());
            }

            return ((value is DateTime) && GetTime().Equals((DateTime)value));
        }

        public virtual int GetDiff()
        {
            if (!Parsed)
            {
                ParseString(Value);
            }

            return ((DiffHourValue * 60) + DiffMinValue);
        }

        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }

        public virtual DateTime GetTime()
        {
            if (!string.IsNullOrEmpty(SecFraction))
            {
                return new DateTime(YearValue, MonthValue, DayValue, HourValue, MinuteValue, SecondValue, int.Parse(SecFraction));
            }

            return new DateTime(YearValue, MonthValue, DayValue, HourValue, MinuteValue, SecondValue);
        }

        protected virtual void Init()
        {
            YearValue = MonthValue = DayValue = HourValue = -1;
            MinuteValue = SecondValue = 0;
            DiffHourValue = DiffMinValue = 0;
            UtcFlag = DerRules;
            SecFraction = "";
        }

        protected static int ParseInt(string str, IntHolder off, int len)
        {
            if ((off.Value + len) > str.Length)
            {
                throw ExceptionUtility.ArgumentOutOfRange("off");
            }

            var mValue = off.Value;
            off.Value += len;

            return int.Parse(str.Substring(mValue, len));
        }

        public abstract void ParseString(string data);

        protected virtual void PutInteger(int width, int value)
        {
            PutInteger(StringBuffer, width, value);
        }

        public static void PutInteger(StringBuilder data, int width, int value)
        {
            var str = Convert.ToString(value);
            var length = str.Length;

            if (length < width)
            {
                for (var i = length; i < width; i++)
                {
                    data.Append('0');
                }
            }
            else if (length > width)
            {
                str = str.Substring(length - width);
            }

            data.Append(str);
        }

        protected virtual void SafeParseString()
        {
            try
            {
                if (!Parsed)
                {
                    ParseString(Value);
                }
            }
            catch (Exception)
            {
            }
        }

        public virtual void SetDiff(int inMinutes)
        {
            if (Math.Abs(inMinutes) > 720)
            {
                throw ExceptionUtility.CryptographicException(Resources.InvalidDiffValue, inMinutes);
            }

            SafeParseString();
            DiffHourValue = inMinutes / 60;
            DiffMinValue = inMinutes % 60;
            CompileString();
        }

        public virtual void SetDiff(int dhour, int dminute)
        {
            if ((dhour < -12) || (dhour > 12))
            {
                throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDiffHourValue, dhour);
            }

            if (Math.Abs(dminute) > 59)
            {
                throw ExceptionUtility.CryptographicException(Resources.Asn1InvalidDiffMinuteValue, dminute);
            }

            SafeParseString();
            DiffHourValue = dhour;

            if (dhour < 0)
            {
                DiffMinValue = -Math.Abs(dminute);
            }
            else
            {
                DiffMinValue = Math.Abs(dminute);
            }

            CompileString();
        }

        public virtual void SetTime(DateTime time)
        {
            Clear();
            YearValue = time.Year;
            MonthValue = time.Month;
            DayValue = time.Day;
            HourValue = time.Hour;
            MinuteValue = time.Minute;
            SecondValue = time.Second;
            SecFraction = Convert.ToString(time.Millisecond);
            DiffHourValue = DiffMinValue = 0;
            UtcFlag = DerRules;
            CompileString();
        }
    }
}
