using System;
using System.Collections.Generic;
using ECOT.Engine;
using ECOT.Engine.Graphics;
using ECOT.Game.Buildings;
using ECOT.Game.States;
using ECOT.Game.Tools;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using ECOT.Engine.Statistics;

namespace ECOT.Game
{
    /// <summary>
    /// 
    /// </summary>
    public enum GameSpeed
    {
        Pause = 0,
        Play = 1,
        Play2X = 2,
        Play4X = 4,
        Play8X = 8
    }

    public enum SimulationPool
    {
        Daily,
        Monthly,
        Yearly
    }

    public delegate void GameEventHandler();

    /// <summary>
    /// This is the most important class of the game. Here happens everything :)
    /// </summary>
    public class GameController : IUpdatable
    {
        #region Membres

        public static readonly object Lock = new object();
        private bool resetCursor = false;

        private Simulation simulation = new Simulation();

        private bool enabled = true;
        // Constantes de depart
        private int totalOfCitiesManaged = 0;
        private int numberOfCitiesManaged = 0;

        private Building selectedBuilding = null;
        private string mapName = null;
        private Map map = null;
        private string rootState;
        private BuildingFactory buildingFactory = new BuildingFactory();

        private Vector2 cameraSpeed = Vector2.Zero;
        private static readonly Vector2 MinCameraSpeed = new Vector2(10.0f);
        private static readonly Vector2 MaxCameraSpeed = new Vector2(100.0f);

        private List<Tool> tools = new List<Tool>();
        private Tool currentTool = null;
        private bool initialized = false;
        private bool enableInput = true;

        private GameSpeed speed = GameSpeed.Play4X;
        private float totalTime = 0.0f;
        private int gameDays = 0;
        private DateTime gameDate; 
        private float dayCounter = 0.0f;

        public float DayCounter
        {
            get { return this.dayCounter; }
        }

        public event GameEventHandler OnNewDay;
        public event GameEventHandler OnNewMonth;
        public event GameEventHandler OnNewYear;
        public event GameEventHandler OnSpeedChanged;
        
        private ISelectable currentSelectedItem = null;
        private ISelectable oldSelectedItem = null;
        private bool moveUnFreezeInput = true;

        private static GameController instance = null;


        #endregion

        #region Initialisation

        public GameController(string rootState, string mapName)
        {
            this.rootState = rootState;
            this.mapName = mapName;
            
            // Sauvegarde l'instance courante
            GameController.instance = this;
        }

        public void Initialize()
        {
            // Charge la map
            this.map = new Map(string.Format(@"Maps\{0}.etm", this.mapName));
            this.OnNewDay += this.map.DayChanged;

            // Initialization date départ
            this.gameDate = Convert.ToDateTime(this.map.StartDate);

            // Crée les templates de bâtiments
            this.CreateBuildingTemplates();
            CityBuilding.ElementsBack = Kernel.Instance.Medias.Get<Texture2D>(@"Textures\Buildings\ElementsBack");
            CityBuilding.ElementsFront = Kernel.Instance.Medias.Get<Texture2D>(@"Textures\Buildings\ElementsFront");

            // Initialise le prévisualisation des villes
            City.StreetsTexture = Kernel.Instance.Medias.Get<Texture2D>(@"Textures\Cities\Streets");
            // Crée les outils
            this.CreateTools();

            this.OnNewYear += this.YearChanged;

                Vector2 camPos = new Vector2();
                foreach (City city in this.map.Cities)
                    if (city.Enabled)
                        camPos = FindCityPosition(city.Name);

                camPos *= 64;
                camPos.X -= Kernel.Instance.Width / 2;
                camPos.Y -= Kernel.Instance.Height / 2;

                Kernel.Instance.Camera.Position = camPos;
                Kernel.Instance.Camera.Zoom = 0.8f;
                Kernel.Instance.Camera.MoveTo(camPos, 1.0f);
            


            this.initialized = true;
            Kernel.Instance.Cursor.Enable = true;
#if XBOX
            Kernel.Instance.Input.InitMouse(new Vector2(Kernel.Instance.Width / 2, Kernel.Instance.Height / 2));
#endif

            // Simule pour la 1ere fois
            this.simulation.Simulate(SimulationPool.Daily.ToString());

            this.initialized = true;
        }

