
/****************************************************************************/
/*Copyright (c) 2011, Florent DEVILLE.                                      */
/*All rights reserved.                                                      */
/*                                                                          */
/*Redistribution and use in source and binary forms, with or without        */
/*modification, are permitted provided that the following conditions        */
/*are met:                                                                  */
/*                                                                          */
/* - Redistributions of source code must retain the above copyright         */
/*notice, this list of conditions and the following disclaimer.             */
/* - Redistributions in binary form must reproduce the above                */
/*copyright notice, this list of conditions and the following               */
/*disclaimer in the documentation and/or other materials provided           */
/*with the distribution.                                                    */
/* - The names of its contributors cannot be used to endorse or promote     */
/*products derived from this software without specific prior written        */
/*permission.                                                               */
/* - The source code cannot be used for commercial purposes without         */
/*its contributors' permission.                                             */
/*                                                                          */
/*THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS       */
/*"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT         */
/*LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS         */
/*FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE            */
/*COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       */
/*INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,      */
/*BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;          */
/*LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER          */
/*CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT        */
/*LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN         */
/*ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE           */
/*POSSIBILITY OF SUCH DAMAGE.                                               */
/****************************************************************************/

using System.Collections.Generic;
using System.Diagnostics;
using System.Xml;

using GE.Visualisation;
using GE.World.Entities;
using GE.Physics.Shapes;
using GE.TimeClock;
using GE.Gui.Hud;
using GE.Tools;
using GE.Message;
using GE.Input;
using GE.Manager;
using GE.Audio;
using GE.Gui;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

namespace GE.World
{
    class World
    {
        public enum eWorldState
        {
            eWorldStateGameRunning,
            eWorldStateMainMenu,
            eWorldStateGamePause,
            eWorldStateGameOver,
            eWorldStateCount
        }

        #region Variables

        /// <summary>
        /// singleton variable
        /// </summary>
        static World _instance = new World();

        /// <summary>
        /// The content manager used by the game
        /// </summary>
        ContentManager _content;

        /// <summary>
        /// list of entities
        /// </summary>
        List<WorldEntity> _entityList;

        /// <summary>
        /// Id of the player in the entity list
        /// </summary>
        int _idPlayer;

        /// <summary>
        /// Id of the camera in the entity list
        /// </summary>
        int _idCamera;

        /// <summary>
        /// Texture id of the splash screen
        /// </summary>
        int _iSplashScreen;

        /// <summary>
        /// World's inner state
        /// </summary>
        eWorldState _innerState;

        /// <summary>
        /// Flag set when the world gotta be reseted
        /// </summary>
        bool _bReset;

        /// <summary>
        /// Life time for the state game over
        /// </summary>
        const int LIFETIME_STATE_GAMEOVER = 3000;

        /// <summary>
        /// Birth time of the current state
        /// </summary>
        int _iBirthState;

        /// <summary>
        /// Flag set when it's the first time the state will be applied
        /// </summary>
        bool _bPreState;

        /// <summary>
        /// Id of the animation of the game over
        /// </summary>
        int _iIdAnimationGameOverAppear;

        /// <summary>
        /// Id of the animation of the game over
        /// </summary>
        int _iIdAnimationGameOverDisappear;

        /// <summary>
        /// Game over texture
        /// </summary>
        int _iIdTextureGameOver;

        /// <summary>
        /// Game over sprite
        /// </summary>
        int _iIdSpriteGameOver;

        /// <summary>
        /// Current frame of the animation
        /// </summary>
        int _iAnimationCurrentFrame;

        /// <summary>
        /// Current time of the animation
        /// </summary>
        int _iAnimationCurrentTime;

        /// <summary>
        /// Flag set when the animation is over
        /// </summary>
        bool _bAnimationGameOverAppearOver;

        /// <summary>
        /// Flag set when the animation is over
        /// </summary>
        bool _bAnimationGameOverDisappearOver;

        /// <summary>
        /// Position of the game over animation
        /// </summary>
        Vector2 _v2PositionAnimationGameOver;

        /// <summary>
        /// Game over state
        /// </summary>
        int _iStateGameOver;

        /// <summary>
        /// Playlist of background sound
        /// </summary>
        Playlist _gamePlaylist;

        #endregion

        #region Singleton & Constructor

        /// <summary>
        /// Return the unique instance of the World class.
        /// </summary>
        public static World Instance { get{ return _instance;} }

