using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Assets.Scripts.Battle;
using Assets.Scripts.UI;
using Assets.Scripts.UI.Placeholder;
using UnityEngine;
using Random = UnityEngine.Random;

namespace Assets.Scripts.Player
{
    public class Mob {
        private readonly List<Buff> _buffs = new List<Buff>();

        public IMobDisplay GraphicalListener { get; set; }

        private Allegiance _allegiance;

        private int _health;
        private int _maxHealth;
        private int _sanity;
        private int _maxSanity;

        internal int Damage;
        internal int Range;
        internal float Accuracy;
        internal float Cooldown;

        private float _timeSinceAttack;
        private float _battlePosition;
        private int _battlePositionTarget;

        public string Name;
        public int SpriteIndex;
        
        public Mob(Allegiance allegiance, int health, int sanity) {
            _allegiance = allegiance;

            _health = _maxHealth = health;
            _sanity = _maxSanity = sanity;
            Damage = 2;
            Range = 2;
            Accuracy = Random.Range(0.6f, 0.8f);
            Cooldown = Random.Range(3.0f, 4.0f);
        }

        public void InitializeBattle(int index) {
            BattleIndex = index;
            _timeSinceAttack = 0;
        }

        public void SetBattlePosition(int pos, bool teleport = false) {
            if (teleport) {
                _battlePosition = pos;
            }
            _battlePositionTarget = pos;
        }

        public bool UpdateBattle(float time, BattleEngine battle) {
            if (IsAlive()) {
                UpdatePosition(time);
                _timeSinceAttack += time;
                if (_timeSinceAttack >= GetAttribute(MobAttribute.Cooldown)) {
                    var target = battle.FindTarget(this);
                    if (InRange(target)) {
                        Attack(target);
                        GraphicalListener.HideNoRange();                  
                    }
                    else
                    {
                        GraphicalListener.ShowNoRange();
                    }
                }
                return true;
            } else {
                return false;
            }
        }

        private void UpdatePosition(float time) {
            const float speed = 2.0f;
            float ds = time * speed;
            if (GraphicalListener != null) {
                GraphicalListener.SetMoving(!Mathf.Approximately(_battlePosition, _battlePositionTarget));
            }
            _battlePosition = Mathf.MoveTowards(_battlePosition, _battlePositionTarget, ds);
        }

        private bool InRange(Mob target) {
            return target != null && Mathf.Abs(target._battlePosition - _battlePosition) <= GetAttribute(MobAttribute.Range) + 0.001f;
        }

        public bool Attack (Mob mob) {
            _timeSinceAttack = 0;
            if (Random.Range(0.0f, 1.0f) < GetAttribute(MobAttribute.Accuracy)) {
                GraphicalListener.Attack();
                mob.TakeDamage(GetAttribute(MobAttribute.Damage));
                return true;
            } else {
                GraphicalListener.Miss();
                return false;
            }
        }

        public bool IsAlive() {
            return GetAttribute(MobAttribute.Health) > 0;
        }

        public bool IsDead () {
            return !IsAlive();
        }

        public bool IsSane () {
            return GetAttribute(MobAttribute.Sanity) > 0;
        }

        public bool IsCrazy () {
            return !IsSane();
        }

        public void TakeDamage(int damage) {
            if (damage > 0) {
                if (GraphicalListener != null) {
                    GraphicalListener.TakeHit();
                }
                _health -= damage;
                if (IsDead() && GraphicalListener != null) {
                    GraphicalListener.Die();
                }
            }
        }

        public void LoseSanity (int amount) {
            if (amount > 0) {
                _sanity -= amount;
                if (IsCrazy()) {
                    TakeDamage(999);
                }
            }
        }

        public Allegiance GetAllegiance() {
            return _allegiance;
        }

        public float GetBattlePosition() {
            return _battlePosition;
        }

        public int GetTargetBattlePosition() {
            return _battlePositionTarget;
        }

        public void MoveBy(int i) {
            _battlePositionTarget += i;
        }

        public int BattleIndex { get; private set; }

        public float GetHealthPercent() {
            return Mathf.Clamp01(GetAttribute(MobAttribute.Health) / (float) GetAttribute(MobAttribute.MaxHealth));
        }

        public float GetCooldownPercent () {
            return Mathf.Clamp01(_timeSinceAttack / GetAttribute(MobAttribute.Cooldown));
        }

        public void Heal(int amount) {
            if (amount > 0 && IsAlive() && IsSane()) {
                _health = Mathf.Min(GetAttribute(MobAttribute.MaxHealth), _health + amount);
            }
        }

        public void HealSanity (int amount) {
            if (amount > 0 && IsAlive() && IsSane()) {
                _sanity = Mathf.Min(GetAttribute(MobAttribute.MaxSanity), _sanity + amount);
            }
        }

        public int GetAttribute (MobAttribute attr) {
            return Mathf.RoundToInt(GetAttributeFloat(attr));
        }

        public float GetAttributeFloat (MobAttribute attr) {
            float value = GetBaseAttribute(attr);

            // All absolutes first...
            foreach (var buff in _buffs) {
                if (buff.Attribute == attr && buff.IsAbsolute) {
                    value = buff.Value;
                }
            }

            // Then deltas
            foreach (var buff in _buffs) {
                if (buff.Attribute == attr && !buff.IsAbsolute) {
                    value += buff.Value;
                }
            }

            return value;
        }

        private float GetBaseAttribute(MobAttribute attr) {
            switch (attr) {
                case MobAttribute.Accuracy:
                    return Accuracy;
                case MobAttribute.Cooldown:
                    return Cooldown;
                case MobAttribute.Damage:
                    return Damage;
                case MobAttribute.Health:
                    return _health;
                case MobAttribute.MaxHealth:
                    return _maxHealth;
                case MobAttribute.MaxSanity:
                    return _maxSanity;
                case MobAttribute.Range:
                    return Range;
                case MobAttribute.Sanity:
                    return _sanity;
            }
            return 0;
        }

        public void AddBuff(Buff buff) {
            _buffs.Add(buff);
        }

        public void Convert(Allegiance newAllegiance) {
            if (_allegiance != newAllegiance) {
                _allegiance = newAllegiance;
                Flipped = true;
            }
        }

        public bool Flipped { get; set; }
    }
}
