#ifndef _HPP_GAIA_MAIN
#define _HPP_GAIA_MAIN

#include "utils.hpp"
#include "vector2.hpp"
#include "line.hpp"

#include <list>
#include <vector>
#include <cstring> // for memcpy
#include <map>
#include <SDL_ttf.h>

namespace gaia {

  void Init(int w, int h, bool vsync, e_WindowMode windowMode, const std::string &windowTitle, int renderContextResolutionMultiplier = 1);
  void Exit();

  struct JoyController {
    JoyController() {
      hasRumble = false;
      activeSDLHapticEffectID = -1;
    }
    SDL_Joystick *sdlJoystick;
    SDL_GameController *sdlGameController;
    SDL_Haptic *sdlHaptic;
    bool hasRumble;
    mutable int activeSDLHapticEffectID;
  };

  class Game;

  class State {

    public:
      State(Game *game);
      virtual ~State();

      virtual void OnActivate() = 0;
      virtual void OnDeactivate() = 0;

      // problem with these: if the state gets deactivated before the onKeyUp, that one will never arrive
      // virtual void OnKeyDown(SDL_Keysym keysym) = 0;
      // virtual void OnKeyUp(SDL_Keysym keysym) = 0;

      virtual void OnStepPhysicsTime() = 0;
      virtual void OnStepGraphicsTime() = 0;

    protected:
      Game *game;

  };

  class Game {

    public:
      Game(int desiredGraphicsTimeStep_ms = 10, int physicsTimeStep_ms = 10);
      virtual ~Game();

      void Run();

      void ActivateState(State *state) {
        if (this->activeState) this->activeState->OnDeactivate();
        this->activeState = state;
        this->activeState->OnActivate();
      }

      void DeactivateActiveState() {
        if (this->activeState) this->activeState->OnDeactivate();
        this->activeState = 0;
      }

      State *GetActiveState() {
        return activeState;
      }

      void SetMaximizedViewport(float aspectRatio = 1.7777777f);
      Viewport GetMainViewport() const { return mainViewport; }

/*
      // todo: do we really need virtual viewport support?
      int CreateVirtualViewport(const Viewport &viewport);
      void SetActiveVirtualViewportID(int id);
      Viewport GetActiveVirtualViewport();
      void ClearVirtualViewports() { virtualViewports.clear(); activeVirtualViewportID = -1; }
*/
      //void SetCamera(const Vector2 &position, float distance = 1.0f, radian rotationAngle = 0.0f, radian viewAngle = 0.25 * pi);

      int GetDesiredGraphicsTimeStep_ms() const { return desiredGraphicsTimeStep_ms; }
      int GetPhysicsTimeStep_ms() const { return physicsTimeStep_ms; }

      unsigned long GetPhysicsTime_ms() const { return (unsigned long)physicsTimeStep_ms * physicsTimeStepsDone; }
      unsigned long GetPhysicsTimeStepsDone() const { return physicsTimeStepsDone; }

      virtual void OnKeyDown(SDL_Keysym keysym) {};
      virtual void OnKeyUp(SDL_Keysym keysym) {};
      virtual void OnJoyControllerAxisEvent(int joyControllerID, SDL_GameControllerAxis axisID, float value) {};
      virtual void OnJoyControllerButtonEvent(int joyControllerID, int buttonID, bool pressedOrReleased) {};
      void JoyRumble(int joyControllerID, float strength, unsigned int duration_ms) const;

      virtual void OnStepPhysicsTime() {};
      virtual void OnStepGraphicsTime() {};

      TTF_Font *GetFont(int uniqueID) const;

    protected:
      void CreateFont(int uniqueID, const std::string &filename, int ptsize = 16);
      void DeleteFont(int uniqueID);

      std::map<int, TTF_Font*> fonts;

    private:
      void StepPhysicsTime();
      void StepGraphicsTime();

      State *activeState;
      int desiredGraphicsTimeStep_ms;
      int physicsTimeStep_ms;
      unsigned long physicsTimeStepsDone;
      unsigned int startTime_ms;

      Viewport mainViewport;

      std::vector<JoyController> joyControllers;
      //SDL_HapticEffect sdlHapticRumbleEffect;

      // std::vector<Viewport> virtualViewports;
      // int activeVirtualViewportID;

      //Camera camera;

  };



  class Sprite {

    public:
      Sprite();
      virtual ~Sprite();

      void LoadFile(const std::string &filename, e_FilterMode filterMode = e_FilterMode_Nearest, unsigned int xSubsprites = 1, unsigned int ySubsprites = 1);
      void SelectSubsprite(unsigned int x, unsigned int y);
      void CreateText(TTF_Font *font, const std::string &text, const SDL_Color &color, e_FilterMode filterMode);
      void DeleteTexture();
      void GetResolution(unsigned int &w, unsigned int &h) const { w = (unsigned int)w_cached; h = (unsigned int)h_cached; }
      unsigned int GetWidth() const { return (unsigned int)w_cached / xSubsprites; }
      unsigned int GetHeight() const { return (unsigned int)h_cached / ySubsprites; }
      unsigned int GetTextureWidth() const { return (unsigned int)w_cached; }
      unsigned int GetTextureHeight() const { return (unsigned int)h_cached; }
      SDL_Texture *GetTexture() const { return texture; }
      void Draw(const Spatial &spatial) const;
      void Draw(const Spatial &spatial, const Viewport &viewport, const Camera &camera) const;
      void SetAlpha(float a = 1.0f);
      void SetFlip(SDL_RendererFlip newFlip);

    protected:
      SDL_Texture *texture;
      SDL_RendererFlip flip;
      int w_cached;
      int h_cached;
      float alpha;
      unsigned int xSubsprites;
      unsigned int ySubsprites;
      unsigned int selectedXSubsprite;
      unsigned int selectedYSubsprite;

  };

