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

using HamJam.Character;

public class CharacterInteractionScript : MonoBehaviour {

    [SerializeField]
    private float _movementScalar;
    [SerializeField]
    private float _maxSpeed;
    [SerializeField]
    private float _health;
    [SerializeField]
    private CharacterTypes _charType;
    [SerializeField]
    private float _movementRandomizationScalar;
    [SerializeField]
    private List<AttackTypes> _attacksList;

    private Rigidbody2D _rBody;
    private float _initialHealth;
    private AttackDataManagerScript _attackDataManager;
    private ScoreboardManagerScript _scoreManager;
    private bool _canAttack = true;
    private bool _isAlive = true;
    private Animator _anim;
    private SoundManager _soundManager;

    void Awake() {
        this._anim = this.GetComponent<Animator>();
        this._rBody = this.GetComponent<Rigidbody2D>();
        this._initialHealth = this._health;
        List<AttackTypes> distinct = new List<AttackTypes>();
        foreach (var attack in this._attacksList) {
            if (!distinct.Contains(attack)) {
                distinct.Add(attack);
            }
        }
        this._attacksList = distinct;
        this._attackDataManager = GameObject.FindGameObjectWithTag("AttackDataManager").GetComponent<AttackDataManagerScript>();
        this._scoreManager = GameObject.FindGameObjectWithTag("ScoreboardManager").GetComponent<ScoreboardManagerScript>();
        this._soundManager = GameObject.FindGameObjectWithTag("SoundManager").GetComponent<SoundManager>();
    }

    public void Move(Vector2 direction) {
        // For now, this is pretty simple: normalize the vector and scale it
        this._rBody.AddForce(direction.normalized * this.MovementScalar);
        // If the character's velocity is faster than its MaxSpeed, adjust it
        if (this._rBody.velocity.magnitude > this.MaxSpeed) {
            this._rBody.velocity = this._rBody.velocity.normalized * this.MaxSpeed;
        }
    }

    public void Halt() {
        // For now, don't do anything special, (negative forces) just immediately stop
        this._rBody.velocity = Vector2.zero;
    }

    public void Look(Vector2 direction) {
        // For now, nothing
    }

    public void PerformAttack(Vector2 direction, AttackTypes attackType) {
        if (!this._canAttack || !this._isAlive) {
            return;
        }
        if (!this._attacksList.Contains(attackType)) {
            throw new ArgumentException("The provided type (namely '" + attackType + "') is not a valid " +
                                        "attack for this character.");
        }
        this._canAttack = false;
        float attackTimeout = this._attackDataManager.GetAttackTimeout(attackType);
        if (0 >= attackTimeout) {
            throw new ArgumentException("The provided timeout (" + attackTimeout + ") is less than (or " +
                                        "equal to) zero.");
        }
        Invoke("EnableAttacking", attackTimeout);
        direction.Normalize();
        float directionAngle;
        Quaternion directionQuaternion;
        GameObject projectile;
        switch (attackType) {
            case AttackTypes.WizardFireBall:
                directionAngle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
                directionQuaternion = Quaternion.AngleAxis(directionAngle, Vector3.forward);
                projectile = this._attackDataManager.GetAttackPrefab(attackType);
                Instantiate(projectile, this._rBody.position, directionQuaternion);
                this._anim.SetBool("Attacking", true);
                this._soundManager.PlayAttackSound(attackType);
                break;
            case AttackTypes.RaidBossFireBarrage:
                StartCoroutine(this.CircularAttackHelper(270f, 1000f, 30f, 0.02f, AttackTypes.RaidBossFireBarrage));
                this._anim.SetBool("Attacking", true);
                this._soundManager.PlayAttackSound(attackType);
                break;
            case AttackTypes.WizardHealingBolt:
                directionAngle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
                directionQuaternion = Quaternion.AngleAxis(directionAngle, Vector3.forward);
                projectile = this._attackDataManager.GetAttackPrefab(attackType);
                Instantiate(projectile, this._rBody.position, directionQuaternion);
                this._anim.SetBool("Attacking", true);
                this._soundManager.PlayAttackSound(attackType);
                break;
            case AttackTypes.RaidBossSword:
                //Nothing to do
                break;
            case AttackTypes.SpiritHit:
                this._anim.SetBool("Attacking", true);
                this._soundManager.PlayAttackSound(attackType);
                break;
            case AttackTypes.SpitterSkag:
                directionAngle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
                directionQuaternion = Quaternion.AngleAxis(directionAngle, Vector3.forward);
                projectile = this._attackDataManager.GetAttackPrefab(attackType);
                Instantiate(projectile, this._rBody.position, directionQuaternion);
                this._anim.SetBool("Attacking", true);
                this._soundManager.PlayAttackSound(attackType);
                break;
            case AttackTypes.CrawlerHit:
                this._anim.SetBool("Attacking", true);
                this._soundManager.PlayAttackSound(attackType);
                break;
            case AttackTypes.WarriorSwing:
                float attackRange = this._attackDataManager.GetAttackRange(AttackTypes.WarriorSwing);
                int layerMask = 1 << LayerMask.NameToLayer("RaidBoss");
                RaycastHit2D rayCast = Physics2D.Raycast(this._rBody.position, direction, attackRange, layerMask);
                if (rayCast.collider != null) {
                    var charInteract = rayCast.collider.gameObject.GetComponent<CharacterInteractionScript>();
                    if (charInteract != null) {
                        charInteract.TakeDamage(this._attackDataManager.GetAttackDamage(AttackTypes.WarriorSwing));
                    }
                }

                this._anim.SetBool("Attacking", true);
                this._soundManager.PlayAttackSound(attackType);
                break;
            case AttackTypes.ArrowToTheKnee:
                directionAngle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
                directionQuaternion = Quaternion.AngleAxis(directionAngle, Vector3.forward);
                projectile = this._attackDataManager.GetAttackPrefab(attackType);
                Instantiate(projectile, this._rBody.position, directionQuaternion);
                this._anim.SetBool("Attacking", true);
                this._soundManager.PlayAttackSound(attackType);
                break;
            default:
                throw new NotImplementedException("The type provided (namely '" + attackType + "')is not handled.");
        }
    }

