using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DataStructures;

namespace Sentience
{
    /// <summary>
    /// The entity states available.
    /// </summary>
    public enum States
    {
        Idle,
        Patrolling,
        Persuing,
        SeekingGoal,
        Diverting,
        Sacrificing,
        ConsumeBrains
    }

    /// <summary>
    /// The team tactics available.
    /// </summary>
    public enum Tactic
    {
        Idle,
        Moderate,
        Defensive,
        Suicidal,
    }

    /// <summary>
    /// IName provides an interface to a class to allow it to report its name.
    /// </summary>
    public interface IName
    {
        String Name { get; }
    }

    /// <summary>
    /// IBehavior implementors must contain a Perform method that controls the behaviour
    /// of the entity passed to it. The implementor must also have a unique ID.
    /// 
    /// Design
    /// ******
    /// 
    /// Behaviors work based on a hierarchical Finite State Machine (FSM). Each team is
    /// assigned a behavior that governs all the entities within that team.
    /// 
    /// Each behavior reacts differently based on what tactic has been assigned to it. For example,
    /// a defensive tactic will cause the entities imbued with a specific behavior to act with a defensive
    /// attitude.
    /// 
    /// [TEAM] --Has 1--> [BEHAVIOR] --Has Many--> [TACTIC] --Affects Many--> [STATE] 
    ///   |                                                                      ^
    ///   |                                                                      |
    ///   |                                                                    Has 1
    ///   |                                                                      |
    ///    --------------------------Has Many---------------------------->   [ENTITY]
    ///                                                           
    /// Whereas a tactic is team dependant, state is assigned to each entity within a team. A state is the lowest
    /// level in the FSM and is the current operation of the each entity. State can (and should be) affected by
    /// the team's tactic.
    /// </summary>
    public interface IBehavior : IName
    {
        int ID { get; }
        void Perform(Entity CurrentEntity);
    }

    /// <summary>
    /// Absolute basic behaviour, i.e. nothing. Teams and entities will do nothing given this
    /// behaviour.
    /// </summary>
    public class Void : IBehavior
    {
        public Void(Map map)
        {
            Map = map;
        }

        public String Name { get { return "[None]"; } }
        public int ID { get { return 0; } }
        private Map Map;

        public void Perform(Entity CurrentEntity)
        {
            
        }
    }

    /// <summary>
    /// Guard Behaviour
    /// ***************
    /// 
    /// A Guard's main objective is to seek out the Goal-Seekers an kill them. 
    /// 
    /// Tactic Behaviour
    /// ----------------
    /// * Idle - Do Nothing.
    /// * Moderate - State = Patrolling until a Goal-Seeker is discovered, then State = Pursuing.
    /// * Defensive - Find a goal point on the map and patrol round it until Goal-Seeker
    ///   is discovered, then State = Pursuing. Defensive has smaller view distance in order
    ///   to keep Guards at their post.
    /// * Suicidal - Find Goal-Seeker (Regardless of view distance) and goto their current position.
    /// 
    /// State Behaviour
    /// ---------------
    /// * Idle - Do Nothing.
    /// * Patrolling - Move randomly one node at a time across the map.
    /// * Pursuing - Move to target entity's current node. Kill entity if the target entity is
    ///   in the list of the current node's children.
    /// </summary>
    public class Guard : IBehavior
    {
        public Guard(Map map)
        {
            Map = map;
        }

        public String Name { get { return "Guard"; } }
        public int ID { get { return 1; } }
        private Map Map;

