using UnityEngine;
using System;
using System.Collections;

public static class MonoBehaviourExtensions {

    public static void SafeStopCoroutine(this MonoBehaviour monoBehaviour, Coroutine c){
		if(c != null) monoBehaviour.StopCoroutine(c);
	}

    /// 1フレーム待機してからActionデリゲートを呼び出す
    public static Coroutine CallWaitForOneFrame(this MonoBehaviour monoBehaviour, Action act){
        return monoBehaviour.StartCoroutine(DoCallWaitForOneFrame(act));
    }
    private static IEnumerator DoCallWaitForOneFrame(Action act){
        yield return null;
        act();
    }
    /// 指定フレーム数待機してからActionデリゲートを呼び出す
    public static Coroutine CallWaitForFrame(this MonoBehaviour monoBehaviour, int frameCount, Action act){
        return monoBehaviour.StartCoroutine(DoCallWaitForFrame(frameCount, act));
    }
    private static IEnumerator DoCallWaitForFrame(int frameCount, Action act){
        for(int i = 0; i < frameCount; ++i) yield return null;
        act();
    }

    /// 1FixedUpdate待機してからActionデリゲートを呼び出す
    public static Coroutine CallWaitForOneFixedUpdate(this MonoBehaviour monoBehaviour, Action act){
        return monoBehaviour.StartCoroutine(DoCallWaitForOneFixedUpdate(act));
    }
    private static IEnumerator DoCallWaitForOneFixedUpdate(Action act){
        yield return new WaitForFixedUpdate();
        act();
    }
    /// 指定FixedUpdate数待機してからActionデリゲートを呼び出す
    public static Coroutine CallWaitForFixedUpdate(this MonoBehaviour monoBehaviour, int frameCount, Action act){
        return monoBehaviour.StartCoroutine(DoCallWaitForFixedUpdate(frameCount, act));
    }
    private static IEnumerator DoCallWaitForFixedUpdate(int frameCount, Action act){
        for(int i = 0; i < frameCount; ++i) yield return new WaitForFixedUpdate();
        act();
    }

    /// 指定秒数待機してからActionデリゲートを呼び出す
    public static Coroutine CallWaitForSeconds(this MonoBehaviour monoBehaviour, float seconds, Action act){
        return monoBehaviour.StartCoroutine(DoCallWaitForSeconds(seconds, act));
    }
    private static IEnumerator DoCallWaitForSeconds(float seconds, Action act){
        yield return new WaitForSeconds(seconds);
        act();
    }
    public static Coroutine CallWaitForSeconds(this MonoBehaviour monoBehaviour, float seconds, Action<float> act, Action callback){
        return monoBehaviour.StartCoroutine(DoCallWaitForSeconds(seconds, act, callback));
    }
    private static IEnumerator DoCallWaitForSeconds(float seconds, Action<float> act, Action callback){
        float t = 0.0f;
        act(t);
        do{
            yield return null;
            t += Time.deltaTime;
            act(t);
        }while(t < seconds);
        callback();
    }
    public static Coroutine CallWaitForRealTimeSeconds(this MonoBehaviour monoBehaviour, float seconds, Action callback){
        return monoBehaviour.StartCoroutine(DoCallWaitForRealTimeSeconds(seconds, ActionExtensions.EmptyActionFloat, callback));
    }
    public static Coroutine CallWaitForRealTimeSeconds(this MonoBehaviour monoBehaviour, float seconds, Action<float> act, Action callback){
        return monoBehaviour.StartCoroutine(DoCallWaitForRealTimeSeconds(seconds, act, callback));
    }
    private static IEnumerator DoCallWaitForRealTimeSeconds(float seconds, Action<float> act, Action callback){
        float startupTime = Time.realtimeSinceStartup;
        float t = 0.0f;
        act(t);
        while(true){
            yield return null;
            t = (Time.realtimeSinceStartup - startupTime);
            if(t >= seconds){
                act(seconds);
                break;
            }else{
                act(t);
            }
        }
        callback();
    }

