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

// Each character has two monobehaviors, 
// a generic character that responds to 
// inputs and an input behavior. The character
// has no knowledge of what its input type is.
//
// Input behaviors for AI types are: 
// AiGeneric, AiAlly, AiZombie.
//
// Input behaviors for player types are:
// InputLocal, InputRemote.
//
// AiGenric handles shared behaviors.

public enum Faction {
    Human,
    Zombie,
    Count
};

public enum Behavior {
    Hide,
    Protect,// AiAlly controls this behavior
    Idle,
    MakeSpace,
    Scout,
    Chase,
    Attack,
    Eat,    // AiZombie controls this behavior
    TakeHit,
    Die,     // behaviors after this are only for nonalive characters
    Sink,
    ReadyToSpawn,
};

public class AiGeneric : MonoBehaviour {

    static List<GameCharacter>[] ThreatList = null;
    public static void AddThreat(GameCharacter character, Faction faction) {
        List<GameCharacter> list = ThreatList[(int)faction];
        
        if (!list.Contains(character))
            list.Add(character);
    }
    public static void RemoveThreat(GameCharacter character, Faction faction) {
        List<GameCharacter> list = ThreatList[(int)faction];
        
        if (list.Contains(character))
            list.Remove(character);
    }
    public static GameCharacter FindClosest(Vector3 position, Faction faction, float range) {
        List<GameCharacter> list = ThreatList[(int)faction];

        GameCharacter retval = null;
        float closest = range;

        foreach (GameCharacter character in list) {
            float dist = Vector3.Distance(position, character.Position);
            if (dist < closest) {
                dist = closest;
                retval = character;

                if (dist < 2f) // close enough!
                    return retval;
            }
        }

        return retval;
    }
    public static void PauseAll(bool paused) {
        foreach(List<GameCharacter> list in ThreatList) {
            foreach (GameCharacter character in list) {
                character.Pause = paused;
            }
        }
    }

    // Behavior values
    float behaviorTime;
    protected float BehaviorTime { get {return Time.time  behaviorTime; }}
    Behavior behavior = Behavior.ReadyToSpawn;
    public Behavior Behavior { get { return behavior; } set { behavior = value; behaviorTime = Time.time; } }

    // customizable values
    protected float AttackRange = 4f;
    protected float DetectionRange = 999f;
    protected float ChaseDistance = 40f;
    protected float UpdateRate = 0.1f;

    protected GameCharacter character = null;
    protected GameCharacter target = null;
    protected Faction targeting = Faction.Count;

    void Awake () {
        if (ThreatList == null) {
            ThreatList = new List<GameCharacter>[(int)Faction.Count];
            for (int i = 0; i < (int)Faction.Count; i++) {
                ThreatList[i] = new List<GameCharacter>();
            }
        }
    }

    // Use this for initialization
    public virtual void Start () {
        character = gameObject.GetComponent<GameHuman>();
        targeting = Faction.Zombie;
        if (character == null) {
            character = gameObject.GetComponent<GameZombie>();
            targeting = Faction.Human;
        }
        if (character == null) {
            character = gameObject.GetComponent<GameCharacter>();
        }

        //StartCoroutine(UpdateBehavior());
    }

    void Update() {
        if (GameManager.Instance.inGame
            && (BehaviorTime > UpdateRate || Behavior == Behavior.Sink))
        {
            DoBehavior();
        }
    }

//    private IEnumerator UpdateBehavior() {
//        for(;;) {
//            if (this is AiAlly)
//                Debug.Log(Ally Behavior:  + Behavior.ToString());
//            if (GameManager.Instance.inGame && character.isAlive)
//                DoBehavior();
//
//            if (Behavior == Behavior.Sink)
//                yield return new WaitForSeconds(0.01f);
//            else
//                yield return new WaitForSeconds(UpdateRate);
//        }
//    }

    // Update is called once per frame
    public virtual void DoBehavior () {
        switch(behavior)
        {
        case Behavior.Hide: break;
        case Behavior.Idle:
        {
            if (BehaviorTime > 1f)
            {
                Behavior = Behavior.Scout;
            }
            else
            {
                target = FindTarget(targeting, DetectionRange);
                if (target != null)
                    Behavior = Behavior.Chase;
            }
        }break;
        case Behavior.MakeSpace:
        {
            if (BehaviorTime > 2f)
            {
                Behavior = Behavior.Idle;
            }
        } break;
        case Behavior.Scout: 
        {
            Vector3 origin = character.Position;
            
            if (Random.Range(0, 10) == 0)
            {
                target = FindTarget(targeting, DetectionRange);
            }
            
            origin.+= Random.Range(10f, 10f);
            origin.+= Random.Range(10f, 10f);

            character.Destination = origin;
            Behavior = Behavior.Idle;
        }break;
        case Behavior.Chase: 
        {
            if (target == null) {
                Behavior = Behavior.Idle;
                break;
            }

            float distance = Vector3.Distance(character.Position, target.Position);

            if (distance < AttackRange)
            {
                Behavior = Behavior.Attack;
            }
            else if (distance > ChaseDistance)
            {
                Behavior = Behavior.Idle;
            }
            else 
            {
                character.Destination = target.Position;
            }
        } break;
        case Behavior.Attack: 
        {
            if (target == null)
            {
                Behavior = Behavior.Idle;
                break;
            }
            character.Destination = target.Position;
            character.TryStrike();
            Behavior = Behavior.Eat;
        } break;
        case Behavior.Eat:
        {
        } break;
        case Behavior.Die: 
        {
            if (BehaviorTime > 2f)
            {
                Behavior = Behavior.Sink;
            }
        } break;
        case Behavior.Sink:
        {
            // sink into the ground
            Vector3 origin = transform.position;
            origin.-= Time.deltaTime * 0.6f;
            transform.position = origin;
            if (BehaviorTime > 3f)
            {
                character.DeSpawn(new Vector3(0f, 100f, 0f));
            }
        } break;
        case Behavior.ReadyToSpawn: break;

        default: Behavior = Behavior.Idle; break;
        }
    }