        public void Perform(Entity CurrentEntity)
        {
            // Set the view distance
            CurrentEntity.Team.ViewDistance = Properties.Settings.Default.ViewDistance;

            // Seed a random number based on entity ID.
            Random randomNumber = new Random(System.Environment.TickCount * (CurrentEntity.ID + 1));

            // Tactic modes
            switch (CurrentEntity.Team.Tactic)
            {
                case Tactic.Idle:
                    CurrentEntity.State = States.Idle;
                    break;
                case Tactic.Defensive:
                    if (CurrentEntity.State == States.Idle)
                        CurrentEntity.State = States.Patrolling;

                    // Half the view distance to keep the Guard at it's post.
                    CurrentEntity.Team.ViewDistance /= 2;
                    break;
                default:
                    if (CurrentEntity.State == States.Idle)
                        CurrentEntity.State = States.Patrolling;
                    break;
            }

            switch (CurrentEntity.State)
            {
                case States.Idle:
                    break;
                case States.Patrolling:

                    bool foundEnemy = false;
                    List<Node<IBlock>> patrolNodes = new List<Node<IBlock>>();

                    // If entity has nothing to do..
                    if (CurrentEntity.Waypoints.Count == 0)
                    {
                        switch (CurrentEntity.Team.Tactic)
                        {
                            case Tactic.Defensive:

                                // Find the goal points on the map and go and patrol around it and it's child nodes.
                                List<Node<IBlock>> goalPoints = new List<Node<IBlock>>();

                                foreach (Node<IBlock> Node in Map.Graph.Nodes)
                                    if ((Node.Data is GoalBlock))
                                        goalPoints.Add(Node);

                                if (goalPoints.Count != 0)
                                    patrolNodes = Map.Graph.FindChildNodes(goalPoints[CurrentEntity.ID % goalPoints.Count]);

                                break;
                            case Tactic.Suicidal:
                                // Find all the Goal-Seekers and go to where they CURRENTLY are.
                                foreach (Entity Enemy in Map.Entities)
                                {
                                    if (Enemy.Team.Behavior is GoalSeeker)
                                        patrolNodes.Add(Enemy.CurrentNode);
                                }

                                break;
                            default:
                                // Just go to a random child of the current node (Patrolling behavior).
                                patrolNodes = Map.Graph.FindChildNodes(CurrentEntity.CurrentNode);
                                break;
                        }

                        // If entity has nowhere to go (e.g. no goal points of Goal-Seekers on the map)..
                        if (patrolNodes.Count == 0)
                        {
                            // Just become boring :)
                            CurrentEntity.Team.Tactic = Tactic.Moderate;
                            CurrentEntity.PerformBehavior();
                        }
                    }

                    // Check if entity has found any Goal-Seekers
                    foreach (Entity Enemy in Map.Entities)
                    {
                        if (Enemy.Team.Behavior is GoalSeeker)
                        {
                            // Check if the entity has been spawned for long enough.. Sometimes a Guard may camp on
                            // a Goal-Seeker spawn point, this causes the Goal-Seeker to be killed over and over again
                            // without having a chance to leave the spawn area (This can also trigger an stack overflow),
                            // so newly spawned entities are given a time whereby they cannot be killed.
                            if ((DateTime.Now - Enemy.Spawn).Milliseconds > Defaults.SpawnInvincibilityTime)
                            {
                                // If the euclidean distance is less than the view distance..
                                if (Utils.Distance(CurrentEntity.CurrentNode.Location, Enemy.CurrentNode.Location) < CurrentEntity.Team.ViewDistance)
                                {
                                    // Pursue the entity
                                    CurrentEntity.Target = Enemy;
                                    CurrentEntity.State = States.Persuing;
                                    foundEnemy = true;

                                    // Assuming team communication is not blocked, the entity will alert all other members
                                    // of its team (That is to say just its team, and not all teams with guard behavior) of the
                                    // location if the entity in its team is not pursuing a Goal-Seeker of it's own.
                                    if (!Properties.Settings.Default.BlockTeamCommunication)
                                    {
                                        foreach (Entity Entity in Map.Entities)
                                        {
                                            if (Entity.Team == CurrentEntity.Team && Entity != CurrentEntity)
                                                if (Entity.State == States.Patrolling)
                                                {
                                                    Entity.Target = Enemy;
                                                    Entity.State = States.Persuing;
                                                    Entity.PerformBehavior();
                                                }
                                        }
                                    }

                                    CurrentEntity.PerformBehavior();
                                }
                            }
                        }
                    }

                    // Finally, move the entity to the chosen location..
                    if (!foundEnemy && CurrentEntity.Waypoints.Count == 0)
                        Map.Move(CurrentEntity, patrolNodes[randomNumber.Next(patrolNodes.Count)]);

                    break;
                case States.Persuing:

                    // If the entity is going somewhere, go and meet it there ;)
                    if (CurrentEntity.Target.Waypoints.Count > 0)
                        Map.Move(CurrentEntity, CurrentEntity.Target.Waypoints[CurrentEntity.Target.Waypoints.Count - 1]);
                    // Else just go to its current location
                    else
                        Map.Move(CurrentEntity, CurrentEntity.Target.CurrentNode);

                    // Kill the enemy if it is close enough..
                    if (CurrentEntity.CurrentNode == CurrentEntity.Target.CurrentNode
                        || Map.Graph.FindChildNodes(CurrentEntity.Target.CurrentNode).Contains(CurrentEntity.CurrentNode))
                    {
                        Map.Kill(CurrentEntity.Target, CurrentEntity);
                        CurrentEntity.Team.Score++;
                        CurrentEntity.State = States.Patrolling;
                        CurrentEntity.PerformBehavior();
                    }

                    break;
            }
        }
    }

