#include <sqlite_wrapper.hpp>
#include <boost/format.hpp>
#include <mof/ConsoleIO.hpp>
#include <mof/utilities.hpp>
#include <mof/script/ObjectData.hpp>
#include <sqlite3.h>
#include <expatJP.h>
#include <algorithm>
#include <boost/lexical_cast.hpp>

namespace 
{
//{{{ callback_select
	int callback_select(void* data, int argc, char** argv, char** colnames)
	{
		mof::script::GameData* gamedata = static_cast<mof::script::GameData*>(data);
		gamedata->data_.resize(gamedata->data_.size() + 1);
		static char converted_text[512];
		for (int i = 0 ; i < argc; i++) {
			char* colname = colnames[i];	
			char* value = argv[i];	
			//if (!value) continue;
			if (!value) throw std::runtime_error(std::string("sqlite_wrapper ") + colname + ": null value");
			int converted_len = UTF8toSJIS(value, strlen(value), converted_text, mof::lengthOf(converted_text));
			gamedata->data_.back()[colname] = mof::tstring(converted_text, converted_len);
		}
		return 0;
	}
//}}}
}

//namespace mof
//{
//namespace script
//{
//{{{ impl
	struct sqlite_wrapper::impl
	{
		sqlite3* db;

		impl()
			: db(NULL)
		{
		}
	};
