#include <memory>
#include <unordered_map>

#include "backend/Enemy.h"
#include "backend/EnemyFactory.h"
#include "backend/LocationFactory.h"
#include "backend/Object.h"
#include "backend/ObjectFactory.h"

#include "GameData.h"

using namespace std;

GameData & GameData::get_instance() {
	static GameData instance;
	return instance;
}

GameData::GameData() {
	this->db = make_unique<DB>("kerkersendraken.db");
}

Enemy * GameData::create_enemy(const string & name) {
	static DBStatement query = this->db->prepare(R"(
		select
			naam,
			omschrijving,
			minimumobjecten,
			maximumobjecten,
			levenspunten,
			aanvalskans,
			minimumschade,
			maximumschade
		from Vijanden
		where lower(naam) = lower(?)
		limit 1
	)");
	query.reset()
		.bind(name)
	;
	
	try {
		auto row = query.row();
		return EnemyFactory::create_enemy(row.col<const char *>(0), row.col<const char *>(1));
		// TODO: set all other properties
	} catch (...) {
		return EnemyFactory::create_enemy(name.c_str());
	}
}

static const unordered_map<string, ObjectType> type_map = {
	{ "teleportatiedrank", ObjectType::CONSUMABLE },
	{ "ervaringsdrank", ObjectType::CONSUMABLE },
	{ "levenselixer", ObjectType::CONSUMABLE },
	{ "wapenrusting", ObjectType::ARMOR },
	{ "wapen", ObjectType::WEAPON },
	{ "goudstukken", ObjectType::GOLD },
};

Object * GameData::create_object(const string & name) {
	static DBStatement query = this->db->prepare(R"(
		select
			type,
			omschrijving,
			minimumwaarde,
			maximumwaarde,
			bescherming
		from Objecten
		where lower(naam) = lower(?)
		limit 1
	)");
	query.reset()
		.bind(name)
	;
	
	try {
		auto row = query.row();
		string type = row.col<const char *>(0);
		if (!type_map.contains(type)) throw std::exception();
		return ObjectFactory::create_object({
			.name = name.c_str(),
			.description = row.col<const char *>(1, nullptr),
			.type = type_map.at(type),
			.min_value = row.col<int>(2),
			.max_value = row.col<int>(3),
			.protection = row.col<int>(4),
		});
	} catch (...) {
		return ObjectFactory::create_object(name.c_str());
	}
}

Location * GameData::create_location(const string & name) {
	static DBStatement query = this->db->prepare(R"(
		select
			naam,
			beschrijving
		from Locaties
		where lower(naam) = lower(?)
		limit 1
	)");
	query.reset()
		.bind(name)
	;
	
	try {
		auto row = query.row();
		return LocationFactory::create_location(row.col<const char *>(0), row.col<const char *>(1));
	} catch (...) {
		return LocationFactory::create_location(name.c_str());
	}
}

void GameData::leaderbord_add(const string & name, unsigned int gold) {
	static DBStatement stmt = this->db->prepare(R"(
		insert into Leaderboard (naam, goudstukken)
		values (?, ?)
	)");
	stmt.reset()
		.bind(name)
		.bind(gold)
	;
	stmt.execute();
}

vector<string> GameData::random_locations(unsigned count) {
	static DBStatement query = this->db->prepare(R"(
		select naam
		from Locaties
		order by random()
		limit ?
	)");
	query.reset()
		.bind(count)
	;

	vector<string> names = {};
	for (DBQueryRow & row : query.rows()) {
		names.push_back(row.col<const char *>(0));
	}
	return names;
}