        /// <summary>
        /// Constructor
        /// </summary>
        private World() 
        {
            _entityList = new List<WorldEntity>();
            _idPlayer = -1;
            _bReset = false;
            _bPreState = true;
            _innerState = eWorldState.eWorldStateMainMenu;
            _iAnimationCurrentFrame = -1;
            _iAnimationCurrentTime = -1;
            _bAnimationGameOverAppearOver = false;
        }

        public void init(ContentManager content)
        {
            _content = content;
        }
        #endregion

        #region Properties

        /// <summary>
        /// Set the texture id for the splash screen
        /// </summary>
        public int IdSplashScreen { set { _iSplashScreen = value; } }

        /// <summary>
        /// Get the entity list
        /// </summary>
        public List<WorldEntity> Entities
        {
            get
            {
                return _entityList;
            }
        }

        
        /// <summary>
        /// Return a reference to the player entity
        /// </summary>
        public PlayerEntity PlayerEntity
        {
            get
            {
                Debug.Assert(_idPlayer >= 0);
                Debug.Assert(_idPlayer < _entityList.Count);

                return (PlayerEntity)_entityList[_idPlayer];
            }
        }

        /// <summary>
        /// Return the camera entity
        /// </summary>
        public CameraEntity CameraEntity
        {
            get
            {
                Debug.Assert(_idCamera >= 0);
                Debug.Assert(_idCamera < _entityList.Count);

                return (CameraEntity)_entityList[_idCamera];
            }
        }

        /// <summary>
        /// Return the position of the camera
        /// </summary>
        public Vector2 CameraPosition
        {
            get
            {
                Debug.Assert(_idCamera >= 0);
                Debug.Assert(_idCamera < _entityList.Count);

                return _entityList[_idCamera].Position;
            }
        }

        /// <summary>
        /// Return the player's world position
        /// </summary>
        public Vector2 PlayerPosition
        {
            get { return PlayerEntity.Position; }
        }

        /// <summary>
        /// Set the game playlist background sound
        /// </summary>
        public Playlist GamePlaylist { set { _gamePlaylist = value; } }

        #endregion

        #region Pause & Start

        /// <summary>
        /// start the game after the loading
        /// </summary>
        public void startGame()
        {
            //_innerState = eWorldState.eWorldStateGameRunning;
            changeInnerState(eWorldState.eWorldStateGameRunning);
        }

        /// <summary>
        /// restart the game after pause
        /// </summary>
        public void reStartGame()
        {
            //_bGameRunning = true;
            //_innerState = eWorldState.eWorldStateGameRunning;
            changeInnerState(eWorldState.eWorldStateGameRunning);
            Clock.instance.startClock();
        }

        /// <summary>
        /// pause the game
        /// </summary>
        public void pauseGame(bool stopClock)
        {
            //_bGameRunning = false;
            //_innerState = eWorldState.eWorldStateGamePause;
            changeInnerState(eWorldState.eWorldStateGamePause);
            if(stopClock)
                Clock.instance.stopClock();
        }

        /// <summary>
        /// The game is over
        /// </summary>
        public void gameOver()
        {
            changeInnerState(eWorldState.eWorldStateGameOver);
        }

        #endregion

        #region Update & Render

        /// <summary>
        /// Update all the entities of the game
        /// </summary>
        public void update()
        {
            if (_bReset)
            {
                _entityList.Clear();
                _idPlayer = -1;
                changeInnerState( eWorldState.eWorldStateMainMenu);
                _bReset = false;
                Physics.Physics.Instance.reset();
            }

            switch (_innerState)
            {
                case eWorldState.eWorldStateMainMenu:
                    updateMainMenu();
                    break;

                case eWorldState.eWorldStateGameOver:
                    updateStateGameOver();
                    break;

                case eWorldState.eWorldStateGameRunning:
                    updateStateGameRunning();
                    break;
            }   
        }

        /// <summary>
        /// Render all the entities
        /// </summary>
        public void render()
        {

            switch (_innerState)
            {
                case eWorldState.eWorldStateMainMenu:
                    break;

                case eWorldState.eWorldStateGameRunning:
                    foreach (WorldEntity e in _entityList)
                        if (e.Active)
                            e.render();

                    HeadUpDisplay.Instance.render();
                    break;

                case eWorldState.eWorldStateGameOver:
                    foreach (WorldEntity e in _entityList)
                        if (e.Active)
                            e.render();

                    HeadUpDisplay.Instance.render();
                    switch (_iStateGameOver)
                    {
                        case -1:
                            _bAnimationGameOverAppearOver = Visu.Instance.displayAnimation(_iIdAnimationGameOverAppear, _v2PositionAnimationGameOver, ref _iAnimationCurrentFrame, ref _iAnimationCurrentTime);
                            break;

                        case 0:
                            Visu.Instance.displaySprite(_iIdTextureGameOver, _iIdSpriteGameOver, _v2PositionAnimationGameOver);
                            break;

                        case 1:
                            _bAnimationGameOverDisappearOver = Visu.Instance.displayAnimation(_iIdAnimationGameOverDisappear, _v2PositionAnimationGameOver, ref _iAnimationCurrentFrame, ref _iAnimationCurrentTime);
                            break;
                    }
                    break;
            }
        }

