using System;
using nft.framework;
using nft.core.geometry;
using System.Collections.Generic;
using System.Collections;

namespace nft.core.structure
{
    public delegate void CatelogVariableEventHandler(CatalogVariableEventArgs arg);
    
    // ジェネリック型を無視してまとめて扱いたい時のためのベースインターフェース
    public interface ICatalogVariable {
        bool Auto { get; set; }
        bool Random { get; set; }
        bool AllowLoop { get; }

        event CatelogVariableEventHandler ValueChanged;

        event CatelogVariableEventHandler OptionChanged;

        /// <summary>
        /// 現在の選択状況を保存／復元するために使用
        /// setでは復元できなくてもエラーにせず現状維持
        /// </summary>
        object CurrentHint { get; set; }
        object Current { get; }
    }

    public interface ICatalogVariable<U> : ICatalogVariable, IEnumerable<U> {
        int Count { get; }
        new U Current { get; }
        U Next();
        U Prev();
        U First();
        U Last();

        /// <summary>
        /// 取り得る値からランダムに取得
        /// ゲームモデル側から利用されるため、ValueChangedは発生しない
        /// </summary>
        /// <returns></returns>
        U NextRandom();
    }

    public abstract class CatalogVariable<U> : ICatalogVariable<U> {
        protected static Random rand = new Random();
        protected int current;
        private bool bAuto = false;
        private bool bRandom = false;
        private readonly bool bLoop;
        protected bool dirty = false;
        public event CatelogVariableEventHandler ValueChanged;
        public event CatelogVariableEventHandler OptionChanged;

        public CatalogVariable(bool allowLoop) {
            this.bLoop = allowLoop;
        }

        protected abstract int Max { get; }
        protected abstract int Min { get; }
        protected abstract int Step { get; }

        public virtual int Count {  
            get{
                return (Max - Min)/Step +1;
            }
        }

        object ICatalogVariable.Current {
            get { return Current; }
        }

        public abstract U Current { get; }

        public U Next() {
            if (current < Max) {
                dirty = true;
                current+=Step;
            } else if (AllowLoop) {
                return First();
            }
            U ret = Current;
            FireValueChangedEvent();
            return ret;
        }

        public U Prev() {
            if (current > Min) {
                current -= Step;
            } else if (AllowLoop) {
                return Last();
            }
            U ret = Current;
            FireValueChangedEvent();
            return ret;
        }

        public U First() {
            int _min = Min;
            if (current != _min) {
                current = _min;
                dirty = true;
            }
            U ret = Current;
            FireValueChangedEvent();
            return ret;
        }

        public U Last() {
            int _max = Max;
            if (current != _max) {
                current = _max;
                dirty = true;
            }
            U ret = Current;
            FireValueChangedEvent();
            return ret;
        }

        public U NextRandom() {
            if (Random) {
                int _min = Min/Step;
                int _max = Max/Step;
                int r = rand.Next(_min, _max + 1);
                return PickForRandom(r * Step);
            } else {
                throw new InvalidOperationException();
            }
        }

        protected abstract U PickForRandom(int val);

        public abstract IEnumerator<U> GetEnumerator();

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

        public bool Auto
        {
	        get { return bAuto; }
            set {
                if (bAuto != value) {
                    bAuto = value;
                    FireOptionChangedEvent();
                }
            }
        }
        public bool Random
        {
	        get { return bRandom; }
            set {
                if (bRandom != value) {
                    bRandom = value;
                    FireOptionChangedEvent();
                }
            }
        }

        public bool AllowLoop {
            get { return bLoop; }
        }
        public virtual object CurrentHint {
            get {
                return current;
            }
            set {
                if (value is IConvertible) {
                    current = ((IConvertible)value).ToInt32(null);
                }
            }
        }

        protected void FireOptionChangedEvent() {
            if (dirty && OptionChanged != null) OptionChanged(new CatalogVariableEventArgs(this));
        }

        protected void FireValueChangedEvent() {
            if (ValueChanged != null) ValueChanged(new CatalogVariableEventArgs(this));
        }

    }



    public class CatListVariable<U> : CatalogVariable<U> {
        protected IList<U> list;
        
        public CatListVariable(IList<U> source, bool allowLoop) : base(allowLoop) {
            list = source;
            current = 0;
        }

        public override int Count {
            get { return list.Count; }
        }

        public override U Current {
            get { return list[current]; }
        }

        protected override U PickForRandom(int val) {
            return list[val];
        } 

        public override IEnumerator<U> GetEnumerator() {
            return list.GetEnumerator();
        }

        protected override int Max {
            get { return list.Count-1; }
        }

        protected override int Min {
            get { return 0; }
        }

        protected override int Step {
            get { return 1; }
        }
    }

    public class IndexVariable : CatalogVariable<int> {
        protected int min, max;
        protected int step;

        public IndexVariable(int min, int max, int step, bool allowLoop)
            : base(allowLoop) {
            this.min = min;
            this.max = max;
            this.step = step;
            this.current = min;
        }
        public IndexVariable(int min, int max, bool allowLoop)
            : this(min, max, 1, allowLoop) {
        }
        public IndexVariable(int max, bool allowLoop)
            : this(0, max, 1, allowLoop) {
        }

        public override int Current {
            get { throw new NotImplementedException(); }
        }
        protected override int PickForRandom(int val) {
            return val;
        }

        public override IEnumerator<int> GetEnumerator() {
            throw new NotImplementedException();
        }

        protected override int Max {
            get { return max; }
        }

        protected override int Min {
            get { return min; }
        }

        protected override int Step {
            get { return step; }
        }

    }

    public class CatalogVariableEventArgs : EventArgs {
        public readonly ICatalogVariable Source;
        public CatalogVariableEventArgs(ICatalogVariable src) {
            this.Source = src;
        }
    }
}
