// This code is part of the Fungus library (https://github.com/snozbot/fungus)
// It is released for free under the MIT open source license (https://github.com/snozbot/fungus/blob/master/LICENSE)

using UnityEngine;

namespace Fungus
{
    /// <summary>
    /// Base for all Conditional based Commands, Ifs, Loops, and so on.
    /// </summary>
    [AddComponentMenu("")]
    public abstract class Condition : Command
    {
        protected End endCommand;
     
        #region Public members

        public override void OnEnter()
        {
            if (ParentBlock == null)
            {
                return;
            }

            //if looping we need the end command in order to work
            if(IsLooping && !EnsureRequiredEnd())
            {
                Debug.LogError(GetLocationIdentifier() + " is looping but has no matching End command");
                Continue();
                return;
            }

            if ( !HasNeededProperties() )
            {
                Debug.LogError(GetLocationIdentifier() + " cannot run due to missing required properties");
                Continue();
                return;
            }

            //Ensuring we arrived at this elif honestly, not incorrectly due to fall through from a previous command
            if (this.IsElseIf && !DoesPassElifSanityCheck())
            {
                //elif is being asked to run but didn't come from a previously failing if or elif, this isn't allowed
                MoveToEnd();
                return;
            }

            EvaluateAndContinue();
        }

        public override bool OpenBlock()
        {
            return true;
        }

        public override Color GetButtonColor()
        {
            return new Color32(253, 253, 150, 255);
        }
        
        public virtual bool IsLooping { get { return false; } }


        /// <summary>
        /// Moves execution to the closing End of the current command, attempts to locate end if not
        /// already known and if no closing End exists.
        /// </summary>
        public virtual void MoveToEnd()
        {
            if(endCommand == null)
            {
                endCommand = FindOurEndCommand();
            }

            if (endCommand != null)
            {
                // Continue at next command after End
                // and make the end non looping incase it gets run via index etc.
                endCommand.Loop = false;
                Continue(endCommand.CommandIndex + 1);
            }
            else
            {
                //nowhere to go, so we assume the block wants to stop but is missing and end, this
                //  is also ensures back compat
                Debug.LogWarning("Condition wants to move to end but no End command found, stopping block. " + GetLocationIdentifier());
                StopParentBlock();
            }
        }

        #endregion
               
        protected End FindOurEndCommand()
        {
            return FindMatchingEndCommand(this);
        }

        /// <summary>
        /// Helper to find the paired End Command for the given command.
        /// </summary>
        /// <param name="startCommand"></param>
        /// <returns>Mathcing End Command or null if not found</returns>
        public static End FindMatchingEndCommand(Command startCommand)
        {
            if (startCommand.ParentBlock == null)
                return null;

            int indent = startCommand.IndentLevel;
            for (int i = startCommand.CommandIndex + 1; i < startCommand.ParentBlock.CommandList.Count; ++i)
            {
                var command = startCommand.ParentBlock.CommandList[i];

                if (command.IndentLevel == indent)
                {
                    if (command is End)
                    {
                        return command as End;
                    }
                }
                else if (command.IndentLevel < indent)
                {
                    //managed to be less indent than the inner but not find and end, this shouldn't occur
                    // but may be user error or bad data, makes sense for completeness here
                    return null;
                }
            }

            return null;
        }

        /// <summary>
        /// Helper for child classes that require an End command to function. For IsLooping commands
        /// this also configures the loopback within the End command.
        /// </summary>
        /// <returns></returns>
        protected virtual bool EnsureRequiredEnd()
        {
            if (endCommand == null)
            {
                endCommand = FindOurEndCommand();

                if (endCommand == null)
                {
                    Debug.LogError( GetLocationIdentifier() + "', could not find closing End command and thus cannot loop.");
                    //StopParentBlock();
                    return false;
                }
            }

            if (IsLooping)
            {
                // Tell the following end command to loop back
                endCommand.Loop = true;
                endCommand.LoopBackIndex = CommandIndex;
            }
            return true;
        }

        /// <summary>
        /// Called by OnEnter when the condition is needed to evaluate and continue execution.
        /// Means child classes do not have to deal with erronuous execution conditions, like fall through.
        /// </summary>
        protected virtual void EvaluateAndContinue()
        {
            PreEvaluate();

            if (EvaluateCondition())
            {
                OnTrue();
            }
            else
            {
                OnFalse();
            }
        }

        /// <summary>
        /// Called when the condition is run and EvaluateCondition returns true 
        /// </summary>
        protected virtual void OnTrue()
        {
            Continue();
        }

        /// <summary>
        /// Called when the condition is run and EvaluateCondition returns false 
        /// </summary>
        protected virtual void OnFalse()
        {
            //looping constructs only care about the end
            if(IsLooping)
            {
                MoveToEnd();
                return;
            }

            // Find the next Else, ElseIf or End command at the same indent level as this If command
            for (int i = CommandIndex + 1; i < ParentBlock.CommandList.Count; ++i)
            {
                Command nextCommand = ParentBlock.CommandList[i];

                if (nextCommand == null)
                {
                    continue;
                }

                // Find next command at same indent level as this If command
                // Skip disabled commands, comments & labels
                if (!((Command)nextCommand).enabled || 
                    nextCommand.GetType() == typeof(Comment) ||
                    nextCommand.GetType() == typeof(Label) ||
                    nextCommand.IndentLevel != indentLevel)
                {
                    continue;
                }

                System.Type type = nextCommand.GetType();
                if (type == typeof(Else) ||
                    type == typeof(End))
                {
                    if (i >= ParentBlock.CommandList.Count - 1)
                    {
                        // Last command in Block, so stop
                        StopParentBlock();
                    }
                    else
                    {
                        // Execute command immediately after the Else or End command
                        Continue(nextCommand.CommandIndex + 1);
                        return;
                    }
                }
                else if (type.IsSubclassOf(typeof(Condition)) && (nextCommand as Condition).IsElseIf)
                {
                    // Execute the Else If command
                    Continue(i);
                    return;
                }
            }

            // No matching End command found, so just stop the block
            StopParentBlock();
        }

        /// <summary>
        /// Sits in the if within EvaluateAndContinue, if returns true, OnTrue will run, if false, OnFalse will run.
        /// </summary>
        protected abstract bool EvaluateCondition();

        /// <summary>
        /// Child classes are required to report if it is possible for them to be evaulated.
        /// </summary>
        protected virtual bool HasNeededProperties() { return true; }

        /// <summary>
        /// Declare if the child class is implementing an 'else if' command, which requires some special handling
        /// </summary>
        protected virtual bool IsElseIf { get { return false; } }

        /// <summary>
        /// Called before EvaluateCondition, allowing for child classes to gather required data
        /// </summary>
        protected virtual void PreEvaluate() { }

        /// <summary>
        /// Ensure that this condition didn't come from a non matching if/elif.
        /// </summary>
        /// <returns></returns>
        protected virtual bool DoesPassElifSanityCheck()
        {
            System.Type previousCommandType = ParentBlock.GetPreviousActiveCommandType();
            var prevCmdIndent = ParentBlock.GetPreviousActiveCommandIndent();
            var prevCmd = ParentBlock.GetPreviousActiveCommand();

            //handle our matching if or else if in the chain failing and moving to us,
            //  need to make sure it is the same indent level
            if (prevCmd == null ||
                prevCmdIndent != IndentLevel ||
                !previousCommandType.IsSubclassOf(typeof(Condition)) ||
                (prevCmd as Condition).IsLooping)
            {
                return false;
            }

            return true;
        }
    }
}