    /// <summary>
    /// Goal-Seeker Behaviour
    /// ***************
    /// 
    /// A Goal-Seeker's main objective is to seek out the goals without getting killed
    /// by the Guards.
    /// 
    /// Tactic Behaviour
    /// ----------------
    /// * Idle - Do Nothing.
    /// * Moderate - State = SeekingGoal.
    /// * Defensive - Make half of the team State = SeekingGoal, the other half State = Diverting.
    /// * Suicidal - Make half of the team State = SeekingGoal, the other half State = Sacrificing.
    /// 
    /// State Behaviour
    /// ---------------
    /// * Idle - Do Nothing.
    /// * SeekingGoal - Head towards specific goal.
    /// * Diverting - Wander across the map to alert the Guards.
    /// * Sacrificing - Find Guard (Regardless of view distance) and goto their current position.
    /// </summary>
    public class GoalSeeker : IBehavior
    {
        public GoalSeeker(Map map)
        {
            Map = map;
        }

        public String Name { get { return "Goal-Seeker"; } }
        public int ID { get { return 2; } }
        private Map Map;

        public void Perform(Entity CurrentEntity)
        {
            // Set the view distance.
            CurrentEntity.Team.ViewDistance = Properties.Settings.Default.ViewDistance;
            // Seed a random number based on entity ID.
            Random randomNumber = new Random(System.Environment.TickCount * (CurrentEntity.ID + 1));

            switch (CurrentEntity.Team.Tactic)
            {
                case Tactic.Idle:
                    CurrentEntity.State = States.Idle;
                    break;
                case Tactic.Defensive:
                    // Get half of the team to seek goals, the other half to divert the Guards..
                    CurrentEntity.State = States.SeekingGoal;

                    if (CurrentEntity.Team.GetMemberCount(Map.Entities) > 1)
                    {
                        if (CurrentEntity.ID % 2 != 0)
                            CurrentEntity.State = States.Diverting;
                    }

                    break;
                case Tactic.Suicidal:
                    // Get half of the team to seek goals, the other half to sacrifice themselves..
                    CurrentEntity.State = States.SeekingGoal;

                    if (CurrentEntity.Team.GetMemberCount(Map.Entities) > 1)
                    {
                        if (CurrentEntity.ID % 2 != 0)
                            CurrentEntity.State = States.Sacrificing;
                    }

                    break;
                default:
                    CurrentEntity.State = States.SeekingGoal;
                    break;
            }

            switch (CurrentEntity.State)
            {
                case States.Idle:
                    break;

                case States.SeekingGoal:

                    // Find all the goal nodes on the map..
                    List<Node<IBlock>> goalNodes = new List<Node<IBlock>>();

                    foreach (Node<IBlock> Node in Map.Graph.Nodes)
                        if ((Node.Data is GoalBlock))
                            goalNodes.Add(Node);

                    // If there aren't any, panic (i.e. break)
                    if (goalNodes.Count == 0)
                        break;

                    switch (CurrentEntity.Team.Tactic)
                    {
                        case Tactic.Defensive:

                            // Goal-Seekers that are playing it safe will try and head towards a goal that
                            // doesn't have a load of Guards near it..
                            Node<IBlock> goalNode;
                            bool goalSafe;

                            do
                            {
                                goalSafe = true;
                                goalNode = goalNodes[randomNumber.Next(goalNodes.Count)];

                                foreach (Entity Enemy in Map.Entities)
                                {
                                    if (Enemy.Team.Behavior is Guard)
                                        if (Utils.Distance(CurrentEntity.CurrentNode.Location, Enemy.CurrentNode.Location) < CurrentEntity.Team.ViewDistance)
                                            if (Utils.Distance(goalNode.Location, Enemy.CurrentNode.Location) < Enemy.Team.ViewDistance)
                                                goalSafe = false;
                                }

                            } while (!goalSafe);

                            Map.Move(CurrentEntity, goalNode);
                            break;
                        default:
                            // Any other tactic, just go for the goals..
                            Map.Move(CurrentEntity, goalNodes[randomNumber.Next(goalNodes.Count)]);
                            break;
                    }
                    break;
                case States.Diverting:

                   // Wander aimlessly across the map
                   List<Node<IBlock>> patrolNodes = Map.Graph.FindChildNodes(CurrentEntity.CurrentNode);

                   Map.Move(CurrentEntity, patrolNodes[randomNumber.Next(patrolNodes.Count)]);
                   break;
                case States.Sacrificing:

                   // Find a Guard (regardless of view distance) and head towards it.. 
                   List<Node<IBlock>> entities = new List<Node<IBlock>>();

                   foreach (Entity Enemy in Map.Entities)
                   {
                       if (Enemy.Team.Behavior is Guard)
                           entities.Add(Enemy.CurrentNode);
                   }

                   Map.Move(CurrentEntity, entities[randomNumber.Next(entities.Count)]);
                   break;
            }
        }
    }
    /// <summary>
    /// Zombie Behaviour
    /// ***************
    /// 
    /// A Zombie's main objective is to seek out any non-zombies and imbue them with zombie behavior.
    /// 
    /// Tactic Behaviour
    /// ----------------
    /// * Idle - Do Nothing.
    /// * Moderate - State = Patrolling.
    /// * Moderate - State = Patrolling.
    /// * Moderate - State = Patrolling.
    /// 
    /// State Behaviour
    /// ---------------
    /// * Idle - Do Nothing.
    /// * Patrolling - Head towards a non-zombie.
    /// * ConsumeBrains - Consume the entity's brains and make it a zombie.
    /// </summary>
    public class Zombie : IBehavior
    {
        public Zombie(Map map)
        {
            Map = map;
        }

