using System;
using System.Collections.Generic;
using System.Text;
using Yanesdk.Draw;
using Yamalib.Util;
using Yanesdk.Timer;
using Yamalib.Input;
using System.Diagnostics;

namespace Kyojin.Component.Ending
{
    /// <summary>
    /// ヒットステータス
    /// </summary>
    public enum HitStatus
    {
        GREAT,
        GOOD,
        BAD,
        POOR,
        ERROR,
        NONE
    };

    /// <summary>
    /// ノート（音符）をヒットしたときのイベント
    /// </summary>
    /// <param name="screen"></param>
    /// <param name="textObj"></param>
    /// <param name="hitTime"></param>
    /// <param name="histStatus"></param>
    /// <returns></returns>
    public delegate bool HitEvent(IScreen screen, TextObj textObj, long hitTime, HitStatus histStatus);


    /// <summary>
    /// タイミングデータ
    /// </summary>
    public class TimingData
    {
        public TimingData(long hitTime, HitEvent[] hitEvent)
        {
            m_hitTime = hitTime;
            m_event = hitEvent;
        }

        public TimingData(long hitTime, HitEvent[] hitEvent, bool hidden)
            : this(hitTime, hitEvent)
        {
            m_hidden = hidden;
        }

        /// ヒットするミリ秒
        public long HitTime
        {
            get { return m_hitTime; }
        }

        public HitEvent[] HitEvent
        {
            get { return m_event; }
        }

        public bool IsHidden
        {
            get { return m_hidden; }
            set { m_hidden = value; }
        }

        #region メンバフィールド

        private long m_hitTime;
        private HitEvent[] m_event;
        private bool m_hidden;

        #endregion

    }

    /// <summary>
    /// テンポデータ
    /// </summary>
    public class TempoData
    {
        /// コンストラクタ
        public TempoData(string text, List<TimingData> timing)
        {
            m_text = text;
            m_charText = text.ToCharArray();
            m_timing = timing;
        }

        public string Text
        {
            get { return m_text; }
        }

        public List<TimingData> Timing
        {
            get { return m_timing; }
        }

        public char GetNextLetter()
        {
            if (m_charText.Length > letterIndex)
            {
                return m_charText[letterIndex++];
            }
            return 'A';
        }

        #region メンバフィールド

        private string m_text;
        private char[] m_charText;
        private List<TimingData> m_timing;
        // 制御用変数
        private int letterIndex = 0;

        #endregion

    }

    /// <summary>
    /// テキストオブジェクト
    /// </summary>
    public class TextObj
    {
        /// <summary>
        /// ヒット結果データクラス
        /// </summary>
        public class HitResult
        {
            public HitResult(long hitTime, HitStatus hitStatus)
            {
                this.hitTime = hitTime;
                this.hitStatus = hitStatus;
            }

            public long HitTime
            {
                get { return hitTime; }
            }
            public HitStatus HitStatus
            {
                get { return hitStatus; }
            }

            private long hitTime;
            private HitStatus hitStatus;
        }

        #region メンバフィールド

        private HitResult m_hitResult;
        private HitEvent[] m_event;
        private FixTimer m_timer;
        private long m_startTime = 0L;
        private ITexture m_img;
        private float m_rate;
        private int m_rad;
        private int m_alpha;
        private readonly Position m_pos = new Position(0, 0);
        private bool m_hitable;
        private bool m_endFlg;
        private bool m_drawEndFlg;
        private bool m_hidden;

        #endregion

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="timer"></param>
        public TextObj(FixTimer timer)
        {
            m_timer = timer;
            m_startTime = timer.Time;
        }

        public TextObj(FixTimer timer, long startTime)
        {
            m_timer = timer;
            m_startTime = startTime;
        }

        /// <summary>
        /// ヒットできるデータかどうか
        /// </summary>
        public bool IsHitable
        {
            get { return m_hitable; }
            set { m_hitable = value; }
        }

        /// <summary>
        /// 描画終了かどうか
        /// </summary>
        public bool IsDrawEnd
        {
            get { return m_drawEndFlg; }
            set { m_drawEndFlg = value; }
        }

        /// <summary>
        /// 終了したか
        /// 描画およびデリゲートが終了
        /// </summary>
        public bool IsEnd
        {
            get { return m_endFlg; }
            set { m_endFlg = value; }
        }

        /// <summary>
        /// 開始しているか
        /// </summary>
        public bool IsStart
        {
            get { return m_timer.Time > StartTime; }
        }