        public void initialise()
        {
            _iIdAnimationGameOverAppear = Visu.Instance.getAnimationID("Game_Over_Appear");
            _iIdAnimationGameOverDisappear = Visu.Instance.getAnimationID("Game_Over_Disappear");
            _iIdTextureGameOver = Visu.Instance.getAnimation(_iIdAnimationGameOverAppear).indexTexture;
            _iIdSpriteGameOver = Visu.Instance.getSpriteId(_iIdTextureGameOver, "game_over_8");
            int iWidth = Visu.Instance.getSpriteWidth(_iIdTextureGameOver, _iIdSpriteGameOver);

            _v2PositionAnimationGameOver = new Vector2(Visu.Instance.ScreenWidth / 2 - iWidth / 2, Visu.Instance.ScreenHeight / 2);

            foreach (WorldEntity e in _entityList)
                e.init();
        }

        /// <summary>
        /// Do the first activation of every entity which gotta be active at the start of the game
        /// </summary>
        public void activate()
        {
            foreach (WorldEntity e in _entityList)
                if(e.Active)
                    e.activate();
        }

        private void updateStateGameRunning()
        {
            if (_bPreState)
            {
                _bPreState = false;
                _gamePlaylist.play(0, true);
                Audio.Audio.Instance.stopSound("menuBackground");
            }

            //update every entity
            foreach (WorldEntity e in _entityList)
                if (e.Active)
                    e.update();
        }

        private void updateStateGameOver()
        {
            if (_bPreState)
            {
                _iStateGameOver = -1;
                _bPreState = false;
                _bAnimationGameOverAppearOver = false;
                _bAnimationGameOverDisappearOver = false;
                _iBirthState = -1;
            }

            if (_bAnimationGameOverAppearOver && _iBirthState == -1)
            {
                _iBirthState = Clock.instance.millisecs;
                _iStateGameOver = 0;
                _iAnimationCurrentFrame = -1;
                _iAnimationCurrentTime = -1;
            }

            if (_iStateGameOver == 0)
            {
                if (Clock.instance.millisecs > _iBirthState + LIFETIME_STATE_GAMEOVER)
                    _iStateGameOver = 1;
            }

            if (_bAnimationGameOverDisappearOver)
                goToMainMenu();

            //update every entity
            foreach (WorldEntity e in _entityList)
                if (e.Active)
                    e.update();

        }

        /// <summary>
        /// Update the main menu
        /// </summary>
        private void updateMainMenu()
        {
            if (_bPreState)
            {
                _bPreState = false;
                _gamePlaylist.stop();

                //play the sound
                Audio.Audio.Instance.playSound("menuBackground");

                //show the menu
                WMainMenu menu = new WMainMenu();
                menu.show();
            }
            
        }

       

        #endregion

        #region CreateXXX


        public BombmanBombEntity createBomb()
        {
            BombmanBombEntity newBomb = new BombmanBombEntity();
            _entityList.Add(newBomb);
            return newBomb;
        }

        public HardHatEntity createBlankHardHat()
        {
            HardHatEntity newEntity = new HardHatEntity();
            _entityList.Add(newEntity);
            return newEntity;
        }

        public BulletBillEntity createBlankBulletBill()
        {
            BulletBillEntity newEntity = new BulletBillEntity();
            _entityList.Add(newEntity);
            return newEntity;
        }

        public HelichomperEntity createBlankHelichomper()
        {
            HelichomperEntity newEntity = new HelichomperEntity();
            _entityList.Add(newEntity);
            return newEntity;
        }

        public SnapperEntity createBlankSnapper()
        {
            SnapperEntity newEntity = new SnapperEntity();
            _entityList.Add(newEntity);
            return newEntity;
        }

        public CutmanScissorEntity createBlankCutmanScissorEntity()
        {
            CutmanScissorEntity newEntity = new CutmanScissorEntity();
            _entityList.Add(newEntity);
            return newEntity;
        }