        private void CreateBuildingTemplates()
        {
            // Water
            this.buildingFactory.RegisterTemplate(new WaterBuilding(Rules.Buildings.Water.WaterTower), true);
            
            // Food
            this.buildingFactory.RegisterTemplate(new FoodBuilding(Rules.Buildings.Food.WacDonald), true);
           
            // Energy
            this.buildingFactory.RegisterTemplate(new EnergyBuilding(Rules.Buildings.Energy.WindMachine), true);

            // Education
            this.buildingFactory.RegisterTemplate(new EducationBuilding(Rules.Buildings.Education.School), true);

            // Ecology
            this.buildingFactory.RegisterTemplate(new EcologyBuilding(Rules.Buildings.Ecology.SmallPark), true);
            this.buildingFactory.RegisterTemplate(new EcologyBuilding(Rules.Buildings.Ecology.MediumPark), false);
        }


        private void CreateTools()
        {
            this.tools.Add(new SelectionTool());
            this.tools.Add(new ConstructionTool());

            this.CurrentTool = ToolType.Selection;
        }

        #endregion

        #region Propriétés

        #region Game Time

        public GameSpeed Speed
        {
            get { return this.speed; }
            set
            {
                this.speed = value;
                if (this.OnSpeedChanged != null)
                    this.OnSpeedChanged();
            }
        }

        public float TotalTime
        {
            get { return this.totalTime; }
        }

        public int GameDays
        {
            get { return this.gameDays; }
        }

        public DateTime GameDate
        {
            get { return this.gameDate; }
            set { this.gameDate = value; }
        }

        #endregion

        #region Building

        public BuildingFactory BuildingFactory
        {
            get { return this.buildingFactory; }
        }

        #endregion

        #region Statistiques

        public double NumberOfCitiesManaged
        {
            get { return this.numberOfCitiesManaged; }
        }

        public int TotalOfCitiesManaged
        {
            get { return this.totalOfCitiesManaged; }
        }
        #endregion

        public Map Map
        {
            get { return this.map; }
        }

        public Point ScreenPosition
        {
            get
            {
                return new Point((int)Kernel.Instance.Origin.X / 64, (int)Kernel.Instance.Origin.Y / 64);
            }
        }

        public ToolType CurrentTool
        {
            get { return this.currentTool.Type; }
            set
            {
                if (this.currentTool != null)
                    this.currentTool.End();
                this.currentTool = this.tools[(int)value];
                this.currentTool.Begin();
            }
        }

        public Tool CurrentToolInstance
        {
            get { return this.currentTool; }
        }

        public ISelectable CurrentSelectedItem
        {
            get { return this.currentSelectedItem; }
            set
            {
                this.oldSelectedItem = this.currentSelectedItem;
                this.currentSelectedItem = value;
            }
        }

        public ISelectable OldSelectedItem
        {
            get { return this.oldSelectedItem; }
        }

        public static GameController Instance
        {
            get { return GameController.instance; }
            set
            {
                // Se désabonner de tous les événements
                GameController.instance = value;
            }
        }

        public Building SelectedBuilding { 
            get { return this.selectedBuilding; } 
            set { this.selectedBuilding = value; } 
        }

        public void RegisterVariable(SimulationPool pool, Variable var)
        {
            this.simulation.Register(pool.ToString(), var);
        }
        #endregion

        #region IUpdatable Membres

        public bool Enabled
        {
            get { return this.enabled; }
            set { this.enabled = this.resetCursor = value; }
        }

        public void UpdateLogic(GameTime gameTime)
        {
            float dt = gameTime.ElapsedGameTime.Milliseconds / 1000.0f;
            this.totalTime += dt;

            // Si on est pas en pause...
            if (this.speed != GameSpeed.Pause)
            {
                // On a passé un jour
                this.dayCounter += dt;
                if (this.dayCounter >= Rules.SecondsPerDay / (int)this.speed)
                {
                    int lastMonth = this.gameDate.Month;
                    int lastYear = this.gameDate.Year;

                    this.gameDays++;
                    this.gameDate = this.gameDate.AddDays(1.0);
                    this.dayCounter -= Rules.SecondsPerDay / (int)this.speed;

                    // Lance la simulation quotidienne
                    this.simulation.Simulate(SimulationPool.Daily.ToString());

                    // Appelle l'événement lorsque l'on change de jour
                    if (this.OnNewDay != null)
                        this.OnNewDay();

                    // On a passé un mois
                    if (lastMonth != this.gameDate.Month)
                    {
                        // Appelle l'événement lorsque l'on change de mois
                        if (this.OnNewMonth != null)
                            this.OnNewMonth();
                    }

                    if (lastYear != this.gameDate.Year)
                    {
                        // Appelle l'événement lorsque l'on change d'année
                        if (this.OnNewYear != null)
                            this.OnNewYear();
                    }
                }
            }

        }