        /// <summary>
        /// 画像
        /// </summary>
        public ITexture Img
        {
            get { return m_img; }
            set { m_img = value; }
        }

        /// <summary>
        ///描画位置
        /// </summary>
        public Position Pos
        {
            get { return m_pos; }
        }

        public float Rate
        {
            get { return m_rate; }
            set { m_rate = value; }
        }

        public int Rad
        {
            get { return m_rad; }
            set { m_rad = value; }
        }

        public int Alpha
        {
            get { return m_alpha; }
            set { m_alpha = value; }
        }

        /// <summary>
        ///  生起してからの時間
        /// </summary>
        public long Time
        {
            get { return m_timer.Time - StartTime; }
        }

        /// <summary>
        /// 隠れタイミング
        /// </summary>
        public bool IsHidden
        {
            get { return m_hidden; }
            set { m_hidden = value; }
        }

        private long m_targetTime;

        /// <summary>
        /// ヒットイベント
        /// </summary>
        public HitEvent[] HitEvent
        {
            get { return m_event; }
            set { m_event = value; }
        }

        /// <summary>
        /// 開始時間
        /// </summary>
        public long StartTime
        {
            get { return m_startTime; }
        }

        /// <summary>
        /// ターゲットにしているヒットのタイム(ms)
        /// </summary>
        public long TargetTime
        {
            get { return m_targetTime; }
            set { m_targetTime = value; }
        }

        public HitResult HitResultData
        {
            get { return m_hitResult; }
            set { m_hitResult = value; }
        }

        /// オブジェクトのシャローコピーを生成する
        public TextObj Clone()
        {
            TextObj obj = new TextObj(m_timer, m_startTime);
            obj.HitEvent = m_event;
            obj.HitResultData = m_hitResult;
            obj.Img = m_img;
            obj.Rate = m_rate;
            obj.Rad = m_rad;
            obj.Alpha = m_alpha;
            obj.Pos.X = m_pos.X;
            obj.Pos.Y = m_pos.Y;
            obj.IsHitable = m_hitable;
            obj.IsEnd = m_endFlg;
            obj.IsDrawEnd = m_drawEndFlg;
            obj.TargetTime = m_targetTime;
            return obj;
        }

        /// <summary>
        /// 描画処理
        /// </summary>
        /// <param name="screen"></param>
        public void OnPaint(IScreen screen)
        {
            if (!IsEnd)
            {
                if (m_hitable && !m_hidden)
                {
                    screen.SetColor(255, 0, 0, Alpha);
                }
                else
                {
                    screen.SetColor(255, 255, 255, Alpha);
                }
                screen.BltRotate(m_img, (int)m_pos.X, (int)m_pos.Y, m_rad, m_rate, 4);
            }
        }

        /// <summary>
        /// デリゲートを起動
        /// </summary>
        /// <param name="screen"></param>
        public void OnDrawDelegate(IScreen screen)
        {
            if (!IsEnd)
            {
                if (HitEvent == null)
                {
                    IsEnd = m_drawEndFlg;
                    return;
                }

                if (HitResultData != null)
                {
                    bool invoking = false;
                    for (int i = 0; i < HitEvent.Length; ++i)
                    {
                        if (HitEvent[i] == null)
                        {
                            continue;
                        }

                        bool result = HitEvent[i](screen, this, HitResultData.HitTime, HitResultData.HitStatus);
                        if (result)
                        {
                            HitEvent[i] = null;
                        }
                        else
                        {
                            invoking = true;
                        }
                    }
                    //どれかのデリゲートが起動中
                    IsEnd = m_drawEndFlg && !invoking;
                }
            }
        }

    }

    public class TextuaryTempo
    {

        #region 定数値


        #endregion

        #region メンバフィールド

        // 移動タイミング用タイマ
        private FixTimer m_timer;
        // フェードアウト用
        private int fadeStartTime;
        private int fadeTime;
        private bool fadeOut = false;
        private bool m_stop = false;
        // ヒットフラグ
        private bool m_invalidHit;
        // auto Play falg
        private bool m_autoPlay;
        // ターゲット範囲
        private ITexture m_scopeImg;
        // ヒット用デバイス
        private IMouseInput m_mouse;
        private bool m_mouseLBtPush;
        // フォントリポジトリ
        private FontRepository m_fontRep;
        // 表示もととなる文字列
        private List<TempoData> m_tempoData;
        // 使用する文字列
        private int m_textIndex = 0;
        // 開始する文字列サイズ
        private float m_startRate = 0.2f;
        // 終端での文字サイズ
        private float m_endRate = 1.5f;
        // 描画開始位置
        private Position m_startPos = new Position(10, 30);
        // 描画終了位置
        private Position m_endPos = new Position(610, 450);
        // OK誤差
        private long m_hitMargin = 400L;
        // 生成スピード(ms)
        private long m_generateInterval = 100L;
        // hit pointに到達するまでの時間 (ms)
        private long m_arrivalTime = 1500L;
        // フェード・インフェード・アウト用カウンタ
        private readonly RootCounterS m_alphaCounter;