        /// <summary>
        /// Create a blank explosion entity and return a reference to it
        /// </summary>
        /// <returns></returns>
        public ExplosionEntity createBlankExplosion()
        {
            ExplosionEntity newEntity = new ExplosionEntity();
            _entityList.Add(newEntity);
            return newEntity;
        }

        public BulletEntity createBlankBullet()
        {
            BulletEntity newEntity = new BulletEntity();
            _entityList.Add(newEntity);
            return newEntity;
        }

        public ShrapnelEntity createBlankShrapnel()
        {
            ShrapnelEntity newEntity = new ShrapnelEntity();
            _entityList.Add(newEntity);
            return newEntity;
        }

        public void createSpike(Rectangle rec)
        {
            SpikeEntity newEntity = new SpikeEntity();
            newEntity.activate(rec);
            _entityList.Add(newEntity);
        }

        public int createPogobot(Vector2 position)
        {
            PogobotEntity newEntity = new PogobotEntity();
            newEntity.activate(position);
            int iIdEntity = _entityList.Count;
            _entityList.Add(newEntity);
            return iIdEntity;
        }

        public PogobotEntity createBlankPogobot()
        {
            PogobotEntity newEntity = new PogobotEntity();
            _entityList.Add(newEntity);
            return newEntity;
        }

        /// <summary>
        /// Create the player. It sucks, need parameters
        /// </summary>
        public void createPlayer(Vector2 position)
        {
            //create the entity player
            PlayerEntity player = new PlayerEntity();
            player.Position = position;
            //save the player id
            _idPlayer = _entityList.Count;

            //add the entity to the list
            _entityList.Add(player);
        }

        /// <summary>
        /// Create the game camera
        /// </summary>
        public void createCamera()
        {
            CameraEntity newCamera = new CameraEntity();
            newCamera.Active = true;
            _idCamera = _entityList.Count;
            _entityList.Add(newCamera);
            //newCamera.setOnPlayer();
            //Vector2 newPosition = newCamera.Position;
            //newPosition.Y = 9725 - Visu.Instance.ScreenHeight / 2;
            //newCamera.Position = newPosition;
        }

        #endregion

        #region Gettors

        /// <summary>
        /// Return true if the entity is active. Return false if the entity is inactive
        /// </summary>
        /// <param name="idEntity">Id of the entity to check</param>
        /// <returns></returns>
        public bool getActive(int idEntity) 
        {
            Debug.Assert(idEntity >= 0);
            Debug.Assert(idEntity < _entityList.Count);

            return _entityList[idEntity].Active;
        }

        #endregion

        #region Tools

        private void changeInnerState(eWorldState newState)
        {
            _innerState = newState;
            _bPreState = true;
        }

        public void goToMainMenu()
        {
            changeInnerState(eWorldState.eWorldStateMainMenu);
            reset();
        }

        /// <summary>
        /// Deactive an entity. This entity won't be udpated nor rendered
        /// </summary>
        /// <param name="idEntity"></param>
        public void DeactivateEntity(int idEntity)
        {
            Debug.Assert(idEntity >= 0);
            Debug.Assert(idEntity < _entityList.Count);

            _entityList[idEntity].Active = false;
        }
    
        /// <summary>
        /// Call the death of an entity
        /// </summary>
        /// <param name="idEntity">The entity to die</param>
        public void entityDie(int idEntity)
        {
            _entityList[idEntity].die();
        }

        /// <summary>
        /// Reset the world
        /// </summary>
        public void reset()
        {
            _bReset = true;
        }

        public void clear()
        {
            _entityList.Clear();

        }