    private IEnumerator CircularAttackHelper(float degreeStart, 
                                             float degreeEnd, 
                                             float degreeChange, 
                                             float timeDelay, 
                                             AttackTypes attackType) {
        if (0 >= degreeChange) {
            throw new ArgumentException("The degreeChange parameter was less than zero.");
        } else if (degreeStart >= degreeEnd) {
            throw new ArgumentException("The parameter degreeStart MUST be smaller than degreeEnd");
        } else if (0 >= timeDelay) {
            throw new ArgumentException("The timeDelay parameter was less than zero.");
        }

        float currentDegree = degreeStart;
        while (currentDegree < degreeEnd) {
            GameObject projectile = this._attackDataManager.GetAttackPrefab(attackType);
            Instantiate(projectile, this._rBody.position, Quaternion.Euler(new Vector3(0, 0, currentDegree)));
            currentDegree += degreeChange;
            yield return new WaitForSeconds(timeDelay);
        }
    }

    public void TakeDamage(float amountOfDamage) {
        if (!this._isAlive) {
            return;
        }
        // Check to make sure the amount of damage is valid
        if (0 >= this._health) {
            amountOfDamage = 0;
        } else if (this._health - amountOfDamage > this._initialHealth) {
            amountOfDamage = this._initialHealth - this._health;
        }

        if (CharacterTypes.Hero == this._charType) {
            this._scoreManager.IncreaseScore(Mathf.Min(amountOfDamage, this._health));
        }
        this._health -= amountOfDamage;
        if (0 >= this._health) {
            this.OnCharacterDeath();
        }
    }

    private void EnableAttacking() {
        this._canAttack = true;
    }

    private void OnCharacterDeath() {
        this._isAlive = false;
        foreach (var collider in this.GetComponents<Collider2D>()) {
            collider.enabled = false;
        }
        this._rBody.gravityScale = 40f;
        this.transform.rotation = Quaternion.Euler(new Vector3(0, 0, -90f));
        Invoke("Suicide", 5f);
    }

    private void Suicide() {
        this._rBody.gravityScale = 0f;
        this.Halt();
        //Destroy(this.gameObject);
    }

    public float MovementScalar {
        get {
            return this._movementScalar;
        }
    }
    public float MaxSpeed {
        get {
            return this._maxSpeed;
        }
    }
    public List<AttackTypes> AttackList {
        get {
            return this._attacksList;
        }
    }
    public float Health {
        get {
            return this._health;
        }
    }
    public float InitialHealth {
        get {
            return this._initialHealth;
        }
    }
    public float PercentageHealth {
        get {
            return (this._health / this._initialHealth);
        }
    }
    public CharacterTypes CharType {
        get {
            return this._charType;
        }
    }
    public bool CanAttack {
        get {
            return this._canAttack;
        }
    }
    public float MovementRandomizationScalar {
        get {
            return this._movementRandomizationScalar;
        }
    }
    public bool IsAlive {
        get {
            return this._isAlive;
        }
    }
}