using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;

[Serializable]
public struct Range {
    [SerializeField] int range;
    public Range(int range) {
        if (range < 0) {
            throw new ArgumentException("range cannot be negative", "range"); 
        }
        this.range = range;
    }

    public static implicit operator int(Range rangeToConvert) => rangeToConvert.range;
    public static explicit operator Range(int range) => new Range(range);
}
public abstract class BaseAction : MonoBehaviour
{
    public static event EventHandler onAnyActionStarted;
    public static event EventHandler onAnyActionCompleted;
    protected Unit unit;
    private bool isActive;
    protected CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    protected SpendablePoints<ActionPoints> actionCost = 1;
    [SerializeField] protected Range maxDistance = (Range)0;

    protected virtual void Awake() {
        unit = transform.GetComponent<Unit>();
    }

    protected void StopAction() {
        cancellationTokenSource.Cancel();
        cancellationTokenSource.Dispose();
        cancellationTokenSource = new CancellationTokenSource();
    }
    protected CancellationToken StartAction(out CancellationToken cancellationToken) {        
        cancellationToken = cancellationTokenSource.Token;      
        isActive = true;
        cancellationToken.Register(() => {
            isActive = false;
            onAnyActionCompleted?.Invoke(this, EventArgs.Empty);
        });
        onAnyActionStarted?.Invoke(this, EventArgs.Empty);
        return cancellationToken;
    }

    public bool IsActive() => isActive;
    public Unit GetUnit() => unit;
    public Range GetMaxDistance() => maxDistance;
    public virtual SpendablePoints<ActionPoints> GetActionPointsCost() => actionCost;
    public abstract string GetActionName();
    public abstract CancellationToken TakeAction(in GridPosition targetGridPosition, out CancellationToken cancellationToken);
    public virtual bool IsValidActionGridPosition(GridPosition gridPosition) => 
        GetValidActionGridPosition().Contains(gridPosition);
    public virtual IEnumerable<GridPosition> GetValidActionGridPosition() =>
        GetValidActionGridPosition(unit.GetGridPosition());
    public virtual IEnumerable<GridPosition> GetValidActionGridPosition(GridPosition gridPosition) =>
        GetOffsetsInCircularRange(maxDistance).
        Select(offset => gridPosition + offset).
        Where((testGridPosition) => IsValidTargetPosition(gridPosition, testGridPosition));
    public virtual IEnumerable<GridPosition> GetValidActionRangeGridPosition() =>
        GetValidActionRangeGridPosition(unit.GetGridPosition());
    public virtual IEnumerable<GridPosition> GetValidActionRangeGridPosition(GridPosition gridPosition) =>
        GetOffsetsInCircularRange(maxDistance).
        Select(offset => gridPosition + offset).
        Where((testGridPosition) => IsValidTargetRangePosition(testGridPosition));
    public bool IsValidTargetPosition(in GridPosition unitGridPosition, GridPosition testGridPosition) =>
        (IsValidTargetRangePosition(testGridPosition) && IsValidTargetPositionSpecific(unitGridPosition, testGridPosition));
    protected abstract bool IsValidTargetPositionSpecific(in GridPosition unitGridPosition, in GridPosition testGridPosition);
    public virtual bool IsValidTargetRangePosition(in GridPosition testGridPosition) =>
        (LevelGrid.Instance.IsValidGridPosition(testGridPosition));

    public abstract EnemyAIAction GetEnemyAIAction(GridPosition gridPosition);

    public IEnumerable<(BaseAction baseAction, EnemyAIAction enemyAIAction)> GetPossibleEnemyAIAction() {
        return (GetValidActionGridPosition().Select(gridPosition => (baseAction: this, enemyAIAction: GetEnemyAIAction(gridPosition))));
    }
    IEnumerable<(int x, int z)> GetOffsetsInCircularRange(Range range) {
        for (int x = -range; x <= range; x++) {
            for (int z = -range; z <= range; z++) {
                if ((MathF.Abs(x) + MathF.Abs(z) <= range)) {
                    yield return (x, z);
                }
            }
        }
    }

}