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

[RequireComponent(typeof(RenderPipeline))]
public class TurnBasedGameLoop : MonoBehaviour
{
    public float aimPreviewLooseness = 0.2f;
    public bool InputCancelsRender = true;
    public bool SimultaneousTurns = true;
    public bool AIWaitsForPlayerRender = true;
    public float MovementRenderTime = 0.1f;
    public float ProjectileSpeed = 7.68f;

    public static TurnBasedGameLoop instance;
    public int TurnCount { get; private set; }
    public int SubTurnCount { get; private set; }
        
    private RenderPipeline renderPipeline;
    ITurnLoopActorList turnActorList = new TurnLoopActorList();
    TurnActor currentActor;
    private bool playerInputApplied = false;
    private bool lastActorWasPlayer = false;

    private enum EngineState
    {
        CONTINUE,
        FRAMEBREAK,
        WAIT
    }

    void Update()
    {

        if(playerInputApplied || renderPipeline.Finished())
        {
            Tick();
        }

    }

    private void Tick()
    {
        if (turnActorList.ActorCount == 0)
            return;

        int maxActorsPerTick = (SimultaneousTurns) ? turnActorList.ActorCount : 1;
        int actorsDone = 0;
        EngineState engineState;

        do
        {
            if (currentActor == null)
                GotoNextTurn();

            lastActorWasPlayer = currentActor.WaitsForInput;

            // break frame prior to player's turn when taken mid-loop, in case we wait for AI anims
            if (lastActorWasPlayer && actorsDone != 0)
                return;

            engineState = ExecuteCurrentActor();

            if (engineState == EngineState.FRAMEBREAK)
            {
                GotoNextTurn();
                return;
            }

            if (engineState == EngineState.CONTINUE)
            {
                GotoNextTurn();
                actorsDone++;
            }

            // break frame after player's turn if the AI waits for player anims
            if (AIWaitsForPlayerRender && lastActorWasPlayer)
                return;
        }
        while (engineState == EngineState.CONTINUE && actorsDone < maxActorsPerTick);
    }

    private EngineState ExecuteCurrentActor()
    {
        if (currentActor == null)
            return EngineState.CONTINUE;

        try
        {
            TurnAction act = currentActor.GetAction();

            if (act == null)
                return EngineState.WAIT;

            if (currentActor.WaitsForInput)
            {                
                if (InputCancelsRender)
                    renderPipeline.CatchUpWithGame();
                playerInputApplied = false;
            } 
            
            ExecuteAction(act);

            if (act.Result == ActionResult.SuccessOthersAffected)
                return EngineState.FRAMEBREAK;
            else
                return EngineState.CONTINUE;
        }
        catch (Exception e)
        {
            Debug.LogError(currentActor + " threw exception in ExecuteCurrentActor, skipping.  Execption: \n" + e.ToString());
            return EngineState.CONTINUE;
        }
    }

    private void ExecuteAction(TurnAction act, float renderDelay=0)
    {
        if (act == null)
            return;

        act.Perform();
        if (act.Result != ActionResult.Abort)
        {
            Render rend = act.GetRender();

            if (renderDelay != 0)
                rend = new RenderDelay(rend, renderDelay);

            if (rend != null)
            {
                renderPipeline.AddRender(rend);
                ExecuteAction(act.FollowupAction, renderDelay + rend.NextRenderDelay);
            }
            else
                ExecuteAction(act.FollowupAction, renderDelay);
        }
    }

    private void GotoNextTurn()
    {
        if(currentActor != null)
            currentActor.gameObject.SendMessage("EndTurn", SendMessageOptions.DontRequireReceiver);
        currentActor = turnActorList.GetNextActor();

        SubTurnCount++;
        if (turnActorList.StartsNewTurn(currentActor))
            TurnCount++;

        Debug.Log("TBGL: Starting turn " + TurnCount + " . " + SubTurnCount + " for " + currentActor.name);
        currentActor.gameObject.SendMessage("StartTurn", SendMessageOptions.DontRequireReceiver);

    }

    public void AddActorToGame(TurnActor actor)
    {
        turnActorList.AddActor(actor);
    }
    public void RemoveActorFromGame(TurnActor actor)
    {
        turnActorList.RemoveActor(actor);
    }
    public void ApplyPlayerInput()
    {
        if (InputCancelsRender)
            playerInputApplied = true;
    }

    public void ExecuteActionImmediate(TurnAction action)
    {
        ExecuteAction(action, 0);
    }

    void Start()
    {
        instance = this;
        renderPipeline = GetComponent<RenderPipeline>();
    }
}