  struct AnimatedSpriteFrame {
    AnimatedSpriteFrame(unsigned int xSprite, unsigned int ySprite, unsigned int sustain_ms = 100) {
      this->selectedXSubsprite = xSprite;
      this->selectedYSubsprite = ySprite;
      this->sustain_ms = sustain_ms;
    }
    unsigned int selectedXSubsprite;
    unsigned int selectedYSubsprite;
    unsigned int sustain_ms;
  };

  typedef std::vector<AnimatedSpriteFrame> AnimatedSpriteSequence;

  class AnimatedSprite : public Sprite {

    public:
      AnimatedSprite();
      virtual ~AnimatedSprite();

      unsigned int AddSequence(const AnimatedSpriteSequence &sequence);
      void SelectSequence(unsigned int sequenceID) { selectedSequence = sequenceID; currentFrame = 0; timeIntoCurrentFrame_ms = 0; }
      unsigned int GetSelectedSequenceIndex() const { return selectedSequence; }
      void OnStepTime(unsigned int timeStep_ms);

    protected:
      std::vector<AnimatedSpriteSequence> sequences;
      unsigned int selectedSequence;
      unsigned int currentFrame;
      unsigned int timeIntoCurrentFrame_ms;

  };

/*
  class Text {

    public:
      Text(TTF_Font *font, const std::string &caption);
      virtual ~Text();

      void Draw(const Spatial &spatial) const;
      void Draw(const Spatial &spatial, const Viewport &viewport, const Camera &camera) const;
      void SetAlpha(float a = 1.0f);

    protected:
      SDL_Texture *texture;

  };
*/

  // for datamap's pathfinding
  struct PathNode {
    PathNode() {
      x = 0;
      y = 0;
      parent = 0;
      startDistance = 0;
      targetDistance = 0;
      open = false;
      closed = false;
    }
    PathNode(const Vector2 &position) {
      this->x = int(round(position.coords[0]));
      this->y = int(round(position.coords[1]));
      parent = 0;
      startDistance = 0;
      targetDistance = 0;
      open = false;
      closed = false;
    }
/*
    bool operator < (const PathNode *comp) const {
      return this->GetTotalDistance() < comp->GetTotalDistance();
    }
*/
/*
    bool operator == (const PathNode *comp) const {
      if (comp->x == x && comp->y == y) return true; else return false;
    }
*/
/*
    bool operator != (const PathNode &comp) const {
      if (comp.x != x || comp.y != y) return true; else return false;
    }
*/
/*
    bool operator != (const Vector2 &comp) const {
      if (int(round(comp.coords[0])) != x ||
          int(round(comp.coords[1])) != y) return true; else return false;
    }
*/
    float GetTotalDistance() const {
      return startDistance + targetDistance;
    }
    int x, y;
    PathNode *parent;
    float startDistance;
    float targetDistance; // heuristic
    bool open;
    bool closed;
  };

  class DataMap {

    public:
      DataMap();
      DataMap(const DataMap &src);
      virtual ~DataMap();

      void ImportFile(const std::string &filename);
      void ResizeFractional(float w_factor, float h_factor);

      float GetLocationData(const Vector2 &location, bool interpolate = false) const;
      void PathFinding(const std::vector<Vector2> &inputWaypoints, std::vector<Vector2> &outputWaypoints, bool keepInputWaypoints = true);

      const float *GetData() const { return data; }
      int GetWidth() const { return w; }
      int GetHeight() const { return h; }

    protected:
      float *data;
      int w, h;

  };

  class Object : public SpatialNode {

    public:
      Object(const SpatialNode *parent = 0, Sprite *sprite = 0) : SpatialNode(parent) {
        SetSprite(sprite);
      }
      virtual ~Object() {}

      Sprite *GetSprite() const { return sprite; }
      void SetSprite(Sprite *sprite) {
        this->sprite = sprite;
        /*
        if (sprite) {
          int w, h;
          sprite->GetResolution(w, h);
          spatial.scale.coords[0] = w;
          spatial.scale.coords[1] = h;
        }
        */
      }
      void Draw() const { assert(sprite); sprite->Draw(this->GetDerivedSpatial()); }
      void Draw(const Viewport &viewport, const Camera &camera) const { assert(sprite); sprite->Draw(this->GetDerivedSpatial(), viewport, camera); }

      DataMap *GetDataMap() { return dataMap; }
      void SetDataMap(DataMap *dataMap) {
        this->dataMap = dataMap;
      }
      float GetDataMapLocationData(const Vector2 &location, bool interpolate = false) const;

    protected:
      Sprite *sprite;
      DataMap *dataMap;

  };

  struct Particle {
    Object *object;
    float alpha;
    Vector2 momentum;
    int timeToLive_ms;
  };

  class ParticleSystem {

    public:
      ParticleSystem(float emissionPerSecond, int timeToLive_ms, int timeToLiveVariation_ms, const Vector2 &startPosition, const Vector2 &startMomentum, Sprite *sprite);
      virtual ~ParticleSystem();

      virtual void SetEmissionPerSecond(int emissionPerSecond) { this->emissionPerSecond = emissionPerSecond; }

      virtual void OnStepPhysicsTime(const Game *game);
      virtual void Draw(const Viewport &viewport, const Camera &camera);

      SpatialNode *spatialNode;
      Vector2 momentum;

    protected:
      virtual void UpdateParticle(Particle &particle, int timeStep_ms) {}

      float emissionPerSecond;
      float emissionRemainder;
      int timeToLive_ms;
      int timeToLiveVariation_ms;
      std::list<Particle> particles;
      Sprite *sprite;

  };

}

#endif
