#pragma once

#include <vector>
#include <list>
#include <queue>
#include <string>

#include <iostream>

#include <glm/vec2.hpp>

#include "missile.hpp"
#include "planet.hpp"

#include "state_update_event.hpp"

// TODO:
// give points for equipment / better weapons / more energy when:
// - player discovers the universe
//
// - the shot which made a kill was much longer than the direkt line between
//   player and his target
//
// - add wormholes
// - add blackholes
// - shoot through suns which add a fire mantle to the rocket to make it more
//   thick and dangerous but it gets destroyed after some time.

namespace game {
    // forward declarations
    class Command;
    class Player;

    class Ship;
    class Trace;
    class Explosion;

    class IdGenerator {
        public:
            IdGenerator() : m_nextId(0)
            {
            }

            size_t makeNextId()
            {
                return m_nextId++;
                if (m_nextId == 0) {
                    std::cerr << "note: id counter just wrapped to 0, "
                        "funny things can happen now." << std::endl;
                }
            }

        private:
            size_t m_nextId;
    };

    class State {
    public:
        /*************************************************************************/
        /* State management */
        /*************************************************************************/

        // called to setup the state (randomize planets, kill
        // traces/missiles/ships etc.)
        void init(int numPlanets=15);

        // main method to advance the simulation by the given timestamp in
        // seconds.
        void advance(float dt);

        /*************************************************************************/
        /* Network / Input */
        /*************************************************************************/

        // The upper layer (network/renderer) calling these three functions
        // should keep id's unique and give one (network) input an id.
        size_t addPlayer();
        void clear(size_t playerId);
        void setName(size_t playerId, std::string name);
        void setSpeed(size_t playerId, double speed);
        void quitPlayer(size_t playerId);
        void commandForPlayer(size_t playerId, Command *cmd);

        // lookup. return nullptr on invalid playerId
        Player *playerForId(size_t playerId);

        /*************************************************************************/
        /* Mixed stuff */

        // distance after which missiles get lost in space (and explode)
        float maxMissileDistance() const { return m_maxMissileDistance; }

        // each ship has the same radius
        float shipRadius()         const { return m_shipRadius; }

        // add a trace to the state
        void addTrace(Trace *trace);

        // add a missile to the state
        void addMissile(Missile *missile);

        // add a ship to the state
        void addShip(Ship *ship);

        // delete traces with this command
        void deleteTrace(Trace *trace); // using a pointer

        // get the current time
        float timestamp() const { return m_time; }

        // check if game is in dev. mode.
        // custom commands will then work etc.
        bool developerMode() const { return m_developerMode; }

        // change the mode.
        void setDeveloperMode(bool on)
        {
            std::cout<<"[state] developer mode is: " << (on ? "on" : "off") << std::endl;
            m_developerMode = on;
        }

        // called when the rendering window changes
        // TODO: give dpi as information too
        void setPlayingFieldSize(int width, int height);
        void setPlayingFieldCenter(int width, int height);

        size_t generateId();

        void applyAndClearAllOldStateUpdates();

        // delete the items for events that are to be removed in proper way
        // after the events were given to other parts of the application.
        const std::list<StateUpdateEvent*> &currentStateUpdateEvents() const;


        /*************************************************************************/
        /* Rendering */
        /*************************************************************************/

        // TODO: hide and replace by events

        // Game items which should be rendered are here:
        // (access missiles by iterating over player's missiles attribute)
        std::vector<Planet*>  planets;
        std::list<Ship*>      ships;
        std::list<Player*>    players;
        std::list<Trace*>     traces;
        std::list<Explosion*> explosions;

    private:
        /*************************************************************************/
        /* Internal */
        /*************************************************************************/

        void playerKillsPlayer(Player *killer, Player *victim);

        void addExplosionFromHit(const Missile::Event *evt);
        bool findPlanetSpawnPosition(bool planetIsSun, float radius, glm::vec2 *pos);

        void advanceTraceAges(float dt);
        void advanceExplosions(float dt);
        void advancePlayerShipSpawns(float dt);
        void advancePlayerCommands(float dt);
        void advancePlayerMissiles(float dt);
        void setupPlanets(int numPlanets);
        Planet::Material materialForStandardPlanetDistribution(int index);

        // try to spawn a ship for this player.
        // return true on success, false on failure to find a spot.
        bool spawnShipForPlayer(Player *player);

        // find some place where nothing is placed nearby (ships/planets).
        // usefule for spanwing things
        bool findFreePositionWithRadius(float r, glm::vec2 &pos);

        float  m_maxMissileDistance;
        float  m_playerRespawnTime;
        float  m_shipRadius;
        float  m_defaultEnergy;
        int    m_maxNumTraces;
        IdGenerator m_ids;
        float  m_time;
        bool   m_developerMode;

        glm::vec2 m_playingFieldCenter;
        glm::vec2 m_playingFieldSize;

        std::list<StateUpdateEvent*> m_nextEvents;
        std::list<StateUpdateEvent*> m_allEvents;
    };
}
