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

public interface IFighterCallback
{
    void OnFighterDeath(Fighter fighter);
}

// Generic subclass for any Fighter on the field, whether it's an Enemy or the AntiPlayer.
// Contains stats management such as health, attacks, damage, death, etc.
public abstract class Fighter : MonoBehaviour, IRoundCallback
{

    [SerializeField] int baseHealth = 100;
    [SerializeField, Tooltip("Attack every x seconds")] float baseAttackSpeed = 1f;
    [SerializeField] int baseAttackDamage = 10;
    [SerializeField] int baseArmor = 1;
    [SerializeField, Tooltip("Being hurt may cancel the current Attack animation, aborting the current attack. If this Fighter is hurt within x seconds of the last attack, attack again after attackRetryDelayAfterBeingHurt seconds")]
    float attackRetryGracePeriod = 0.8f;
    [SerializeField, Tooltip("If an attack is being retried, how long to wait until to actually attack again. Gives the Hrt animation time to play out.")]
    float attackRetryDelayAfterBeingHurt = 0.25f;
    [SerializeField] List<AudioClip> attackBarks;
    [SerializeField] List<AudioClip> hurtBarks;
    [SerializeField] List<AudioClip> deathBarks;

    public List<IFighterCallback> callbacks = new List<IFighterCallback>();

    protected int maxHealth = 100; // Dynamically valculated based on upgrades
    protected int currentHealth = 100;
    protected float currentHealthPercentage { get => (float) currentHealth / maxHealth; }
    protected Animator animator;
    protected new Rigidbody2D rigidbody;
    protected SpriteRenderer spriteRenderer;
    protected HealthBarController healthBarController;
    protected DamageIndicatorCanvas damageIndicator;
    protected BaseCameraController cameraController;
    protected bool alive { get => currentHealth > 0; }
    protected bool roundRunning = false;
    protected FighterTypes fighterType;
    protected string opponentTag;

    protected Vector3 initalScale;
    protected Vector3 initalPosition;
    protected int initialLayer;
    protected Sprite initialSprite;

    protected abstract bool CanAttack();
    protected abstract void Attack();

    float timeSinceLastAttack = float.PositiveInfinity;

    protected virtual void Awake()
    {
        animator = GetComponent<Animator>();
        rigidbody = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        healthBarController = GetComponentInChildren<HealthBarController>();
        damageIndicator = GetComponentInChildren<DamageIndicatorCanvas>();
        cameraController = Camera.main.GetComponent<BaseCameraController>();

        fighterType = FighterTypes.ENEMY;

        initalPosition = transform.position;
        initalScale = transform.localScale;
        initialLayer = gameObject.layer;
        initialSprite = spriteRenderer.sprite;

        OnRoundEnd(false);
    }

    protected virtual void Start()
    {
        RoundController.instance.roundCallbacks.Add(this);
    }

    protected virtual void Update()
    {
        if (alive && roundRunning)
        {
            if (CanAttack())
            {
                timeSinceLastAttack += Time.deltaTime;
                var timeUntilNextAttack = baseAttackSpeed - timeSinceLastAttack;
                if (timeUntilNextAttack <= 0)
                {
                    cameraController.PlayRandomGlobalAudioClip(attackBarks);
                    Attack();
                    timeSinceLastAttack = float.IsInfinity(timeUntilNextAttack) ? 0 : -timeUntilNextAttack; // To account for overshoot
                }
            }
            else
            {
                timeSinceLastAttack = float.PositiveInfinity;
            }
        }
    }

    public virtual int DealDamage(int dmg)
    {
        if (!alive || !roundRunning)
        {
            return 0;
        }

        var actualDamage = Mathf.Max(0, Mathf.RoundToInt(dmg - baseArmor * GetStats().armorMultiplier));
        currentHealth = Mathf.Max(currentHealth - actualDamage, 0);

        if (currentHealth == 0)
        {
            animator.SetTrigger("Death");
            callbacks.ForEach(c => c.OnFighterDeath(this));
            // Disable own collision so that other Fighters can run over our dead body
            gameObject.layer = LayerMask.NameToLayer("Ground-Only Collision");
            healthBarController.SetHealthBarEnabled(false);
            cameraController.PlayRandomGlobalAudioClip(deathBarks);
        }
        else
        {
            animator.SetTrigger("Hurt");
            healthBarController.ShowHealth(currentHealthPercentage);
            if (timeSinceLastAttack - attackRetryGracePeriod <= 0f)
            {
                // Fighter was hit during the grace period, possibly interrupting the Attack animation.
                // Attack again after another grace period to allow the Hurt animation to play out.
                timeSinceLastAttack = baseAttackSpeed - attackRetryDelayAfterBeingHurt;
            }
            cameraController.PlayRandomGlobalAudioClip(hurtBarks);
        }
        damageIndicator.FlashText(string.Format("-{0}", actualDamage));

        return actualDamage;
    }

    protected FighterStats GetStats()
    {
        return StatsManager.instance.fighterStats[fighterType];
    }

    void OnTriggerEnter2D(Collider2D other)
    {
#if false
        Debug.Log(string.Format("'{0}' (tag '{1}') collided with '{2}' (tag '{3}') in parent '{4}' (tag '{5}')", name, tag, other.name, other.tag, other.transform.parent ? other.transform.parent.name : null, other.transform.parent ? other.transform.parent.tag : null));
#endif
        if (roundRunning && other.CompareTag(opponentTag))
        {
            var damageToDeal = Mathf.RoundToInt(baseAttackDamage * GetStats().damageMultiplier);
            other.GetComponent<Fighter>().DealDamage(damageToDeal);
        }
    }

    public virtual void OnRoundStart()
    {
        roundRunning = true;
        animator.enabled = true;
        rigidbody.bodyType = RigidbodyType2D.Dynamic;
        maxHealth = currentHealth = Mathf.RoundToInt(baseHealth * GetStats().healthMultiplier);
        animator.Play("Idle");
        healthBarController.Reset();
        initalPosition = transform.position;
    }

    public virtual void OnRoundEnd(bool won)
    {
        roundRunning = false;
        animator.enabled = false;
        timeSinceLastAttack = float.PositiveInfinity;
        rigidbody.velocity = Vector2.zero;
        rigidbody.bodyType = RigidbodyType2D.Static;
        transform.position = initalPosition;
        transform.localScale = initalScale;
        gameObject.layer = initialLayer;
        spriteRenderer.sprite = initialSprite;
        callbacks.Clear();
        healthBarController.SetHealthBarEnabled(false);
    }

    protected virtual void OnDestroy()
    {
        RoundController.instance.roundCallbacks.Remove(this);
    }
}