using System;
using System.Collections.Generic;
using System.Text;
using nft.core.view;
using System.Diagnostics;

namespace nft.core.schedule {
    public class PrimitiveConditions {
        protected static Dictionary<string, ISceneCondition> conditions = new Dictionary<string, ISceneCondition>();
        protected static Dictionary<string, NamedConditionGroup> groups = new Dictionary<string, NamedConditionGroup>();
        protected static Dictionary<string, TimeUnit> formats = new Dictionary<string, TimeUnit>();

        static PrimitiveConditions() {
            formats["MM"] = formats["M"] = TimeUnit.Month;
            formats["dd"] = formats["d"] = TimeUnit.Day;
            formats["HH"] = formats["H"] = TimeUnit.Hour;
            formats["E"] = TimeUnit.Day;
        }

        public static void RegisterCondtion(ISceneCondition cond) {
            Debug.WriteLine("Register condition: name="+cond.ConditionName);
            conditions[cond.ConditionName] = cond; //重複は後のもので上書き
            NamedConditionGroup g = null;
            if (!groups.TryGetValue(cond.GroupName, out g)) {
                g = new PrimitiveConditions.NamedConditionGroup(cond.GroupName);
                groups[cond.GroupName] = g;
            }
            g.Add(cond);
        }

        public static ISceneCondition GetByName(string name) {
            return conditions[name];
        }

        public static ISceneCondition CreateSceneCondition(string groupname, string name, ConditionTypes ctype, IComparable match, TimeUnit option_resol) {
            return CreateSceneConditionImpl(groupname, name, ctype, match, null, option_resol);
        }

        public static ISceneCondition CreateSceneCondition(string groupname, string name, ConditionTypes ctype, object match) {
            return CreateSceneConditionImpl(groupname, name, ctype, match, null, TimeUnit.Ticks);
        }

        public static ISceneCondition CreateSceneCondition(string groupname, string name, ConditionTypes ctype, IComparable from, IComparable until) {
            return CreateSceneConditionImpl(groupname, name, ctype, from, until, TimeUnit.Ticks);
        }

        public static ISceneCondition CreateSceneCondition(string groupname, string name, ConditionTypes ctype, IComparable from, IComparable until, TimeUnit unit) {
            return CreateSceneConditionImpl(groupname, name, ctype, from, until, unit);
        }

        public static ISceneCondition CreateSceneConditionFromDateFormat(string groupname, string name, ConditionTypes ctype, string format, IComparable match) {
            return CreateSceneConditionFromDateFormatImpl(groupname, name, ctype, format, match, null);
        }

        public static ISceneCondition CreateSceneConditionFromDateFormat(string groupname, string name, ConditionTypes ctype, string format, IComparable from, IComparable until) {
            return CreateSceneConditionFromDateFormatImpl(groupname, name, ctype, format, from, until);
        }

        public static TimeUnit SelectUnitFromDateFormat(string format) {
            format = "" + format;
            TimeUnit tu;
            if (formats.TryGetValue(format, out tu)) {
                return tu;
            }
            return TimeUnit.Ticks;
        }

        protected static ISceneCondition CreateSceneConditionFromDateFormatImpl(string groupname, string name, ConditionTypes ctype, string format, IComparable _from, IComparable _until) {
            ISceneCondition cond = null;

            TimeUnit unit = SelectUnitFromDateFormat(format);
            IConditionValueExtracter ex;
            if (unit == TimeUnit.Ticks) {
                // 任意のフォーマット変換による比較
                ex = new FormattedTimeStrConditionExtracter(ctype, format);
            } else {
                ex = CreateCondValueExtracter(ctype, unit);
            }
            Type t = ex.ComparableType;

            if (_until == null || _until.CompareTo(_from) == 0) {
                cond = new EqualityCondition(groupname, name, ex, AdjustType(_from, t));
            } else {
                _until = AdjustType(_until, t);
                _from = AdjustType(_from, t);
                if (_until.CompareTo(_from) > 0) {
                    cond = new TimeRangeCondition(groupname, name, ex, _from, _until);
                } else {
                    cond = new BoudaryTimeRangeCondition(groupname, name, ex, _from, _until);
                }
            }
            RegisterCondtion(cond);
            return cond;
        }