        /// <summary>
        /// Load a level from a file
        /// </summary>
        /// <param name="assetName"></param>
        /// <returns></returns>
        public int loadLevel(string assetName)
        {
            //load the xml file
            XmlDocument doc = new XmlDocument();
            try { doc.Load(_content.RootDirectory + "\\" + assetName); }
            catch
            {
                Logger.Instance.error(this, "Cannot load : " + _content.RootDirectory + "\\" + assetName);
                return -1;
            }

            //check if the xml is an level xml
            if (doc.DocumentElement.Name != "Level")
            {
                Logger.Instance.error(this, "Cannot load : " + assetName);
                return -1;
            }

            //create the level entity
            LevelEntity newLevel = new LevelEntity();
            newLevel.activate();
            _entityList.Add(newLevel);

            //go through every node
            foreach (XmlNode n in doc.DocumentElement.ChildNodes)
            {
                if (n.Name == "SpriteElements")
                {
                    foreach(XmlNode spriteNode in n.ChildNodes)
                    {
                        int idTexture = Visu.Instance.loadTilset(spriteNode.Attributes["tilesetName"].InnerText);
                        string spriteName = spriteNode.Attributes["spriteName"].InnerText;
                        Vector2 position = XmlHelper.getVector2FromNode(spriteNode);
                        newLevel.addSprite(idTexture, spriteName, position);
                    }
                }
                else if (n.Name == "StaticPhysicShapes")
                {
                    StaticShapeRectangle[] shapeArray = new StaticShapeRectangle[n.ChildNodes.Count];
                    for(int i = 0; i < n.ChildNodes.Count ; i++)
                    {
                        XmlNode shapeNode = n.ChildNodes[i];
                        string innerValue = shapeNode.InnerText;
                        string[] seperatedValue = innerValue.Split(' ');
                        shapeArray[i] = new StaticShapeRectangle(int.Parse(seperatedValue[2]), int.Parse(seperatedValue[3]),
                            Vector2.Zero, new Vector2(int.Parse(seperatedValue[0]), int.Parse(seperatedValue[1])), 0, newLevel);
                        shapeArray[i]._iGroup = (int)ePhysicGroup.ePhysicPlatform;
                    }
                    newLevel.addShape(shapeArray);
                }
                else if (n.Name == "Ladders")
                {
                    foreach (XmlNode ladderNone in n.ChildNodes)
                    {
                        LadderEntity newEntity = new LadderEntity();

                        Rectangle rec = new Rectangle();
                        string innerText = ladderNone.InnerText;
                        string[] values = innerText.Split(' ');
                        rec.X = int.Parse(values[0]);
                        rec.Y = int.Parse(values[1]);
                        rec.Width = int.Parse(values[2]);
                        rec.Height = int.Parse(values[3]);

                        newEntity.activate(rec);
                        _entityList.Add(newEntity);
                    }
                }
                else if (n.Name == "Spikes")
                {
                    foreach (XmlNode spikeNode in n.ChildNodes)
                    {
                        //SpikeEntity newEntity = new SpikeEntity();

                        Rectangle rec = new Rectangle();
                        string innerText = spikeNode.InnerText;
                        string[] values = innerText.Split(' ');
                        rec.X = int.Parse(values[0]);
                        rec.Y = int.Parse(values[1]);
                        rec.Width = int.Parse(values[2]);
                        rec.Height = int.Parse(values[3]);

                        createSpike(rec);
                    }
                }
                else if (n.Name == "Entities")
                {
                    foreach (XmlNode entityNode in n.ChildNodes)
                        loadEntity(entityNode);
                }
                
            }

            //createCamera();
            
            return 1;
        }

        private void loadEntity(XmlNode root)
        {
            //get the type
            string strType = root.Attributes["type"].InnerText;
            System.Type tType = System.Type.GetType("GE.World.Entities." + strType);

            //if it's the player entity, save the id
            if (strType == "PlayerEntity")
                _idPlayer = _entityList.Count;
            else if (strType == "CameraEntity")
                _idCamera = _entityList.Count;

            //create the entity
            object newEntity = System.Activator.CreateInstance(tType);
            _entityList.Add((WorldEntity)newEntity);
            

            //go through every child node
            foreach (XmlNode property in root.ChildNodes)
            {
                string strPropertyName = property.Attributes["name"].InnerText;
                string strPropertyTypeName = tType.GetProperty(strPropertyName).PropertyType.Name;

                object value = null;

                switch (strPropertyTypeName)
                {
                    case "Vector2":
                        value = XmlHelper.getVector2FromNode(property);
                        break;

                    case "Boolean":
                        if (property.InnerText.ToUpper() == "TRUE")
                            value = true;
                        else
                            value = false;
                        break;

                    case "Int32":
                        value = XmlHelper.getIntFromNode(property);
                        break;

                    case "Single":
                        value = XmlHelper.getFloatFromNode(property);
                        break;

                    case "Rectangle":
                        string[] strValue = property.InnerText.Split(' ');
                        Rectangle newRec = new Rectangle();
                        newRec.X = int.Parse(strValue[0]);
                        newRec.Y = int.Parse(strValue[1]);
                        newRec.Width = int.Parse(strValue[2]);
                        newRec.Height = int.Parse(strValue[3]);
                        value = newRec;
                        break;

                    default : //type enum
                        System.Type enumType = tType.GetProperty(strPropertyName).PropertyType;
                        value = System.Enum.Parse(enumType, property.InnerText);
                        break;
                }

                tType.GetProperty(strPropertyName).SetValue(newEntity, value, null);
            }

        }

        #endregion


    }
}
