#ifndef _HPP_GAIA_UTILS
#define _HPP_GAIA_UTILS

#include <cstdio>
#include <cassert>
#include <string>
#include <cmath>
#include <sstream> // for to_string patch

#include <SDL.h>
#include <SDL_ttf.h>

#include "vector2.hpp"

// bug in mingw, see http://stackoverflow.com/questions/12975341/to-string-is-not-a-member-of-std-says-so-g
namespace patch {
  template < typename T > std::string to_string(const T& n) {
    std::ostringstream stm;
    stm << n;
    return stm.str();
  }
}

namespace gaia {

  enum e_FilterMode {
    e_FilterMode_Nearest,
    e_FilterMode_Linear,
    e_FilterMode_Anisotropic
  };

  enum e_WindowMode {
    e_WindowMode_BorderlessFullscreen,
    e_WindowMode_Fullscreen,
    e_WindowMode_Windowed
  };

  struct Viewport {
    Viewport() {
      x = y = w = h = 0;
    }
    int x, y, w, h;
  };

  struct Spatial {
    Spatial() {
      z = 0;
      orientation = 0;
      scale = Vector2(1.0f, 1.0f);
    }

    Spatial &operator *= (const Spatial &parentSpatial) {

      this->scale *= parentSpatial.scale;
      this->position *= parentSpatial.scale;

      //this->position -= parentSpatial.position; // to rotation center space
      this->position.Rotate(parentSpatial.orientation);
      //this->position += parentSpatial.position; // and restore
      this->position += parentSpatial.position;
      this->z += parentSpatial.z;
      this->orientation += parentSpatial.orientation;

      return *this;
    }

    Vector2 position;
    float z;
    //radian baseOrientation;
    //Vector2 rotationCenter; // relative to exact center
    radian orientation;
    Vector2 scale;
  };

  class SpatialNode {

    public:
      //SpatialNode() : parent(0) {};
      SpatialNode(const SpatialNode *parent = 0) : parent(parent) {};
      virtual ~SpatialNode() {};

      Spatial GetDerivedSpatial() const;
      //Spatial GetSpatial();

      const SpatialNode *parent;
      Spatial spatial;

  };

  class Camera : public SpatialNode {

    public:
      Camera() : SpatialNode() {
        this->spatial.z = 1.0f;
        fov = 0.25f * pi;
      }
      virtual ~Camera() {};

      radian fov;

  };

  SDL_Window *GetSDLWindow();
  SDL_Renderer *GetSDLRenderer();
  int GetContextWidth();
  int GetContextHeight();

  int Modulo(int m, int n); // because negative numbers are treated dependent on implementation (with the % operator), *sigh*
  float ModuloFloat(float m, float n); // because negative numbers are treated dependent on implementation (with the modf function), *sigh*
  float ModulateIntoRange(float value, float min, float max);
  float Clamp(float value, float min, float max);
  float NormalizedClamp(float value, float min, float max);
  float Curve(float source, float bias = 1.0f);
  signed int SignSide(float n);
  float Random(float min, float max);

  Vector2 GetCubicBezierPosition(float u, const Vector2 &p1, const Vector2 &p2, const Vector2 &c1, const Vector2 &c2);

  int InitSDL(int w = 1280, int h = 720, bool vsync = true, e_WindowMode windowMode = e_WindowMode_Windowed, const std::string windowTitle = "Gaia game", int renderContextResolutionMultiplier = 1);
  int ExitSDL();
  Uint32 GetPixel(SDL_Surface *surface, int x, int y);
  SDL_Texture *LoadSDLTexture(const std::string &filename, e_FilterMode filterMode = e_FilterMode_Nearest, int *w = 0, int *h = 0);
  SDL_Texture *RenderText(TTF_Font *font, const std::string &text, const SDL_Color &color, e_FilterMode filterMode, int *w = 0, int *h = 0);

  Vector2 WorldToScreenVector(const Vector2 &worldVector, const Viewport &viewport, const Camera &camera);
  Spatial WorldToScreenSpatial(const Spatial &worldSpatial, const Viewport &viewport, const Camera &camera);

}

#endif