        protected static ISceneCondition CreateSceneConditionImpl(string groupname, string name, ConditionTypes ctype, object from, object until, TimeUnit unit) {
            ISceneCondition cond = null;
            if (ctype == ConditionTypes.Weather) {
                cond = new WeatherCondition(groupname, from);
            } else {
                IComparable _from = from as IComparable;
                IComparable _until = until as IComparable;
                IConditionValueExtracter ex = CreateCondValueExtracter(ctype, unit);
                Type t = ex.ComparableType;

                if (_until == null || _until.CompareTo(_from) == 0) {
                    cond = new EqualityCondition(groupname, name, ex, AdjustType(_from, t));
                } else {
                    _until = AdjustType(_until, t);
                    _from = AdjustType(_from, t);
                    if (_until.CompareTo(_from) > 0) {
                        cond = new TimeRangeCondition(groupname, name, ex, _from, _until);
                    } else {
                        cond = new BoudaryTimeRangeCondition(groupname, name, ex, _from, _until);
                    }
                }
            }
            RegisterCondtion(cond);
            return cond;
        }

        protected static IComparable AdjustType(IComparable org, Type t) {
            return (IComparable) Convert.ChangeType(org, t);
        }

        public static IConditionValueExtracter CreateCondValueExtracter(ConditionTypes ctype) {
            switch (ctype) {
            case ConditionTypes.Daily:
            case ConditionTypes.Weekly:
            case ConditionTypes.Monthly:
            case ConditionTypes.Yearly:
            case ConditionTypes.TimeOfDay:
                return new TimeConditionValueExtracter(ctype, TimeUnit.Ticks);
            case ConditionTypes.Scale:
                return new ScaleConditionValueExtracter();
            case ConditionTypes.Weather:
                throw new ArgumentException("Weather type is not acceptable. Use WeatherConditon instead.");
            }
            return null;
        }

        public static IConditionValueExtracter CreateCondValueExtracter(ConditionTypes ctype, TimeUnit resolution) {
            try {
                TimeUnit tc = (TimeUnit)ctype;
            } catch (Exception) {
                throw new ArgumentException(Enum.GetName(typeof(ConditionTypes), ctype) + " type is not acceptable.");
            }

            switch (resolution) {
            case TimeUnit.Hour:
            case TimeUnit.Day:
            case TimeUnit.Week:
                return new TimeConditionValueExtracter(ctype, resolution);
            case TimeUnit.Month:
                return new MonthConditionValueExtracter();
            }
            return null;
        }

        public static NamedConditionGroup GetGroupByName(string name) {
            return groups[name];
        }

        /// <summary>
        /// Clear cached flag for all CacheableSceneCondition instances.
        /// Be ware that CacheableSceneCondition will igonre ISceneParams and return cached result whenever once cashed.
        /// 
        /// CacheableSceneConditionは一度結果をキャッシュすると、次からは無条件で前回の結果を返す
        /// ISceneParamsを更新したらこのメソッドを読んでキャッシュをクリアしないといけない。
        /// </summary>
        public static void InvalidateCachedConditions() {
            CacheableSceneCondition.master_cashetick++;
        }

        public class NamedConditionGroup : IEnumerable<ISceneCondition> {
            private readonly string name;
            private List<string> members;

            internal protected NamedConditionGroup(string group_name) {
                name = group_name;
                members = new List<string>();
            }

            public string Name {
                get{ return name; }
            }

            public IEnumerator<ISceneCondition> GetEnumerator() {
                foreach (string s in members) {
                    yield return PrimitiveConditions.GetByName(s);
                }
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
                return this.GetEnumerator();
            }

            internal protected void Add(ISceneCondition cond) {
                members.Add(cond.ConditionName);
            }

            public int Count {
                get{ return members.Count; }
            }
        }

    }

    /// <summary>
    /// CacheableSceneCondition caches result when IsMatch called first.
    /// After that it will igonre ISceneParams and return cached result, untill PrimitiveConditions.InvalidateCashedConditions is called.
    /// 
    /// CacheableSceneConditionは一度結果をキャッシュすると、次からは無条件で前回の結果を返す
    /// ISceneParamsを更新したらPrimitiveConditions.InvalidateCachedConditionsを呼んでキャッシュをクリアしないといけない。
    /// </summary>
    public abstract class CacheableSceneCondition : ISceneCondition {
        protected internal static uint master_cashetick = 0;
        protected readonly string name;
        protected readonly string grp_name;
        protected uint cachetick;
        protected bool cached_result;

        protected CacheableSceneCondition(string groupname, string name) {
            this.grp_name = groupname;
            this.name = name;
            this.cachetick = --master_cashetick;
        }

        public string GroupName {
            get {
                return grp_name;
            }
        }
        public string ConditionName {
            get {
                return name;
            }
        }

        public abstract ConditionTypes ConditionType { get; }

        public virtual bool IsMatch(ISceneParams p) {
            if (this.cachetick != master_cashetick) {
                cached_result = IsMatchCore(p);
                this.cachetick = master_cashetick;
            }
            return cached_result;
        }

