#include "usql.h"
#include "exception.h"
#include "ml_date.h"
#include "ml_string.h"

#include <algorithm>
#include <fstream>

namespace usql {



std::unique_ptr<Table> USql::execute_create_table(CreateTableNode &node) {
	check_table_not_exists(node.table_name);

	Table table{node.table_name, node.cols_defs};
	m_tables.push_back(table);

	return create_stmt_result_table(0, "table created", 0);
}


std::unique_ptr<Table> USql::execute_create_table_as_table(CreateTableAsSelectNode &node) {
	check_table_not_exists(node.table_name);

	auto select = execute_select((SelectFromTableNode &) *node.select_table);

	// create table
	Table new_table{node.table_name, select->m_col_defs};
	m_tables.push_back(new_table);

	// copy rows
	// must be here, if rows are put into new_table, they are lost during m_tables.push_table
	Table *table = find_table(node.table_name);
	for( Row& orig_row : select->m_rows) {
		table->commit_copy_of_row(orig_row);
	}

	select.release();	// is it correct? hoping not to release select table here and then when releasing CreateTableAsSelectNode

	return create_stmt_result_table(0, "table created", table->m_rows.size());
}



std::unique_ptr<Table> USql::execute_drop(DropTableNode &node) {
	auto name_cmp = [node](const Table& t) { return t.m_name == node.table_name; };

	auto table_def = std::find_if(begin(m_tables), end(m_tables), name_cmp);
	if (table_def != std::end(m_tables)) {
		m_tables.erase(table_def);
		return create_stmt_result_table(0, "drop succeeded", 0);
	}

	throw Exception("table not found (" + node.table_name + ")");
}

std::unique_ptr<Table> USql::execute_set(SetNode &node) {
	Settings::set_setting(node.name, node.value);
	return create_stmt_result_table(0, "set succeeded", 1);
}

std::unique_ptr<Table> USql::execute_show(ShowNode &node) {
	std::string value = Settings::get_setting(node.name);
	return create_stmt_result_table(0, "show succeeded: " + value, 1);
}


std::unique_ptr<Table> USql::create_stmt_result_table(long code, const std::string &text, size_t affected_rows) {
	std::vector<ColDefNode> result_tbl_col_defs{};
	result_tbl_col_defs.emplace_back("code", ColumnType::integer_type, 0, 1, false);
	result_tbl_col_defs.emplace_back("desc", ColumnType::varchar_type, 1, 48, false);
	result_tbl_col_defs.emplace_back("affected_rows", ColumnType::integer_type, 0, 1, true);

	auto table_def = std::make_unique<Table>("result", result_tbl_col_defs);

	Row& new_row = table_def->create_empty_row();
	new_row.setIntColumnValue(0, code);
	new_row.setStringColumnValue(1, text.size() <= 48 ? text : text.substr(0,48));
	new_row.setIntColumnValue(2, (long)affected_rows);
	table_def->commit_row(new_row);

    return table_def;
}



std::unique_ptr<Table> USql::execute_load(LoadIntoTableNode &node) {
	// find source table
	Table *table_def = find_table(node.table_name);

	// read data
	// std::ifstream ifs(node.filename);
	// std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
	// load rows
	// auto rows_cnt = table_def->load_csv_string(content);

	auto rows_cnt = table_def->load_csv_file(node.filename);

	return create_stmt_result_table(0, "load succeeded", rows_cnt);
}


std::unique_ptr<Table> USql::execute_save(SaveTableNode &node) {
	// find source table
	Table *table_def = find_table(node.table_name);

	// make csv string
	std::string csv_string = table_def->csv_string();

	// save data
	std::ofstream file(node.filename);
	file << csv_string;
	file.close();

	return create_stmt_result_table(0, "save succeeded", table_def->rows_count());
}


}	// namespace