    protected GameCharacter FindTarget(Faction faction, float detectRange) {
        float closest = detectRange;
        GameCharacter value = null;
        foreach (GameCharacter threat in ThreatList[(int)faction]) {
            float dist = Vector3.Distance(threat.Position, character.Position);
            if (dist < closest && threat != character) {
                value = threat;
                closest = dist;
            }
        }
        return value;
    }

    public virtual void UpdateStats(CharacterStatistics statistics) {}
}
// AiZombie is a simple class. It just
// likes  to eat brains.
public class AiZombie : AiGeneric {

    Animator animator = null;

    // Use this for initialization
    public override void Start () {
        base.Start();
        animator = GetComponent<Animator>();
    }
    
    // Update is called once per frame
    public override void DoBehavior () {
        if (Behavior == Behavior.Eat) {
            if (target == null)
            {
                Behavior = Behavior.Idle;
                return;
            }
            character.Destination = target.Position;

            if (BehaviorTime > 0.45f)
            {
                AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0);
                if (info.IsTag(Attack1)
                       && Vector3.Distance(character.Position, target.Position) < AttackRange) 
                {
                    target.TakeDamage(character.Power, character.Position);
                    Behavior = Behavior.Idle;
                }
            }
                
            if (BehaviorTime > 0.5f)
                Behavior = Behavior.Idle;

        }

        base.DoBehavior();
    }
}

// AiAlly has special behaviors for killing
// zombies as well as protecting the player

public class AiAlly : AiGeneric {

    float FollowDistance = 10f;
    float SettleDistance = 8f;
    float BreakoffDistance = 14f;

    // Use this for initialization
    public override void Start () {

        UpdateRate = 0.02f;
        Behavior = Behavior.Idle;
        AttackRange = 4f;
        DetectionRange = 15f;
        ChaseDistance = 15f;
        FollowDistance = 10f;
        BreakoffDistance = 14f;

        base.Start();
        StartCoroutine(UpdateDialog());
    }

    private IEnumerator UpdateDialog() {
        for(;;) {
            MissionData mission = GameManager.Instance.CurrentMission;

            if (mission != null && character != null) {
                string dialog = Random.value < 0.25f ? mission.GameDialog1 : Random.value < 0.25 ? mission.GameDialog2 : null;
                
                GameHuman human = character as GameHuman;

                if (human != null) {
                    human.ShowSpeech(dialog);
                }
            }

            yield return new WaitForSeconds(5f);
        }
    }

    public override void DoBehavior () {
        switch (Behavior)
        {
        case Behavior.Idle:
        {
            float followDist = character.isAttacking ? BreakoffDistance : FollowDistance;

            GameCharacter ally = FindTarget(Faction.Human, 999f);
            if (ally && Vector3.Distance(ally.Position, character.Position) > followDist) 
            {
                Behavior = Behavior.Protect;
            }
            else
            {
                target = FindTarget(targeting, DetectionRange);
                if (target != null)
                    Behavior = Behavior.Chase;
            }
        }
            break;
        case Behavior.Protect:
        {
            GameCharacter ally = FindTarget(Faction.Human, 999f);
            if (ally && Vector3.Distance(ally.Position, character.Position) > SettleDistance) 
            {
                character.Destination = ally.Position;
            }
            else
            {
                character.Destination = character.Position;
                Behavior = Behavior.Idle;
            }
        }
            break;
        case Behavior.Attack: 
            {
                if (target == null)
                {
                    Behavior = Behavior.Idle;
                    break;
                }
                character.Destination = target.Position;
                if (target.CompareTag(Crawler)) {
                    character.TryStrike(DownStrike);
                } else {
                    character.TryStrike();
                }
                Behavior = Behavior.Idle;
            } break;

        default:
            base.DoBehavior();
            break;
        }
    }

    public override void UpdateStats(CharacterStatistics statistics) {
        AttackRange = 4f + (statistics.Reach / 5);
        base.UpdateStats(statistics);
    }
}