#include <iostream>
#include <cstdint>
#include <cmath>
#include <cassert>
#include <map>
#include <list>
#include <unordered_set>
#include <SFML/Graphics.hpp>

namespace procgen {
	class prng_t {
		uint32_t state = 0;

	public:
		void seed(uint32_t seed);
		uint32_t next(void);
		float next_float(void);
		void unit_vec(float out[2]);

	};

	class perlin_noise_t {
		size_t size;
		float (*table)[2] = nullptr;

		float table_dot(size_t nx, size_t ny, float dx, float dy);

	public:
		~perlin_noise_t();

		void generate(prng_t *prng, size_t size);
		float get(float x, float y, float scale);
	};
}

namespace render { class state_t; }

namespace world {
	#define SECTOR_SIZE 8

	class tile_t {
	public:
		char type;
	};

	class sector_index_t {
	public:
		int64_t x, y;

		sector_index_t ();
		sector_index_t (int64_t x_, int64_t y_);
		sector_index_t (float x_, float y_);
		bool operator<(sector_index_t B) const;
	};

	class entity_t;

	class sector_t {
	public:
		sector_index_t index;
		sf::FloatRect bounds;
		std::unordered_set<entity_t*> ents;

		bool empty = true;
		tile_t tiles[SECTOR_SIZE * SECTOR_SIZE];
	};

	class world_t {
		procgen::prng_t prng;
		procgen::perlin_noise_t perlin;
		std::map<sector_index_t, sector_t> sectors;

		void generate_tile(ssize_t lx, ssize_t ly, tile_t *tile);
		void generate(sector_t *sector, sector_index_t index);

	public:
		world_t(void);
		sector_t *get_sector(sector_index_t index);
		tile_t *get_tile(ssize_t x, ssize_t y);

		// FIXME: iterators instead of returning std::lists
		std::list<sector_t*> get_sectors(sf::FloatRect rect);
		std::list<entity_t*> get_entities(sf::FloatRect rect);

		void render(sf::RenderWindow *window);

		void debug_point(sf::Vector2f point);
	};

	class entity_t {
		world_t *parent_world;
		std::vector<sector_t*> parents;

		void link_to_sector(sector_t *sector);

	protected:
		friend world_t;
		size_t cookie = 0;

	public:
		sf::FloatRect bounds;

		void link(world_t *world);
		void unlink();

		virtual void render(render::state_t *render) = 0;
	};
}

namespace game {
	class state_t {
	public:
		world::world_t world;

		void start(void);
		void tick(void);
		void render(render::state_t *render);
	};
}

namespace interface {
	class state_t {
		sf::RenderWindow *window;
		game::state_t *game;

		struct {
			sf::Vector2f center;
			int target_zoom = 3;
			float zoom = 3.0f;
			bool dragging = false;
			sf::Vector2f drag_ref;
		} camera;

	public:
		state_t(sf::RenderWindow *window_, game::state_t *game);
		void tick(void);
		void render(void);
	};
}

namespace render {
	class animated_texture_t {
		friend state_t;
	protected:
		sf::Texture *frames = NULL;
		size_t frame_count;

	public:
		~animated_texture_t(void);
		bool load(std::string prefix, size_t frame_count_);
	};

	class state_t {
		sf::RenderWindow *window;
		double now;
	public:

		state_t(sf::RenderWindow *window_);
		void begin_frame(double time_);
		void end_frame(void);
		void render(game::state_t *game);
		void render(animated_texture_t *anim, sf::FloatRect bounds);
	};
}

// Divide and round to minus infinity.
template <typename T>
T divide_rmi(T x, T y, T *rem)
{
	T rv;

	if (x >= 0) {
		*rem = x % y;
		return x / y;
	}

	rv = (x + 1) / y - 1;
	*rem = x - rv * y;
	return rv;
}

// Linear interpolation.
template <typename T>
T lerp(T a, T b, T x)
{
	return a * (1 - x) + b * x;
}

// Bilinear interpolation.
template <typename T>
T bilerp(T a, T b, T c, T d, T x, T y)
{
	T ab, cd;

	ab = lerp(a, b, x);
	cd = lerp(c, d, x);
	return lerp(ab, cd, y);
}