        public void UpdateInput(GameTime gameTime)
        {
// Si on autorise pas l'Input
if (!this.enableInput) return;

InputState input = Kernel.Instance.Input;

// Variable permettant de calculer le déplacement de la caméra en fonction 
// du temps précédant le dernier de la méthode Update
float dt = gameTime.ElapsedGameTime.Milliseconds / 1000.0f;

            #region Caméra

            if (input.IsKeyDown(Keys.Up) || input.MousePosition.Y <= 5)
            {
                this.cameraSpeed.Y += input.MoveSpeed * dt;
                Kernel.Instance.Camera.Move(new Vector2(0.0f, -this.cameraSpeed.Y));
            }
            else if (input.IsKeyDown(Keys.Down) || input.MousePosition.Y >= Kernel.Instance.Height - 5)
            {
                this.cameraSpeed.Y += input.MoveSpeed * dt;
                Kernel.Instance.Camera.Move(new Vector2(0.0f, this.cameraSpeed.Y));
            }
            else
            {
                    this.cameraSpeed.Y = MinCameraSpeed.Y;
            }



            if (input.IsKeyDown(Keys.Right) || input.MousePosition.X >= Kernel.Instance.Width - 5)
            {
                this.cameraSpeed.X += input.MoveSpeed * dt;
                Kernel.Instance.Camera.Move(new Vector2(this.cameraSpeed.X, 0.0f));
            }
            else if (input.IsKeyDown(Keys.Left) || input.MousePosition.X <= 5)
            {
                this.cameraSpeed.X += input.MoveSpeed * dt;
                Kernel.Instance.Camera.Move(new Vector2(-this.cameraSpeed.X, 0.0f));
            }
            else
                    this.cameraSpeed.X = MinCameraSpeed.X;

            if (input.IsButtonFirstPress(Buttons.Back) && this.speed == GameSpeed.Pause)
                this.speed = GameSpeed.Play2X;


            #endregion

            #region Tool
            // Met à jour l'outil courant
                this.currentTool.Update(gameTime);
            #endregion

            #region Racourcis claviers

#if !XBOX
            if (input.IsNewKeyPress(Keys.P))
            {
                GameController.Instance.SelectedBuilding = GameController.Instance.BuildingFactory.GetBuilding(Text.BuildingsWindMachine);
                // Change l'outil courant en outil de construction
                GameController.Instance.CurrentTool = ToolType.Construction;
            }
            if (input.IsNewKeyPress(Keys.R))
            {
                GameController.Instance.SelectedBuilding = GameController.Instance.BuildingFactory.GetBuilding(Text.BuildingsSmallPark);
                // Change l'outil courant en outil de construction
                GameController.Instance.CurrentTool = ToolType.Construction;
            }
            if (input.IsNewKeyPress(Keys.O))
            {
                GameController.Instance.SelectedBuilding = GameController.Instance.BuildingFactory.GetBuilding(Text.BuildingsMediumPark);
                // Change l'outil courant en outil de construction
                GameController.Instance.CurrentTool = ToolType.Construction;
            }
            if (input.IsNewKeyPress(Keys.G))
            {
                GameController.Instance.SelectedBuilding = GameController.Instance.BuildingFactory.GetBuilding(Text.BuildingsSchool);
                // Change l'outil courant en outil de construction
                GameController.Instance.CurrentTool = ToolType.Construction;
            }
            if (input.IsNewKeyPress(Keys.W))
            {
                GameController.Instance.SelectedBuilding = GameController.Instance.BuildingFactory.GetBuilding(Text.BuildingsWaterTower);
                // Change l'outil courant en outil de construction
                GameController.Instance.CurrentTool = ToolType.Construction;
            }
            if (input.IsNewKeyPress(Keys.N))
            {
                GameController.Instance.SelectedBuilding = GameController.Instance.BuildingFactory.GetBuilding(Text.BuildingsWacDonald);
                // Change l'outil courant en outil de construction
                GameController.Instance.CurrentTool = ToolType.Construction;
            }

            /*
            else if (input.IsNewKeyPress(Keys.T))
                ControlPanel.Instance.ShowContructionBar(BuildingCategory.Transport);
            // Health
            else if (input.IsNewKeyPress(Keys.Q))
                ControlPanel.Instance.ShowContructionBar(BuildingCategory.Health);
            // Job
            else if (input.IsNewKeyPress(Keys.S))
                ControlPanel.Instance.ShowContructionBar(BuildingCategory.Job);
            // Education
            else if (input.IsNewKeyPress(Keys.D))
                ControlPanel.Instance.ShowContructionBar(BuildingCategory.Education);
            // Mini games
            else if (input.IsNewKeyPress(Keys.F))
             */

#if DEBUG
            if (input.IsNewKeyPress(Keys.F5))
                this.Speed = GameSpeed.Play;
            else if (input.IsNewKeyPress(Keys.F6))
                this.Speed = GameSpeed.Play2X;
            else if (input.IsNewKeyPress(Keys.F7))
                this.Speed = GameSpeed.Play4X;
            else if (input.IsNewKeyPress(Keys.F8))
                this.Speed = GameSpeed.Play8X;
#endif
#endif

            #endregion

            #region Special Actions

            if (input.ZoomIn > 0.3f)
            {
                // Vérifie si le zoom est possible
                if (Kernel.Instance.Camera.Zoom < 1.00f)
                {
                    Kernel.Instance.Camera.Zoom += input.ZoomIn * 0.02f;

                    if (Kernel.Instance.Camera.Zoom > 1.0f)
                        Kernel.Instance.Camera.Zoom = 1.0f;
                }
            }

            if (input.ZoomOut > 0.3f)
            {
                // Si le zoom est possible
                if (Kernel.Instance.Camera.Zoom > 0.6f)
                {
                    // Kernel.Instance.Camera.Zoom -= 0.20f;
                    Kernel.Instance.Camera.Zoom -= input.ZoomOut * 0.02f;

                    if (Kernel.Instance.Camera.Zoom < 0.6f)
                        Kernel.Instance.Camera.Zoom = 0.6f;

                }
            }

            #endregion
        }

