#include "ModuleBase.h"
#include <iostream>
#include <string>
#include <sqlite3.h>
#include <boost/algorithm/string.hpp>

namespace mdd {
	class ModuleSQL : public ModuleBase {
	private:
		std::string _dbname;
		sqlite3* _db;
		std::string _tbname;
		void erase_keyword(std::string& str, std::string key);
		struct _entity {
			std::string key;
			std::string type;
		};
		std::vector<_entity> _content;

	protected:
		bool configureChild(const json& config) override;

	public:
		ModuleSQL();
		~ModuleSQL();
		state update() override;
	};

	void ModuleSQL::erase_keyword(std::string& str, std::string key) {
		while (true) {
			int start = (int)str.find(key);
			if (start != -1) {
				str.erase(start, key.length());
			}
			else {
				return;
			}
		}
	}

	ModuleSQL::ModuleSQL()
	: ModuleBase(R"JSON(
        {
		"configure":{
            "database":{
				"value":""
			}
		},
        "GUI":{
            "name":{"value":"SQL"},
            "color":{"value":"#01a8ad"}
        }
        })JSON")
	{
		key = "ModuleSQL";
	}

	state ModuleSQL::update() {
		int rc;
		std::string sql;
		sqlite3_stmt* res;
		int step;
		// Create SQL statement
		sql = "SELECT * "\
			"FROM " + _tbname + " "\
			"WHERE " + _content[0].key + " = " + std::to_string(getInput(0)->getValue()[0]);

		std::cout << sql << std::endl;

		// Execute SQL statement
		rc = sqlite3_prepare_v2(_db, sql.c_str(), -1, &res, 0);

		if (rc != SQLITE_OK) {
			fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(_db));
			sqlite3_close(_db);
		}
		else {
			fprintf(stdout, "Operation done successfully\n");
		}

		step = sqlite3_step(res);
		state state = state::UNCHANGED;
		if (step == SQLITE_ROW) {
			int nCol = sqlite3_column_count(res);
			std::string parse_str = "{\"" + _tbname + "\" : {";
			for (size_t i = 0; i < nCol; i++)
			{
				//json val = (char*)sqlite3_column_text(res, i);
				getOutput(i)->setValue({ std::atof((char*)sqlite3_column_text(res, i)) });
			}
		}
		sqlite3_finalize(res);

		return state;
	}

	ModuleSQL::~ModuleSQL() {
		if (_db != nullptr)
		{
			sqlite3_close(_db);
		}
	}

	bool ModuleSQL::configureChild(const json& config) {
		auto jit = config.find("database");
		if (jit != config.end())
		{
			_dbname = jit.value()["value"].get<std::string>();
		}
		else
		{
			return false;
		}
		
		int rc = sqlite3_open(_dbname.c_str(), &_db);
		if (rc) {
			fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(_db));
			return false;
		}
		else {
			fprintf(stderr, "Opened database successfully\n");
		}
		std::string sql;
		sqlite3_stmt* res;
		int step;


		// Create SQL statement
		sql = "SELECT tbl_name  "\
			"FROM   sqlite_master "\
			"WHERE type = 'table' ";

		// Execute SQL statement
		rc = sqlite3_prepare_v2(_db, sql.c_str(), -1, &res, 0);

		if (rc != SQLITE_OK) {
			fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(_db));
			sqlite3_close(_db);
		}
		else {
			fprintf(stdout, "Operation done successfully\n");
		}

		step = sqlite3_step(res);

		if (step == SQLITE_ROW) {
			int nCol = sqlite3_column_count(res);
			for (size_t i = 0; i < nCol; i++)
			{
				_tbname = (char*)sqlite3_column_text(res, i);
			}

		}
		sqlite3_finalize(res);


		// Create SQL statement
		sql = "SELECT sql "\
			"FROM   sqlite_master "\
			"WHERE type = 'table' AND tbl_name = '" + _tbname + "'";
		//std::cout << sql << std::endl;
		// Execute SQL statement
		rc = sqlite3_prepare_v2(_db, sql.c_str(), -1, &res, 0);

		if (rc != SQLITE_OK) {
			fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(_db));
			sqlite3_close(_db);
		}
		else {
			fprintf(stdout, "Operation done successfully\n");
		}

		step = sqlite3_step(res);

		if (step == SQLITE_ROW) {
			std::string sql_res = (char*)sqlite3_column_text(res, 0);
			int start = sql_res.find("(");
			int end = sql_res.find(")");
			sql_res = sql_res.substr(++start, (size_t)(--end) - start);

			erase_keyword(sql_res, "KEY");
			erase_keyword(sql_res, "PRIMARY");
			erase_keyword(sql_res, "AUTOINCREMENT");
			erase_keyword(sql_res, "NOT NULL");

			std::string::iterator new_end = std::unique(sql_res.begin(), sql_res.end(), [](char lhs, char rhs)->bool { return (lhs == rhs) && (lhs == ' '); });
			sql_res.erase(new_end, sql_res.end());

			_content.clear();
			while (true) {
				end = sql_res.find(" ,");
				if (end == -1)
				{
					end = sql_res.length();
				}
				std::string sub_str = sql_res.substr(0, end);
				int mid = sub_str.find(" ");
				_entity enti;
				enti.key = sub_str.substr(0, mid);
				enti.type = sub_str.substr(mid + 1, sub_str.length() - (mid + 1));
				_content.push_back(enti);
				//std::cout << "'" << enti.key << "':  '" << enti.type << "'\n";
				if (end != sql_res.length())
				{
					sql_res.erase(0, end + 2);
				}
				else {
					break;
				}
			}

		}
		sqlite3_finalize(res);
		if (_content.empty())
		{
			return false;
		}

		if (inputs.size()<1)
		{
			inputs.push_back(std::make_shared<Input>(_content[0].key, inputs.size()));
			inputs.back()->setParent(shared_from_this());
		}
		else
		{
			inputs[0]->setName(_content[0].key);
		}
		
		if (outputs.size() > _content.size())
		{
			outputs.erase(outputs.begin() + (_content.size() - 1), outputs.begin() + (outputs.size() - 1));
		}
		for (int i = 0; i < outputs.size(); ++i)
		{
			outputs[0]->setName(_content[i].key);
		}
		for (int i = outputs.size(); i < _content.size(); ++i)
		{
			outputs.push_back(std::make_shared<Output>(_content[i].key, outputs.size()));
			outputs.back()->setParent(shared_from_this());
		}

		_base_config["configure"]["database"]["value"] = _dbname;
		return true;
	}
}