//}}}
//{{{ constructor
	sqlite_wrapper::sqlite_wrapper()
		: pimpl_(new impl())
	{
		if (sqlite3_open("gamedata.db", &pimpl_->db) != SQLITE_OK) {
			throw std::runtime_error("Faild: sqlite3_open");
		}

		if (sqlite3_exec(pimpl_->db, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK) {
			throw std::runtime_error("Faild: begin transaction");
		}

		// TODO 外部から設定
		/*
		sqlite3_create_function
		(
			pimpl_->db,
			"ehves_power", 3,// n_args
			SQLITE_UTF8, NULL,
			glue_ehves_power, NULL, NULL
		);
		*/

	}
//}}}
//{{{ destructor
	sqlite_wrapper::~sqlite_wrapper()
	{
		/*
		if (sqlite3_exec(pimpl_->db, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK) {
			throw std::runtime_error("Faild: commit");
		}
		*/

		sqlite3_close(pimpl_->db);
	}
//}}}
//{{{ query_item_data
	mof::script::GameData::ptr sqlite_wrapper::query_item_profile()
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* sql = 
			"SELECT item.*, item_id as id, item_profile.durability, item.durability as max_durability, "
			" attached, stored, equipped "
			"FROM item_profile, item WHERE item.id = item_profile.item_id";
		char* err;
		if (sqlite3_exec(pimpl_->db, sql, callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		return p;
	}
//}}}
//{{{ query_relic_data
	mof::script::GameData::ptr sqlite_wrapper::query_relic_profile()
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* sql = 
			"SELECT relic_profile.id, relic_id, num, name, icon_type, grade, explanation "
			"FROM relic_profile, relic WHERE relic.id = relic_profile.relic_id";
		char* err;
		if (sqlite3_exec(pimpl_->db, sql, callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		return p;
	}
//}}}
//{{{ query_ideal_data
	mof::script::GameData::ptr sqlite_wrapper::query_ideal_profile()
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* sql = 
			"SELECT ideal_profile.id, ideal_id, name, icon_type, type, attend, explanation, durability "
			"FROM ideal_profile, ideal WHERE ideal.id = ideal_profile.ideal_id";
		char* err;
		if (sqlite3_exec(pimpl_->db, sql, callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		return p;
	}
//}}}
//{{{ query_area_service
	mof::script::GameData::ptr sqlite_wrapper::query_area_service(float ehves, float bolony, float stra)
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* t_sql = 
			"SELECT grade_condition.*, service.id AS id, service.name AS name, grade, explanation "
			"FROM service, grade_condition "
			"WHERE service.id = grade_condition.service_id AND "
			"%f >= grade_condition.ehves AND "
			"%f >= grade_condition.bolony AND "
			"%f >= grade_condition.stra "
			"ORDER BY grade_condition.ehves + grade_condition.bolony + grade_condition.stra DESC "
			"LIMIT 3";// 1つのランドにつき、3つのサービスを持つ
		mof::tstring sql = (boost::format(t_sql) % ehves % bolony % stra).str();
		char* err;
		if (sqlite3_exec(pimpl_->db, sql.c_str(), callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		//DEBUG_PRINT(*p);
		return p;
	}
//}}}
//{{{ query_service_info
	mof::script::GameData::ptr sqlite_wrapper::query_service_info(int service_id, int grade)
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* t_sql = 
			"SELECT * "
			"FROM service, grade_condition "
			"WHERE service.id = %d and grade_condition.service_id = %d and grade_condition.grade = %d ";
		std::string sql = (boost::format(t_sql) % service_id % service_id % grade).str();
		char* err;
		if (sqlite3_exec(pimpl_->db, sql.c_str(), callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		return p;
	}
//}}}
//{{{ query_shop_list
	mof::script::GameData::ptr sqlite_wrapper::query_shop_list(int service_id, int grade)
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* t_sql = 
			"SELECT * "
			"FROM shop_list, item "
			"WHERE shop_list.service_id = %d and shop_list.grade = %d and item.id = shop_list.item_id "
			"ORDER BY item.type, item.price";
		std::string sql = (boost::format(t_sql) % service_id % grade).str();
		char* err;
		if (sqlite3_exec(pimpl_->db, sql.c_str(), callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		return p;
	}
//}}}
//{{{ query_ideal_church_recipes
	mof::script::GameData::ptr sqlite_wrapper::query_ideal_church_recipe(int item_id)
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* t_sql = 
			"SELECT ideal.*, recipe1.grade "
			"FROM recipe1, ideal "
			"WHERE recipe1.item_id = %d and ideal.id = recipe1.ideal_id" ;
		std::string sql = (boost::format(t_sql) % item_id ).str();
		char* err;
		if (sqlite3_exec(pimpl_->db, sql.c_str(), callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		return p;
	}
//}}}
//{{{ query_area_profile
	mof::script::GameData::ptr sqlite_wrapper::query_area_profile(int x, int y)
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* t_sql = 
			"SELECT name, ehves, bolony, stra "
			"FROM area "
			"WHERE x = %d and y = %d ";
		std::string sql = (boost::format(t_sql) % x % y).str();
		char* err;
		if (sqlite3_exec(pimpl_->db, sql.c_str(), callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		return p;
	}
//}}}
//{{{ query_main_profile
	mof::script::GameData::ptr sqlite_wrapper::query_main_profile()
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* sql = 
			"SELECT * "
			"FROM main_profile ";
		char* err;
		if (sqlite3_exec(pimpl_->db, sql, callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		return p;
	}
//}}}
//{{{ query_history_profile
	mof::script::GameData::ptr sqlite_wrapper::query_history_profile()
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* sql = 
			"SELECT * "
			"FROM history_profile "
			"ORDER BY timestamp ";
		char* err;
		if (sqlite3_exec(pimpl_->db, sql, callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		return p;
	}
//}}}
//{{{ insert_history_profile
	void sqlite_wrapper::insert_history_profile(int x, int y, const mof::tstring& event)
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* sql1 = 
			"SELECT coalesce(max(id), -1) as max_id, coalesce(max(timestamp), -1) as max_timestamp "
			"FROM history_profile ";
		char* err;
		if (sqlite3_exec(pimpl_->db, sql1, callback_select, (void*)p.get(), &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql1 + "]" + err);
			sqlite3_free(err);
			throw e;
		}
		int next_id = boost::lexical_cast<int>(p->data_[0]["max_id"]) + 1;
		int next_timestamp = boost::lexical_cast<int>(p->data_[0]["max_timestamp"]) + 1;

		static const char* t_sql2 = 
			"INSERT INTO history_profile VALUES('%d', '%d', '%s', '%d', '%d')";
		std::string sql2 = (boost::format(t_sql2) % next_id % next_timestamp % event % x % y).str();
		if (sqlite3_exec(pimpl_->db, sql2.c_str(), NULL, NULL, &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql2 + "]" + err);
			sqlite3_free(err);
			throw e;
		}

	}
//}}}
//{{{ update_main_profile
	void sqlite_wrapper::update_main_profile(const mof::script::GameData::ptr& game_data)
	{
		using namespace mof::script;
		GameData::ptr p = std::make_shared<GameData>();
		static const char* t_sql = 
			"UPDATE main_profile SET forest_element = '%d', earth_element = '%d', water_element = '%d' "
			"WHERE id = '%d' ";
		char* err;
		std::string sql = (boost::format(t_sql) % 
				game_data->data_[0]["forest_element"] %
				game_data->data_[0]["earth_element"] %
				game_data->data_[0]["water_element"] %
				game_data->data_[0]["id"] 
				).str();
		if (sqlite3_exec(pimpl_->db, sql.c_str(), NULL, NULL, &err) != SQLITE_OK) {
			std::logic_error e(std::string("[") + sql + "]" + err);
			sqlite3_free(err);
			throw e;
		}

	}
//}}}
//{{{ save
	void sqlite_wrapper::save()
	{
		if (sqlite3_exec(pimpl_->db, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK) {
			throw std::runtime_error("Faild: commit");
		}
		if (sqlite3_exec(pimpl_->db, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK) {
			throw std::runtime_error("Faild: begin transaction");
		}

	}
//}}}
//}// namespace script
//}// namespace mof