        protected abstract bool IsMatchCore(ISceneParams p);

        public override string ToString() {
            return String.Format("{0}:({1}/{2}/{3})", GetType().Name, ConditionType, GroupName, ConditionName); 
        }
    }

    public abstract class ComparebleSceneCondition : CacheableSceneCondition {
        protected IConditionValueExtracter extracter;

        protected ComparebleSceneCondition(string groupname, string name):base(groupname, name) {
        }

        protected ComparebleSceneCondition(string groupname, string cname, IConditionValueExtracter cvext):base(groupname, cname) {
            this.extracter = cvext;
        }

        public override ConditionTypes ConditionType {
            get {
                return extracter.ConditionType;
            }
        }
    }

    public class EqualityCondition : ComparebleSceneCondition {
        protected IComparable match;
        public EqualityCondition(string groupname, string name, IConditionValueExtracter ext, IComparable v)
            : base(groupname, name, ext) {
            this.match = v;
        }

        protected override bool IsMatchCore(ISceneParams p) {
            return extracter.GetTestValue(p).CompareTo(match)==0;
        }
    }

    public class TimeEqualityCondition : EqualityCondition, ITimeRangeCondition {
        public TimeEqualityCondition(string groupname, string name, IConditionValueExtracter ext, IComparable v)
            : base(groupname, name, ext, v) {
        }

        #region ITimeRangeCondition implementation
        // These result are not cached

        /// <summary>
        /// returns true if this condition is after the scene
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public bool IsAfter(ISceneParams p) {
            return extracter.GetTestValue(p).CompareTo(match) < 0;
        }

        /// <summary>
        /// returns true if this condition is before the scene
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public bool IsBefore(ISceneParams p) {
            return extracter.GetTestValue(p).CompareTo(match) > 0;
        }
        #endregion
    }

    public class TimeRangeCondition : ComparebleSceneCondition, ITimeRangeCondition {
        protected IComparable from, until;
        public TimeRangeCondition(string groupname, string name, IConditionValueExtracter ext, IComparable from, IComparable until)
            : base(groupname, name, ext) {
            this.from = from;
            this.until = until;                
        }

        protected override bool IsMatchCore(ISceneParams p) {
            IComparable v = extracter.GetTestValue(p);
            return v.CompareTo(from) >= 0 && v.CompareTo(until) < 0; //from <= v && v < until;
        }

        #region ITimeRangeCondition implementation
        // These result are not cached

        /// <summary>
        /// returns true if this condition is after the scene
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public bool IsAfter(ISceneParams p) {
            return extracter.GetTestValue(p).CompareTo(from) < 0;
        }

        /// <summary>
        /// returns true if this condition is before the scene
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public bool IsBefore(ISceneParams p) {
            return extracter.GetTestValue(p).CompareTo(until) > 0;
        }
        #endregion
    }

    /// <summary>
    /// This condition is used for splited range around the boundery of cyclic period.
    /// For example, from at 23 o'clock to at 1 o'clock of the next day.
    /// This condition is given with arguments from=23, to=1.
    /// </summary>
    public class BoudaryTimeRangeCondition : ComparebleSceneCondition, ITimeRangeCondition {
        protected IComparable from, until;
        public BoudaryTimeRangeCondition(string groupname, string name, IConditionValueExtracter ext, IComparable from, IComparable until)
            : base(groupname, name, ext) {
            this.from = from;
            this.until = until;
        }

        protected override bool IsMatchCore(ISceneParams p) {
            IComparable v = extracter.GetTestValue(p);
            return v.CompareTo(until) < 0 || v.CompareTo(from) >= 0; //v < until || from <= v;
        }

        #region ITimeRangeCondition implementation
        // These result are not cached

        /// <summary>
        /// returns true if this condition is after the scene
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public bool IsAfter(ISceneParams p) {
            return extracter.GetTestValue(p).CompareTo(from) < 0;
        }

        /// <summary>
        /// returns true if this condition is before the scene
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public bool IsBefore(ISceneParams p) {
            return extracter.GetTestValue(p).CompareTo(until) > 0;
        }
        #endregion
    }

    public class WeatherCondition : CacheableSceneCondition {
        protected Object weather;
        internal protected WeatherCondition(string groupname, Object weather) : base(groupname, weather.ToString()){
        }

        public override ConditionTypes ConditionType {
            get {
                return ConditionTypes.Weather;
            }
        }

        protected override bool IsMatchCore(ISceneParams p) {
            return weather.Equals(p.Weather);
        }
    }

}
