using System;
using System.Collections.Generic;
using ECOT.Engine;
using ECOT.Game.Buildings;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ECOT.Game
{
    /// <summary>
    /// Cet enum représente le type de tile.
    /// Afin de facilité les calcul, il codé de la manière qui suit:
    ///  * 1e byte: identifiants pour les tiles de background passifs
    ///  * 2e byte: identifiants pour les tiles de background actifs
    ///  * 3e byte: identifiants pour les éléments passifs
    ///  * 4e byte: identifiants pour les éléments actifs
    ///  * 5e et 6e: réservés
    ///  * 7e byte: 1 si destructible, 0 sinon
    ///  * 8e byte: 1 si constructible, 0 sinon
    /// </summary>
    [Flags]
    public enum TileFamily : uint
    {
        PassiveBackground = 0x00000001,
        ActiveBackground = 0x00000010,
        PassiveElements = 0x00001000,
        ActiveElements = 0x00010000,
        Destructible = 0x01000000,
        Constructible = 0x10000000,

        // Backgrounds passifs
        Water = 0x00000001,
        Border = 0x00000002,

        // Backgrounds actifs
        Grass = 0x10000010,
        Sand = 0x10000020,
        AridGround = 0x10000040,
        Ground = 0x10000080,
        Snow = 0x10000100,
        Montain = 0x00000200,
        River = 0x00000400,
        Cost = 0x00000800,

        // Eléments passifs
        BadWeed = 0x11001000,
        Rock = 0x01002000,
        Detail = 0x11004000,
        Fun = 0x00008000,

        // Eléments actifs
        Forest = 0x01010000,
    }

    public class TileProperties
    {
        public double ConstructionCostFactor;
        public double ConstructionTimeFactor;

        public double WaterInfluenceFactor;
        public double FoodInfluenceFactor;
        public double EnergyInfluenceFactor;
        public double HousingInfluenceFactor;
        public double HealthInfluenceFactor;
        public double TransportInfluenceFactor;
        public double JobInfluenceFactor;
        public double EducationInfluenceFactor;

        public double PollutionInfluenceFactor;

        public static TileProperties Merge(TileProperties prop1, TileProperties prop2)
        {
            TileProperties mergeProp = new TileProperties();
            mergeProp.ConstructionCostFactor = (prop1.ConstructionCostFactor + prop2.ConstructionCostFactor) / 2;
            mergeProp.ConstructionTimeFactor = (prop1.ConstructionTimeFactor + prop2.ConstructionTimeFactor) / 2;

            mergeProp.WaterInfluenceFactor = (prop1.WaterInfluenceFactor + prop2.WaterInfluenceFactor) / 2;
            mergeProp.FoodInfluenceFactor = (prop1.FoodInfluenceFactor + prop2.FoodInfluenceFactor) / 2;
            mergeProp.EnergyInfluenceFactor = (prop1.EnergyInfluenceFactor + prop2.EnergyInfluenceFactor) / 2;
            mergeProp.HousingInfluenceFactor = (prop1.HousingInfluenceFactor + prop2.HousingInfluenceFactor) / 2;
            mergeProp.HealthInfluenceFactor = (prop1.HealthInfluenceFactor + prop2.HealthInfluenceFactor) / 2;
            mergeProp.TransportInfluenceFactor = (prop1.TransportInfluenceFactor + prop2.TransportInfluenceFactor) / 2;
            mergeProp.JobInfluenceFactor = (prop1.JobInfluenceFactor + prop2.JobInfluenceFactor) / 2;
            mergeProp.EducationInfluenceFactor = (prop1.EducationInfluenceFactor + prop2.EducationInfluenceFactor) / 2;

            mergeProp.PollutionInfluenceFactor = (prop1.PollutionInfluenceFactor + prop2.PollutionInfluenceFactor) / 2;

            return mergeProp;
        }

        public static double GetProductionFactor(TileProperties properties, BuildingCategory category)
        {
            switch (category)
            {
                case BuildingCategory.Energy:
                    return properties.EnergyInfluenceFactor;

                case BuildingCategory.Education:
                    return properties.EducationInfluenceFactor;

                default:
                    return 1.0;
            }
        }
    }

    public class Tile
    {
        public Texture2D Texture = null;
        public Point Position;
        public TileFamily Family;
        public TileProperties Properties = null;
        public Color Color = Color.White;
    }

    public struct MapRenderState
    {
    }

    public enum MapLayer
    {
        Background,
        Elements
    }

    public class Map
    {
        private string name;
        private Size size;
        
        private Tile[,] tiles;
        private Tile[,] elements;
        private readonly List<City> cities = new List<City>();
        private readonly List<int> sortedByYCities = new List<int>();
        private readonly List<int> sortedByXCities = new List<int>();
        private string startDate;
        private string level;
        public char[] separators = new char[] { '/' };
        public string[] strSplit;
        private string storyline;



        public string Storyline
        {
            get { return storyline; }
            set { storyline = value; }
        }

        public Map(string filename) 
            :this (filename, true)
        {
        }

        public Map(string filename, bool game)
        {
            MapLoader loader = new MapLoader(this);
            
            if(game)
            {
                loader.Load(filename);
                Kernel.Instance.Camera.MinLimit = new Vector2(0.0f, 0.0f);
                Kernel.Instance.Camera.MaxLimit = new Vector2(
                    this.Size.Width * 64.0f,
                    this.Size.Height * 64.0f);
                Kernel.Instance.Camera.Update(new GameTime()); // Met à jour directement, pour ne pas redessiner la minimap après

                this.sortCities();

                this.CreateTileStatistics();
            }
            else
                loader.LoadInfo(filename);
        }

        private void CreateTileStatistics()
        {

        }

        private class SortCityPosition : IComparer<City>
        {
            public enum ComparisonType
            {
                ByX,
                ByY
            }

            private ComparisonType comparisonType = 0;

            public ComparisonType TypeOfComparison
            {
                set { this.comparisonType = value; }
            }

            #region IComparer<City> Members

            public int Compare(City c1, City c2)
            {
                if (this.comparisonType == ComparisonType.ByY)
                {
                    if (c1.Position.Y > c2.Position.Y)
                        return 1;
                    if (c1.Position.Y < c2.Position.Y)
                        return -1;
                    return 0;
                }
                else
                {
                    if (c1.Position.X > c2.Position.X)
                        return 1;
                    if (c1.Position.X < c2.Position.X)
                        return -1;
                    return 0;
                }
            }

            #endregion
        }

        private void sortCities()
        {
            SortCityPosition sort = new SortCityPosition();
            List<City> tmpSortedByXCities = new List<City>();
            List<City> tmpSortedByYCities = new List<City>();

            foreach (City city in this.cities)
            {
                tmpSortedByXCities.Add(city);
                tmpSortedByYCities.Add(city);
            }

            sort.TypeOfComparison = SortCityPosition.ComparisonType.ByX;
            tmpSortedByXCities.Sort(sort);
            sort.TypeOfComparison = SortCityPosition.ComparisonType.ByY;
            tmpSortedByYCities.Sort(sort);

            for (int i = 0; i < this.cities.Count; i++)
            {
                this.sortedByXCities.Add(this.cities.IndexOf(tmpSortedByXCities[i]));
                this.sortedByYCities.Add(this.cities.IndexOf(tmpSortedByYCities[i]));
            }
        }

        public string Name
        {
            get { return this.name; }
            set { this.name = value; }
        }

        public Size Size
        {
            get { return this.size; }
            set
            {
               // Debug.Assert(this.tiles == null);
                this.tiles = new Tile[value.Width, value.Height];
                this.elements = new Tile[value.Width, value.Height];
                this.size = value;
            }
        }

        public string Level
        {
            get { return level; }
            set { level = value; }
        }

        public string StartDate
        {
            get { return startDate; }
            set { startDate = value; }
        }

        public string StartDay
        {
            get
            {
                strSplit = this.StartDate.Split(separators);
                return strSplit[0];
            }
        }

        public string StartMonth
        {
            get {
                strSplit = this.StartDate.Split(separators);
                return strSplit[1];
            }
        }

        public string StartYear
        {
            get 
            {
                strSplit = this.StartDate.Split(separators);
                return strSplit[2];                
            }
        }

        public Tile[, ] Tiles
        {
            get { return this.tiles; }
        }

        public Tile[, ] Elements
        {
            get { return this.elements; }
        }

        public City[] Cities
        {
            get { return this.cities.ToArray(); }
        }

        public int[] CitiesSortedByX
        {
            get { return this.sortedByXCities.ToArray(); }
        }

        public int[] CitiesSortedByY
        {
            get { return this.sortedByYCities.ToArray(); }
        }

        public int CitiesSortedByXCount
        {
            get { return this.sortedByXCities.Count; }
        }

        public int CitiesSortedByYCount
        {
            get { return this.sortedByYCities.Count; }
        }

        public void AddCity(City city)
        {
            this.cities.Add(city);
        }

        public void RemoveCity(City city)
        {
            this.cities.Remove(city);
        }

        public void RemoveCityName(string cityname)
        {
            this.RemoveCity(this.GetCity(cityname));
        }

        public City GetCity(string cityName)
        {
            foreach (City city in this.cities)
                if (city.Name == cityName)
                    return city;
            return null;
        }

        public City GetCityByIndex(int numCity)
        {
            return this.cities[numCity];
        }

        public int GetCityIndex(City city)
        {
            return this.cities.IndexOf(city);
        }

        public int GetCityIndex(City city, int sortType)
        {
            int cityIdx = this.GetCityIndex(city);
            int retCityIdx = 0;

            if (sortType == 0)
                for (; retCityIdx < this.sortedByXCities.Count && this.sortedByXCities[retCityIdx] != cityIdx; retCityIdx++) ;
            else if (sortType == 1)
                for (; retCityIdx < this.sortedByYCities.Count && this.sortedByYCities[retCityIdx] != cityIdx; retCityIdx++) ;

            return retCityIdx;
        }

        public City FindCity(Predicate<City> match)
        {
            return this.cities.Find(match);
        }

        public int CitiesCount
        {
            get { return this.cities.Count; }
        }

        public ISelectable GetItem(Vector2 position)
        {
            foreach (City city in this.cities)
            {
                // See if city is selected
                if (city.IsSelected(position))
                    return (city);

                // So try to see if it's one of it's buildings
                foreach (Building building in city.Buildings)
                    if (building.IsSelected(position))
                        return (building);
            }

            return null;
        }

        public void DayChanged()
        {
            // TODO: vérif des triggers
            // Met à jour les niveaux des villes
            foreach (City city in this.cities)
            {
                if (city.Enabled)
                {
                    city.DayChanged();
                }
            }
        }

        public void Render(GameTime gameTime, byte alpha)
        {
            SpriteBatch spriteBatch = Kernel.Instance.SpriteBatch;

            int x = (int)(Kernel.Instance.Origin.X / 64.0f);
            int xLimit = Math.Min(x + (int)Math.Ceiling(Kernel.Instance.Width / 64.0 / Kernel.Instance.Camera.Zoom) + 1, this.size.Width);
            int yBeg = (int)(Kernel.Instance.Origin.Y / 64.0f);
            int yLimit = Math.Min(yBeg + (int)Math.Ceiling(Kernel.Instance.Height / 64.0 / Kernel.Instance.Camera.Zoom) + 1, this.size.Height);

            // FIXME: trick de rush, bug qui arrive de temps en temps, à voir
            if (yBeg < 0) yBeg = 0;
            if (x < 0) x = 0;

            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None, Kernel.Instance.Camera.TransformMatrix);


            // Dessine les tiles
            for ( ; x < xLimit; x++)
            {
                for (int y = yBeg ; y < yLimit; y++)
                {
                    // TEST
                    /*float step = 1.0f / cycleColors.Length;
                    float fcurStep = f / step;
                    int icurStep = (int)fcurStep;
                    float lerp = fcurStep - icurStep;
                    int idx = Math.Min(icurStep, 11);
                    Color Color = Utilities.Lerp(cycleColors[idx], cycleColors[idx + 1], lerp);*/
                    //Color Color = new Color(255, 255, 255, alpha); //PAS EN TEST NORMALEMENT
                    // END TEST

                    if (this.DrawTile(MapLayer.Background, x, y, alpha, true, 1))
                        this.DrawTile(MapLayer.Elements, x, y, alpha, true, 1);
                }
            }

            spriteBatch.End();

            // Dessine les villes
            for (int i = 0; i < this.cities.Count; i++)
                // FIXME: afficher les villes uniquement visibles
                this.cities[i].Render(gameTime);

        }

        public bool DrawTile(MapLayer layer, int x, int y, byte alpha, bool useFx, int extend)
        {
            Tile tile;

            // Gets the tile
            if (layer == MapLayer.Background)
            {
                tile = this.tiles[x, y];
            }
            else
                tile = this.elements[x, y];

            if (tile == null) return false;

            Color color = new Color(tile.Color.R, tile.Color.G, tile.Color.B, alpha);

            // Dessine le tile
            Kernel.Instance.SpriteBatch.Draw(tile.Texture,
                new Rectangle(x * 64, y * 64, 64 + extend, 64 + extend),
                new Rectangle(tile.Position.X * 64, tile.Position.Y * 64, 64, 64), color, 0.0f,
                Vector2.Zero, SpriteEffects.None, 0.0f);

            return true;
        }

        public static string GetLocalizedName(string orignalName)
        {
            orignalName = orignalName.Replace(" ", "");
            orignalName = orignalName.Replace("é", "e");

            return Kernel.Instance.Text.Get(string.Format("MapName{0}", orignalName));
        }
    }
}
