using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

public enum TimeValidationResult {
    Success,
    OffLine,
    TimeOut,
    HardwareTimeShiftFaster,
    HardwareTimeShiftLater,
}

public enum TimeValidateState {
    Requesting,
    Succeeded,
    Failed,
}

public class TimeValidateCoroutine {

    private Action removeAction;
    public TimeValidateCoroutine(Action removeAction){
        this.removeAction = removeAction;
    }

    public void Remove(){
        removeAction();
        removeAction = ActionExtensions.EmptyAction;
    }
}

static class TimeValidateCoroutineExtensions {
    public static void StopCoroutine(this TimeValidateCoroutine coroutine){
        if(coroutine != null) coroutine.Remove();
    }
}

public class TimeValidator : SingletonMonoBehaviour<TimeValidator> {

    [SerializeField]
    private float boundaryMinutes = 5;

    private TimeValidateState state = TimeValidateState.Requesting;
    private List<Action<TimeValidationResult>> callbackList = new List<Action<TimeValidationResult>>();
    private TimeValidationResult result;
    private Coroutine coroutine;

#if !UNITY_EDITOR
    void Awake(){
        coroutine = BeginTimeValidation();
    }
#endif

    public TimeValidateCoroutine TimeValidation(Action<TimeValidationResult> callback){
        switch(state){
        case TimeValidateState.Requesting:
            callbackList.Add(callback);
            return new TimeValidateCoroutine(() => callbackList.Remove(callback));
        default:
            callback(result);
            return null;
        }
    }

    void OnApplicationPause(bool pauseStatus){
        if(pauseStatus){
            this.SafeStopCoroutine(coroutine);
            state = TimeValidateState.Requesting;
        }else{
            coroutine = BeginTimeValidation();
        }
    }
    private Coroutine BeginTimeValidation(){
#if DEVELOPMENT_BUILD
            state = TimeValidateState.Succeeded;
            result = TimeValidationResult.Success;
        return null;
#else
        if(Application.internetReachability == NetworkReachability.NotReachable){
            state = TimeValidateState.Failed;
            result = TimeValidationResult.OffLine;
            return null;
        }
        return StartCoroutine(TimeValidation(networkTime => {
            DateTime now = DateTime.UtcNow;
            bool isLater = networkTime < now;
            TimeSpan difference;
            if(isLater){
                difference = now - networkTime;
            }else{
                difference = networkTime - now;
            }
            var isSuccess = difference.TotalMinutes <= boundaryMinutes;
            if(isSuccess){
                state = TimeValidateState.Succeeded;
                result = TimeValidationResult.Success;
            }else{
                state = TimeValidateState.Failed;
                if(isLater){
                    result = TimeValidationResult.HardwareTimeShiftLater;
                }else{
                    result = TimeValidationResult.HardwareTimeShiftFaster;
                }
            }
            foreach(var callback in callbackList){
                callback(result);
            }
            callbackList.Clear();
        }, () => {
            state = TimeValidateState.Failed;
            result = TimeValidationResult.TimeOut;
            foreach(var callback in callbackList){
                callback(result);
            }
            callbackList.Clear();
        }));
#endif
    }

    private IEnumerator TimeValidation(Action<DateTime> succeededCallback, Action failedCallback){
        var retryCounter = 3;
        var isTimeOut = true;
        var timeout = 3000;
        while(isTimeOut && retryCounter > 0){
            yield return new NtpRequest().GetNetworkTime(this, result => {
                isTimeOut = false;
                succeededCallback(result);
            }, ActionExtensions.EmptyAction, timeout);
            --retryCounter;
            timeout *= 5;
        }
        if(isTimeOut){
            failedCallback();
        }
    }
}