        public void UpdateComponents(GameTime gameTime)
        {
        }

        public void Update(GameTime gameTime)
        {
            if (!this.initialized || !this.enabled) return;

            this.UpdateInput(gameTime);
            this.UpdateLogic(gameTime);
            this.UpdateComponents(gameTime);
        }

        #endregion

        #region Actions des outils

        public City FindNearestCity(Vector2 position)
        {
            Vector2 vPos = new Vector2(position.X * 64, position.Y * 64);
            float currentDist = float.MaxValue;
            City currentCity = null;

            foreach (City city in this.map.Cities)
            {
                Vector2 cPos = new Vector2(
                    (city.Position.X + Rules.Cities.Size.Width / 2.0f) * 64,
                    (city.Position.Y + Rules.Cities.Size.Height / 2.0f) * 64);
                float dist = Vector2.Distance(vPos, cPos);

                if (dist < currentDist)
                {
                    currentCity = city;
                    currentDist = dist;
                }
            }

            return currentCity;
        }

        public City FindCity(string cityName)
        {
            return this.map.GetCity(cityName);
        }

        public Vector2 FindCityPosition(string cityName)
        {
            City currentCity = null;

            foreach (City city in this.map.Cities)
            {
                if (city.Name == cityName)
                {
                    currentCity = city;
                    continue;
                }
            }

            if (currentCity == null)
                throw new NullReferenceException();
            return currentCity.Position;
        }

        private void ViewClosiestCity()
        {
            City city = (City)this.currentSelectedItem;
            if (city != null)
                city.Blur();

            Vector2 tmp = new Vector2(Kernel.Instance.Camera.Position.X, Kernel.Instance.Camera.Position.Y);
            // on centre la positon (milieu de l'écran)
            tmp.Y += Kernel.Instance.Height / 2;
            tmp.X += Kernel.Instance.Width / 2;
            tmp /= 64;
            // on regarde la ville la plus proche
            city = FindNearestCity(tmp);

            
                Vector2 vec = city.ScreenPosition;
                vec.Y -= Kernel.Instance.Height/2;
                vec.X -= Kernel.Instance.Width/2;
                // on ajuste pour le viewport
                vec = AdjustPositionAtViewport(vec);
                // et on se positionne dessus
                this.MoveCamera(vec, 10000, false, false);

                city.Focus();
                GameController.Instance.CurrentSelectedItem = city;
            
        }

        /// <summary>
        /// Adjust a vector to be on the viewport
        /// </summary>
        /// <param name="vec"></param>
        /// <returns></returns>
        public Vector2 AdjustPositionAtViewport(Vector2 vec)
        {
            if (vec.X > Kernel.Instance.Camera.MaxLimit.X)
                vec.X = Kernel.Instance.Camera.MaxLimit.X;

            if (vec.X < Kernel.Instance.Camera.MinLimit.X)
                vec.X = Kernel.Instance.Camera.MinLimit.X;

            if (vec.Y > Kernel.Instance.Camera.MaxLimit.Y)
                vec.Y = Kernel.Instance.Camera.MaxLimit.Y;

            if (vec.Y < Kernel.Instance.Camera.MinLimit.Y)
                vec.Y = Kernel.Instance.Camera.MinLimit.Y;

            return vec;
        }