        // プログラム制御用
        private List<TextObj> textObj;
        private long preGenTime = long.MinValue;
        private readonly Position centerPos = new Position();
        private int dxCenter;
        private int dyCenter;
        private int rad;

        /// すべてのオブジェクトデータを作成する
        private readonly Dictionary<TextObj, TimingData> checkMap = new Dictionary<TextObj, TimingData>();

        #endregion


        #region プロパティ

        public FixTimer Timer
        {
            get { return m_timer; }
            set { m_timer = value; }
        }

        public IMouseInput Mouse
        {
            get { return m_mouse; }
            set { m_mouse = value; }
        }

        public bool IsAutoPlay
        {
            get { return m_autoPlay; }
            set { m_autoPlay = value; }
        }

        public FontRepository FontRepository
        {
            get { return m_fontRep; }
            set { m_fontRep = value; }
        }

        public ITexture ScopeImg
        {
            get { return m_scopeImg; }
            set { m_scopeImg = value; }
        }

        public List<TempoData> TempoData
        {
            get { return m_tempoData; }
            set { m_tempoData = value; }
        }

        public int TextIndex
        {
            get { return m_textIndex; }
            set { m_textIndex = value; }
        }

        public Position StartPos
        {
            get { return m_startPos; }
            set { m_startPos = value; }
        }

        public Position EndPos
        {
            get { return m_endPos; }
            set { m_endPos = value; }
        }

        public long HitMargin
        {
            get { return m_hitMargin; }
            set { m_hitMargin = value; }
        }

        public float StartRate
        {
            get { return m_startRate; }
            set { m_startRate = value; }
        }

        public float EndRate
        {
            get { return m_endRate; }
            set { m_endRate = value; }
        }

        public long GenerateInterval
        {
            get { return m_generateInterval; }
            set { m_generateInterval = value; }
        }

        public long ArrivalTime
        {
            get { return m_arrivalTime; }
            set { m_arrivalTime = value; }
        }

        public RootCounterS AlphaCounter
        {
            get { return m_alphaCounter; }
        }

        #endregion