    public static Coroutine CallLerp(this MonoBehaviour monoBehaviour, float duration, Action<float> act){
        return monoBehaviour.CallLerp(duration, act, ActionExtensions.EmptyAction);
    }
    public static Coroutine CallLerp(this MonoBehaviour monoBehaviour, float duration, Action<float> act, Action callback){
        return monoBehaviour.StartCoroutine(DoCallLerp(duration, act, callback));
    }
    public static IEnumerator DoCallLerp(float duration, Action<float> act){
        return DoCallLerp(duration, act, ActionExtensions.EmptyAction);
    }
    public static IEnumerator DoCallLerp(float duration, Action<float> act, Action callback){
        float t = 0.0f;
        act(t);
        while(true){
            yield return null;
            t += Time.deltaTime / duration;
            if(t >= 1.0f){
                act(1.0f);
                break;
            }else{
                act(t);
            }
        }
        callback();
    }
    public static Coroutine CallFixedLerp(this MonoBehaviour monoBehaviour, float duration, Action<float> act){
        return monoBehaviour.CallFixedLerp(duration, act, ActionExtensions.EmptyAction);
    }
    public static Coroutine CallFixedLerp(this MonoBehaviour monoBehaviour, float duration, Action<float> act, Action callback){
        return monoBehaviour.StartCoroutine(DoCallFixedLerp(duration, act, callback));
    }
    public static IEnumerator DoCallFixedLerp(float duration, Action<float> act){
        return DoCallFixedLerp(duration, act, ActionExtensions.EmptyAction);
    }
    public static IEnumerator DoCallFixedLerp(float duration, Action<float> act, Action callback){
        float t = 0.0f;
        act(t);
        while(true){
            yield return new WaitForFixedUpdate();
            t += Time.fixedDeltaTime / duration;
            if(t >= 1.0f){
                act(1.0f);
                break;
            }else{
                act(t);
            }
        }
        callback();
    }
    public static Coroutine CallFixedLerpRealTime(this MonoBehaviour monoBehaviour, float duration, Action<float> act){
        return monoBehaviour.CallFixedLerpRealTime(duration, act, ActionExtensions.EmptyAction);
    }
    public static Coroutine CallFixedLerpRealTime(this MonoBehaviour monoBehaviour, float duration, Action<float> act, Action callback){
        return monoBehaviour.StartCoroutine(DoCallFixedLerpRealTime(duration, act, callback));
    }
    public static IEnumerator DoCallFixedLerpRealTime(float duration, Action<float> act){
        return DoCallFixedLerpRealTime(duration, act, ActionExtensions.EmptyAction);
    }
    public static IEnumerator DoCallFixedLerpRealTime(float duration, Action<float> act, Action callback){
        float t = 0.0f;
        act(t);
        while(true){
            yield return new WaitForFixedUpdate();
            t += Time.fixedUnscaledDeltaTime / duration;
            if(t >= 1.0f){
                act(1.0f);
                break;
            }else{
                act(t);
            }
        }
        callback();
    }
    public static Coroutine CallLerpSmooth(this MonoBehaviour monoBehaviour, float duration, Action<float> act){
        return monoBehaviour.CallLerpSmooth(duration, act, ActionExtensions.EmptyAction);
    }
    public static Coroutine CallLerpSmooth(this MonoBehaviour monoBehaviour, float duration, Action<float> act, Action callback){
        return monoBehaviour.StartCoroutine(DoCallLerpSmooth(duration, act, callback));
    }
    public static IEnumerator DoCallLerpSmooth(float duration, Action<float> act){
        return DoCallLerpSmooth(duration, act, ActionExtensions.EmptyAction);
    }
    public static IEnumerator DoCallLerpSmooth(float duration, Action<float> act, Action callback){
        float t = 0.0f;
        act(t);
        while(true){
            yield return null;
            t += Time.smoothDeltaTime / duration;
            if(t >= 1.0f){
                act(1.0f);
                break;
            }else{
                act(t);
            }
        }
        callback();
    }

    public static Coroutine CallLerpRealtime(this MonoBehaviour monoBehaviour, float duration, Action<float> act){
        return monoBehaviour.CallLerpRealtime(duration, act, ActionExtensions.EmptyAction);
    }
    public static Coroutine CallLerpRealtime(this MonoBehaviour monoBehaviour, float duration, Action<float> act, Action callback){
        return monoBehaviour.StartCoroutine(DoCallLerpRealtime(duration, act, callback));
    }
    public static IEnumerator DoCallLerpRealtime(float duration, Action<float> act){
        return DoCallLerpRealtime(duration, act, ActionExtensions.EmptyAction);
    }
    public static IEnumerator DoCallLerpRealtime(float duration, Action<float> act, Action callback){
        float startupTime = Time.realtimeSinceStartup;
        float t = 0.0f;
        act(t);
        while(true){
            yield return null;
            t = (Time.realtimeSinceStartup - startupTime);
            if(t >= duration){
                act(1.0f);
                break;
            }else{
                act(t / duration);
            }
        }
        callback();
    }

    /// 指定秒間、指定秒間隔でactionを行う
    public static Coroutine CallTimer(this MonoBehaviour monoBehaviour, float seconds, float interval, Action<int> action){
        return monoBehaviour.CallTimer(seconds, 0.0f, interval, action, ActionExtensions.EmptyAction);
    }
    public static Coroutine CallTimer(this MonoBehaviour monoBehaviour, float seconds, float interval, Action<int> action, Action callback){
        return monoBehaviour.StartCoroutine(DoCallTimer(seconds, 0.0f, interval, action, callback));
    }
    public static Coroutine CallTimer(this MonoBehaviour monoBehaviour, float seconds, float firstTime, float interval, Action<int> action){
        return monoBehaviour.CallTimer(seconds, firstTime, interval, action, ActionExtensions.EmptyAction);
    }
    public static Coroutine CallTimer(this MonoBehaviour monoBehaviour, float seconds, float firstTime, float interval, Action<int> action, Action callback){
        return monoBehaviour.StartCoroutine(DoCallTimer(seconds, firstTime, interval, action, callback));
    }
    private static IEnumerator DoCallTimer(float seconds, float firstTime, float interval, Action<int> action, Action callback){
        float t = 0.0f;
        float timer = firstTime;
        int counter = 1;
        do{
            yield return null;
            t += Time.deltaTime;
            timer += Time.deltaTime;
            while(timer >= interval){
                timer -= interval;
                action(++counter);
            }
        }while(t < seconds);
        callback();
    }
    public static Coroutine CallTimerRealtime(this MonoBehaviour monoBehaviour, float seconds, float interval, Action<int> action, Action callback){
        return monoBehaviour.StartCoroutine(DoCallTimerRealtime(seconds, interval, action, callback));
    }
    private static IEnumerator DoCallTimerRealtime(float seconds, float interval, Action<int> action, Action callback){
        float startupTime = Time.realtimeSinceStartup;
        float t = 0.0f;
        int counter = 0;
        action(counter + 1);
        do{
            yield return null;
            t = (Time.realtimeSinceStartup - startupTime);
            int count = (int)(t / interval);
            if(counter != count){
                counter = count;
                action(counter + 1);
            }
        }while(t < seconds);
        callback();
    }
}