        public String Name { get { return "Zombie"; } }
        public int ID { get { return 3; } }
        private Map Map;

        public void Perform(Entity CurrentEntity)
        {
            // Set the view distance
            CurrentEntity.Team.ViewDistance = Properties.Settings.Default.ViewDistance;
            // Seed a random number based on entity ID.
            Random randomNumber = new Random(System.Environment.TickCount * (CurrentEntity.ID + 1));

            switch (CurrentEntity.Team.Tactic)
            {
                case Tactic.Idle:
                    CurrentEntity.State = States.Idle;
                    break;
                default:
                    // If not idle, patrol..
                    if (CurrentEntity.State == States.Idle)
                        CurrentEntity.State = States.Patrolling;
                    break;
            }

            switch (CurrentEntity.State)
            {
                case States.Idle:
                    break;
                case States.Patrolling:

                    // Get the list of non-zonbies
                    List<Entity> entities = new List<Entity>();

                    foreach (Entity Enemy in Map.Entities)
                    {
                        if (!(Enemy.Team.Behavior is Zombie))
                            entities.Add(Enemy);
                    }

                    if (entities.Count == 0)
                    {
                        //List<Node<IBlock>> patrolNodes = Map.Graph.FindChildNodes(CurrentEntity.CurrentNode);
                        //Map.Move(CurrentEntity, patrolNodes[randomNumber.Next(patrolNodes.Count)]);

                        // Once everything is a zombie, just go idle and report that the zombie's have won.
                        CurrentEntity.Team.Tactic = Tactic.Idle;
                        Map.Report("The Zombies have consumed all.");
                    }
                    else
                    {
                        // Get a random entity from the list and consume it..
                        CurrentEntity.Target = entities[randomNumber.Next(entities.Count)];
                        CurrentEntity.State = States.ConsumeBrains;
                        CurrentEntity.PerformBehavior();
                    }
                    break;
                case States.ConsumeBrains:

                    // If the entity is going somewhere, go and meet it there ;)
                    if (CurrentEntity.Target.Waypoints.Count > 0)
                        Map.Move(CurrentEntity, CurrentEntity.Target.Waypoints[CurrentEntity.Target.Waypoints.Count - 1]);
                    // Else just go to its current location
                    else
                        Map.Move(CurrentEntity, CurrentEntity.Target.CurrentNode);

                    // Consume the enemy if it is close enough..
                    if (CurrentEntity.CurrentNode == CurrentEntity.Target.CurrentNode
                        || Map.Graph.FindChildNodes(CurrentEntity.Target.CurrentNode).Contains(CurrentEntity.CurrentNode))
                    {
                        Map.Report(CurrentEntity, "consumed the brains of", CurrentEntity.Target);
                        CurrentEntity.Target.Team = CurrentEntity.Team;

                        CurrentEntity.Team.Score++;
                        CurrentEntity.State = States.Patrolling;
                        CurrentEntity.PerformBehavior();
                    }

                    break;
            }
        }
    }
}
