using System;
using Sirenix.Utilities;
using UnityEngine;
using VOID.Generic.Objects.Unit.Actions.Exceptions;

namespace VOID.Generic.Objects.Unit.Actions
{
    public enum ActionState
    {
        Ready,
        Running,
        Finished,
        Cancelled
    }

    /// <summary>
    /// Action for <see cref="UnitObject"/>.<br/>
    /// 
    /// Executing order when <see cref="DoIt"/> used:<br/>
    /// <ul>
    /// <li><see cref="CanDoIt"/></li>
    /// <li><see cref="OnDo"/></li>
    /// <li><see cref="OnDoIt"/></li>
    /// <li>Inside OnDoIt: <see cref="OnBeforePerform"/></li>
    /// <li>Inside OnDoIt: Action's logic here</li>
    /// <li>Inside OnDoIt: <see cref="OnAfterPerform"/> - obligatory if action ends immediately</li>
    /// </ul>
    /// 
    /// Executing order when <see cref="EndIt"/> used:<br/>
    /// <ul>
    /// <li><see cref="OnEndIt"/></li>
    /// <li>Inside OnEndIt: <see cref="OnAfterPerform"/> - obligatory if not used in OnDoIt</li>
    /// <li><see cref="OnEnd"/></li>
    /// </ul>
    /// 
    /// Executing order when <see cref="CancelIt"/> used:<br/>
    /// <ul>
    /// <li><see cref="OnCancelIt"/></li>
    /// <li>Inside OnCancelIt: <see cref="OnAfterPerform"/> - obligatory if not used in OnDoIt</li>
    /// <li><see cref="OnCancel"/></li>
    /// </ul>
    /// </summary>
    [Serializable]
    public abstract class AbstractAction
    {
        /// <summary>Run after checking and before executing action.</summary>
        public event Action OnDo;

        /// <summary>Defined by referenced action. Usually right *before* main point of that action.</summary>
        public event Action OnBeforePerform;

        /// <summary>Defined by referenced action. Usually right *after* main point of that action.</summary>
        public event Action OnAfterPerform;

        /// <summary>Run after action ended.</summary>
        public event Action OnEnd;

        /// <summary>Run after action ended forcefully.</summary>
        public event Action OnCancel;

        /// <summary>Current state of action.</summary>
        public ActionState state { get; protected set; } = ActionState.Ready;

        /// <summary>If given, current action will be executed only if given one is finished successfully</summary>
        public AbstractAction runAfterAction;

        /// <summary>Simple check if unit is in range to execute this action.</summary>
        public abstract bool IsInRange();
        
	    /// <summary>Determines whether this action CAN be executed INSTANTLY outside the queue.</summary>
	    public virtual bool CanSkipQueue() => false;
        
	    /// <summary>Determines whether this action CAN be stopped early, by something else besides itself.</summary>
	    public virtual bool CanBeStopped() => true;
		
        /// <summary>Action's checks if action can be executed. <see cref="Check"/> have to be used here.</summary>
        public abstract void CanDoIt();
        
        /// <summary>Action's logic.</summary>
        protected abstract void OnDoIt();
        
        /// <summary>Action's logic when whole action ends successfully.</summary>
        protected abstract void OnEndIt();
        
        /// <summary>Action's logic when whole action is forced to end.</summary>
        protected abstract void OnCancelIt();

        public bool CanDoItBoolean()
        {
            try
            {
                CanDoIt();
                return true;
            }
            catch (UnitActionErrorException)
            {
                return false;
            }
        }

        /// <summary>
        /// 1. Checking if action can be done by function in child: <see cref="CanDoIt"/><br/>
        /// 2. Executing main login of this action
        /// </summary>
        public void DoIt()
        {
            Check(
                runAfterAction == null || runAfterAction.state == ActionState.Finished,
                UnitActionErrorType.PreviousActionNotFinished);
            CanDoIt();
            state = ActionState.Running;
            OnDo?.Invoke();
            OnDoIt();
        }

        /// <summary>
        /// Executed by action itself when everything is done correctly.
        /// </summary>
        protected void EndIt()
        {
            if (state is ActionState.Finished or ActionState.Cancelled)
            {
                Debug.LogWarning($"{GetType().GetNiceName()} can't be ended. State: '{state}'");
                return;
            }

            OnEndIt();
            state = ActionState.Finished;
            OnEnd?.Invoke();
        }

        /// <summary>
        /// Executed by external source. Stopping this action forcefully.
        /// </summary>
        public void CancelIt()
        {
            // Cancel events only when action really started otherwise there will be nothing to cancel
            if (state == ActionState.Running)
            {
                OnCancelIt();
                OnCancel?.Invoke();
            }

            state = ActionState.Cancelled;
        }

        /// <summary>
        /// Should be executed from child right before main logic is started, usually in <see cref="OnDoIt"/>
        /// </summary>
        protected void BeforePerform()
        {
            OnBeforePerform?.Invoke();
        }

        /// <summary>
        /// Should be executed from child right after main logic is done, usually in <see cref="OnDoIt"/>
        /// </summary>
        protected void AfterPerform()
        {
            OnAfterPerform?.Invoke();
        }

        /// <summary>
        /// Execute <see cref="Throw"/> if check is false
        /// </summary>
        protected void Check(bool condition, UnitActionErrorType elseError)
        {
            if (!condition) Throw(elseError);
        }

        /// <summary>
        /// Cancels this Action and then throw <see cref="UnitActionErrorException"/>
        /// </summary>
        protected void Throw(UnitActionErrorType errorType)
        {
            CancelIt();
            throw new UnitActionErrorException(this, errorType);
        }
    }
}