        public ISelectable RemoveElement(Vector2 position)
        {
            // Détruit un bâtiment
            ISelectable item = this.map.GetItem(position);
            if (item != null && item is CityBuilding)
            {
                CityBuilding building = (CityBuilding)item;

                building.City.RemoveBuilding(building);

                building.Infos.GlobalCount--;
                if (building.Infos.GlobalCount < building.Infos.GlobalLimit /* TODO: city limit */)
                    GameController.Instance.BuildingFactory.SetBuildingAvailable(building.Infos.Name, true);
            }
            else
            {
                // Détruit un élément de la map
                if (this.map.Elements[(int)position.X, (int)position.Y] != null)
                    this.Map.Elements[(int)position.X, (int)position.Y] = null;
            }

            return item;
        }

        public void Restart()
        {
            GameRenderer.Instance = null;
            GameController.Instance = null;

            GC.Collect();

            new GameController(this.rootState, this.mapName);
            
            GameController.instance.Initialize();
            new GameRenderer(GameController.Instance.Map);
        }
         
        public void Quit()
        {
            GameRenderer.Instance = null;
            GameController.Instance = null;

            GC.Collect();                
        }
        #endregion

        #region time

        public void YearChanged()
        {
        }

        #endregion

        #region Camera Outils
        public void MoveCamera(Vector2 position, float accel, bool freezeInput, bool unfreezeInputOnFinish)
        {
            Kernel.Instance.Camera.MoveTo(position, accel);

            if (freezeInput)
            {
                this.enableInput = !freezeInput;
                moveUnFreezeInput = unfreezeInputOnFinish;
                Kernel.Instance.Camera.OnMoveToFinished += this.CameraMoveToFinishedHandler;
            }
        }

        private void CameraMoveToFinishedHandler(Camera camera)
        {
            this.enableInput = moveUnFreezeInput;

            camera.OnMoveToFinished -= this.CameraMoveToFinishedHandler;
        }

        public void MoveCameraCenter(Vector2 position, float accel, bool freezeInput, bool unfreezeInputOnFinish)
        {
            // La position doit être centrée sur l'écran
            position.X -= Kernel.Instance.Width / 3;
            position.Y -= Kernel.Instance.Height / 2;

            // Empeche la caméra de sortir du viewport
            if (position.X < Kernel.Instance.Camera.MinLimit.X)
                position.X = Kernel.Instance.Camera.MinLimit.X;
            else if (position.X + Kernel.Instance.Width > Kernel.Instance.Camera.MaxLimit.X)
                position.X = Kernel.Instance.Camera.MaxLimit.X - Kernel.Instance.Width;

            if (position.Y < Kernel.Instance.Camera.MinLimit.Y)
                position.Y = Kernel.Instance.Camera.MinLimit.Y;
            else if (position.Y + Kernel.Instance.Height > Kernel.Instance.Camera.MaxLimit.Y)
                position.Y = Kernel.Instance.Camera.MaxLimit.Y - Kernel.Instance.Height;

            this.MoveCamera(position, accel, freezeInput, unfreezeInputOnFinish);
        }

        public void MoveCameraToCity(string cityName, float accel, bool freezeInput, bool unfreezeInputOnFinish)
        {
            this.MoveCameraToCity(this.FindCity(cityName), accel, freezeInput, unfreezeInputOnFinish);
        }

        public void MoveCameraToCity(City city, float accel, bool freezeInput, bool unfreezeInputOnFinish)
        {
            Vector2 cityPos = city.ScreenPosition;

            // Déplacement
            this.MoveCameraCenter(cityPos, accel, freezeInput, unfreezeInputOnFinish);

            // Visualise l'item sélectionné
            // HACK
            GameController.Instance.Map.GetItem(Vector2.Zero);

            if (GameController.Instance.OldSelectedItem != null)
                GameController.Instance.OldSelectedItem.Blur();

            GameController.Instance.CurrentSelectedItem = city;
            city.Focus();
        }

        public bool IsInViewport(Vector2 vec)
        {
            return (vec.X >= Kernel.Instance.Camera.MinLimit.X && vec.Y >= Kernel.Instance.Camera.MinLimit.Y &&
                    vec.X <= Kernel.Instance.Camera.MaxLimit.X && vec.Y <= Kernel.Instance.Camera.MaxLimit.Y);
        }
        #endregion

    }
}
