#include "renderer.h"
#include <SDL2/SDL_log.h>
#include <SDL2/SDL_rect.h>
#include <SDL2/SDL_render.h>
#include <cassert>
#include <SDL2/SDL_video.h>
#include <SDL2/SDL_image.h>
#include <filesystem>

namespace rogue {
RenderData::~RenderData() {
	SDL_DestroyRenderer(this->renderer); // also frees textures
	SDL_DestroyWindow(this->window);
	Render::clear_render_data();
}

RenderDataSetup &RenderDataSetup::with_window(char const *name, Uint32 flags) {
	assert(this->window == nullptr && "Cannot initialize window twice");
	SDL_DisplayMode mode;
	SDL_GetCurrentDisplayMode(0, &mode);
	this->window = SDL_CreateWindow(name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, mode.w, mode.h, flags);
	return *this;
}

RenderDataSetup &RenderDataSetup::with_renderer(Uint32 flags) {
	assert(this->renderer == nullptr && "Cannot initialize renderer twice");
	assert(this->window != nullptr && "Cannot initialize renderer before window");
	this->renderer = SDL_CreateRenderer(this->window, -1, flags);
	return *this;
}

RenderDataSetup &RenderDataSetup::with_resource_path(std::filesystem::path path) {
	assert(!this->resource_base_path.has_value() && "Cannot set base resource path twice");
	this->resource_base_path = path;
	return *this;
}

RenderDataSetup &RenderDataSetup::with_sprite(std::filesystem::path sprite_path) {
	if(SDL_Texture *texture{this->load_texture(sprite_path)}) {
		this->sprites.push_back(texture);
	}
	return *this;
}

RenderDataSetup &RenderDataSetup::with_menu(std::filesystem::path file_path) {
	if(SDL_Texture *texture{this->load_texture(file_path)}) {
		this->menus.push_back(texture);
	}
	return *this;
}

RenderData RenderDataSetup::build() {
	assert(this->window != nullptr && "Cannot build RenderData without a window");
	assert(this->renderer != nullptr && "Cannot build RenderData without a renderer");
	assert(this->sprites.size() > 0 && "Cannot build RenderData with 0 sprites");
	RenderData data;
	data.window=this->window;
	data.renderer=this->renderer;
	data.sprites = this->sprites;
	data.menus = this->menus;
	return data;
}

SDL_Texture *RenderDataSetup::load_texture(std::filesystem::path file_path) {
	assert(this->renderer != nullptr && "Cannot create sprites without a renderer");
	assert(this->resource_base_path.has_value() && "Resource base path has to be set before loading sprites");
	std::filesystem::path complete_path{this->resource_base_path.value() / file_path};
	SDL_Log("Adding sprite: %s", complete_path.string().c_str());
	assert(std::filesystem::exists(complete_path) && "Sprite path has to exist");
	if(SDL_Texture *texture{IMG_LoadTexture(this->renderer, complete_path.string().c_str())}) {
		return texture;
	} else {
		SDL_Log("Failed to load texture %s reason: %s", complete_path.string().c_str(), SDL_GetError());
		return nullptr;
	}
}

Tile Render::camera_center{0u, 0u};
RenderData* Render::data{nullptr};
unsigned Render::camera_width{10};
Tile Render::camera_offset{0, 0};
SDL_Point Render::window_size{0, 0};
int Render::tile_screen_width{2};
int Render::half_tile_screen_width{1};

#define assert_render_initialized() assert(Render::data != nullptr && __func__ && " provide_render_data has to be called before any other Render functions")

void Render::clear_render_data() {
	assert_render_initialized();
	Render::data = nullptr;
}

void Render::provide_render_data(RenderData &data) {
	Render::data = &data;
}

void Render::start_frame(Tile camera_center, unsigned fov) {
	assert_render_initialized();
	Render::camera_width = fov;
	SDL_SetRenderDrawColor(Render::data->renderer, 0u, 0u, 0u, 255u);
	SDL_RenderClear(Render::data->renderer);

	SDL_GetWindowSize(Render::data->window, &window_size.x, &window_size.y);
	Render::tile_screen_width = int(window_size.x / Render::camera_width);
	Render::half_tile_screen_width = Render::tile_screen_width >> 1;

	Render::camera_center = camera_center;
	Render::camera_offset.x = -Render::camera_center.x * Render::tile_screen_width + (window_size.x >> 1);
	Render::camera_offset.y = -Render::camera_center.y * Render::tile_screen_width + (window_size.y >> 1);
}

void Render::draw(Tile world_space_tile, Sprite sprite) {
	assert_render_initialized();
	if(abs(world_space_tile.x - Render::camera_center.x) > Render::camera_width / 2
	|| abs(world_space_tile.y - Render::camera_center.y) > Render::camera_width / 2) {
		return;
	}
	SDL_Rect const rect{
		.x = (world_space_tile.x * Render::tile_screen_width) - Render::half_tile_screen_width + Render::camera_offset.x,
		.y = (world_space_tile.y * Render::tile_screen_width) - Render::half_tile_screen_width + Render::camera_offset.y,
		.w = Render::tile_screen_width,
		.h = Render::tile_screen_width
	};
	SDL_RenderCopy(Render::data->renderer, Render::data->sprites[sprite], 0 , &rect);
}

void Render::draw_menu(Sprite menu) {
	assert_render_initialized();
	Render::start_frame({0, 0});
	SDL_Rect const rect {
		.x = window_size.x/2 - window_size.y/2,
		.y = 0,
		.w = window_size.y,
		.h = window_size.y
	};
	SDL_RenderCopy(Render::data->renderer, Render::data->menus[menu], 0, &rect);
}

void Render::draw_healthbar(int current, int max, SDL_Rect area, int border) {
	assert_render_initialized();
	SDL_SetRenderDrawColor(Render::data->renderer, 0,0, 0, 255);
	SDL_RenderFillRect(Render::data->renderer, &area);
	area.x += border;
	area.y += border;
	area.h -= border * 2;
	area.w = ((area.w - border * 2) * current) / max;
	SDL_SetRenderDrawColor(Render::data->renderer, 100,100, 100, 255);
	SDL_RenderFillRect(Render::data->renderer, &area);
}

void Render::finish_frame() {
	assert_render_initialized();
	SDL_RenderPresent(Render::data->renderer);
}
}