        /// <summary>
        /// ヒット結果を元にHitResultデータを生成し返却する
        /// </summary>
        /// <param name="targetTime"></param>
        /// <param name="hitTime"></param>
        /// <returns></returns>
        public static TextObj.HitResult createHitResult(long targetTime, long hitTime)
        {
            int errorMargin = (int)Math.Abs(targetTime - hitTime);

            // Great!
            if (50 >= errorMargin)
            {
                return new TextObj.HitResult(hitTime, HitStatus.GREAT);
            }
            // Good!
            if (75 >= errorMargin)
            {
                return new TextObj.HitResult(hitTime, HitStatus.GOOD);
            }
            // Bad!
            if (100 >= errorMargin)
            {
                return new TextObj.HitResult(hitTime, HitStatus.BAD);
            }
            // poor!
            return new TextObj.HitResult(hitTime, HitStatus.POOR);
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public TextuaryTempo()
        {
            m_alphaCounter = new RootCounterS(255, 255, 1);
            // 中心点を求めておく
            CalcCenterPos();
        }


        /// <summary>
        /// 動作処理	
        /// </summary>
        /// <param name="screen"></param>
        public void OnMove(IScreen screen)
        {
            if (m_tempoData == null)
            {
                return;
            }
            m_timer.Update();
            if (m_stop)
            {
                return;
            }

            // アルファ
            m_alphaCounter.Inc();
            // 文字移動
            OnMoveTextObj(screen);

            // ヒットされたか
            if (m_mouse != null)
            {
                m_invalidHit = false;
                bool mouseLDown = m_mouse.IsPress(Yanesdk.Input.MouseInput.Button.Left);
                //bool mouseLNewlyDown = mouseLDown && !m_mouseLBtPush;
                bool mouseLNewlyDown = m_mouse.IsPush(Yanesdk.Input.MouseInput.Button.Left);
                // どれかのオブジェクトに対応するヒットが存在したか
                bool hitObject = !mouseLNewlyDown;
                long now = m_timer.Time;
                foreach (TextObj obj in textObj)
                {
                    if (!obj.IsStart || obj.IsEnd || !obj.IsHitable)
                    {
                        continue;
                    }
                    long time = obj.TargetTime;

                    if (m_autoPlay)
                    {
                        if ((time - now) < 50 || now > time)
                        {
                            // ヒット結果保存
                            if (!obj.IsHidden)
                            {
                                obj.HitResultData = createHitResult(time, now);
                                obj.IsHitable = false;
                            }
                            else
                            {
                                // 隠しデータ
                                obj.HitResultData = new TextObj.HitResult(time, HitStatus.NONE);
                                obj.IsHitable = false;
                            }
                            // ノートを叩いたのでOK
                            hitObject = true;
                        }
                    }
                    // マウスが押された時刻が誤差内
                    else if (mouseLNewlyDown && IsInRange(time - m_hitMargin, time + m_hitMargin, now))
                    {
                        //printf("OK: %d\n", time - now);
                        // ヒット結果保存
                        if (!obj.IsHidden)
                        {
                            obj.HitResultData = createHitResult(time, now);
                            obj.IsHitable = false;
                        }
                        else
                        {
                            // 隠しデータ
                            obj.HitResultData = new TextObj.HitResult(time, HitStatus.NONE);
                            obj.IsHitable = false;
                        }
                        // ノートを叩いたのでOK
                        hitObject = true;
                    }
                    else if (now > time + m_hitMargin)
                    {
                        // ヒットされなかった
                        obj.HitResultData = new TextObj.HitResult(long.MinValue, !obj.IsHidden ? HitStatus.POOR : HitStatus.NONE);
                        obj.IsHitable = false;
                        // 叩いたことにする
                        hitObject = true;
                    }
                }

                if (!hitObject)
                {
                    // ノートを叩いていないのにヒットしたので無関係なヒット
                    m_invalidHit = true;
                    //Log.print("HIT ERROR!!");
                }

                m_mouseLBtPush = mouseLDown;
            }
        }

        /// <summary>
        /// 動作処理	
        /// </summary>
        /// <param name="screen"></param>
        public void OnMoveTextObj(IScreen screen)
        {
            // 起点
            CalcCenterPos();
            float rateBreadth = m_endRate - m_startRate;
            float alphaRate = fadeOut ? 1.0f - ((m_timer.Time - fadeStartTime) / (float)fadeTime) : 1.0f;
            if (alphaRate < 0.0f)
            {
                alphaRate = 0.0f;
                m_stop = true;
            }
            float nowAlpha = fadeOut ? m_alphaCounter.Value() * alphaRate : m_alphaCounter.Value();
            foreach (TextObj obj in textObj)
            {
                if (obj.IsStart && !obj.IsEnd)
                {
                    // 描画が終了していないなら継続
                    if (!obj.IsDrawEnd)
                    {
                        // 描画位置・角度・拡大率 を求める
                        float timeProgress = obj.Time / (float)m_arrivalTime;
                        obj.Pos.X = m_startPos.X + (int)(dxCenter * timeProgress);
                        obj.Pos.Y = m_startPos.Y + (int)(dyCenter * timeProgress);
                        obj.Rate = m_startRate + (rateBreadth * timeProgress);
                        obj.Rad = rad;

                        if (timeProgress < 1.0f)
                        {
                            obj.Alpha = (int)(nowAlpha * timeProgress);
                        }
                        else
                        {
                            obj.Alpha = (int)(nowAlpha - (nowAlpha * (timeProgress - 1.0f)));

                            // アルファが0になったら終了
                            if (obj.Alpha < 0)
                            {
                                obj.IsDrawEnd = true;
                            }
                        }
                    }
                    obj.OnDrawDelegate(screen);
                }
            }
        }

        /// <summary>
        /// 描画処理
        /// </summary>
        /// <param name="screen"></param>
        public void OnPaint(IScreen screen)
        {
            if (m_tempoData == null)
            {
                return;
            }
            if (m_stop)
            {
                return;
            }

            // テンポテキスト描画
            for (int i = textObj.Count - 1; i >= 0; --i)
            {
                if (textObj[i].IsEnd || !textObj[i].IsStart)
                {
                    continue;
                }
                textObj[i].OnPaint(screen);
            }

            // ターゲット画像
            screen.BlendSrcAlpha();
            screen.SetColor(255, 255, 255, 200);
            screen.BltRotate(m_scopeImg, (int)centerPos.X, (int)centerPos.Y, rad, 1.5f, 4);
        }


        public bool IsInvalidHit()
        {
            return m_invalidHit;
        }

        /// <summary>
        /// タイマをリセットして、動作準備する
        /// </summary>
        public void Reset()
        {
            textObj = null;
            preGenTime = long.MinValue;

            // タイマをリセット
            m_timer.Reset();
            textObj = CreatTextObjAll(m_tempoData[m_textIndex]);
            preGenTime = m_timer.Time;
            m_timer.Reset();
        }

        /// <summary>
        /// 残りのヒットデータ
        /// </summary>
        /// <returns></returns>
        public int RestHitData()
        {
            int nowTime = m_timer.Time;
            int count = 0;
            foreach (TextObj obj in textObj)
            {
                if (obj.IsHitable && obj.TargetTime > nowTime)
                {
                    ++count;
                }
            }
            return count;
        }

        public void Stop()
        {
            m_stop = true;
        }

        /// <summary>
        /// アルファフェードしながら表示を終了する
        /// </summary>
        /// <param name="fadeTime"></param>
        public void FadeStop(int fadeTime)
        {
            fadeStartTime = m_timer.Time;
            this.fadeTime = fadeTime;
            fadeOut = true;
        }

        #region 非公開メンバ

        private static long Min(long x, long y)
        {
            return x < y ? x : y;
        }

        private static bool IsInRangeAbs(long just, long margin, long myValue)
        {
            return (just - margin) <= myValue && (just + margin) >= myValue;
        }

        private static bool IsInRange(long min, long max, long target)
        {
            return min <= target && max >= target;
        }

        /// <summary>
        /// 引数に指定したテンポ情報より、描画オブジェクトを作成する
        /// </summary>
        /// <param name="data"></param>
        /// <param name="startTime"></param>
        /// <returns></returns>
        private TextObj CreateTextObj(TempoData data, long startTime)
        {
            char ch = data.GetNextLetter();
            ITexture img = FontRepository.GetTexture(ch, 0);
            TextObj obj = new TextObj(Timer, startTime);
            obj.Img = img;
            obj.Rate = StartRate;
            obj.Alpha = 0;
            obj.Pos.X = StartPos.X;
            obj.Pos.Y = StartPos.Y;
            //setHitable(obj, data, startTime);
            return obj;
        }

        private List<TextObj> CreatTextObjAll(TempoData data)
        {
            List<TextObj> result = new List<TextObj>();
            for (int i = 0; i < data.Text.Length; ++i)
            {
                result.Add(CreateTextObj(data, GenerateInterval * i));
            }

            // 作ったTextObj[]に TimingData を割り当てる
            foreach (TimingData timingData in data.Timing)
            {
                long time = timingData.HitTime;
                TextObj minData = null;
                long minTime = long.MaxValue;
                // 範囲内のtextObj からもっとも精度のよいものを抽出
                foreach (TextObj textObj in result)
                {
                    if (IsInRangeAbs(time, m_hitMargin, textObj.StartTime + m_arrivalTime))
                    {
                        long aMinTime = Min(minTime, Math.Abs(time - (textObj.StartTime + m_arrivalTime)));
                        if (minTime > aMinTime)
                        {
                            minTime = aMinTime;
                            minData = textObj;
                        }
                    }
                }

                Debug.Assert(minData != null);
                if (checkMap.ContainsKey(minData))
                {
                    checkMap.Remove(minData);
                    //Log.print("over rite");
                }

                checkMap.Add(minData, timingData);
                minData.IsHitable = true;
                minData.TargetTime = time;
                minData.HitEvent = timingData.HitEvent;
                minData.IsHidden = timingData.IsHidden;
            }
            return result;
        }

        // 開始終点より中間位置を求める
        private void CalcCenterPos()
        {
            dxCenter = (int)(EndPos.X - StartPos.X) / 2;
            centerPos.X = StartPos.X + dxCenter;

            dyCenter = (int)(EndPos.Y - StartPos.Y) / 2;
            centerPos.Y = StartPos.Y + dyCenter;

            // こいつは内部でシングルトン
            rad = Yanesdk.Math.SinTable.Instance.Atan((int)(StartPos.X - EndPos.X), (int)(StartPos.Y - EndPos.Y)) >> 7;
        }

        #endregion


    }
}
