#include "table.h"
#include "csvreader.h"
#include "ml_string.h"
#include "fast_double_parser.h"

#include <charconv>
#include <fstream>
#include <algorithm>

namespace usql {

Table::Table(const std::string& name, const std::vector<ColDefNode>& columns) {
	m_name = name;
	m_col_defs = columns;
	m_rows.reserve(256);
}

Table::Table(const Table &other) {
	m_name = other.m_name;
	m_col_defs = other.m_col_defs;
	m_rows.reserve(other.m_rows.size());
	for(const Row& orig_row : other.m_rows) {
		commit_copy_of_row(orig_row);
	}
}

ColDefNode Table::get_column_def(const std::string &col_name) {
	auto name_cmp = [col_name](const ColDefNode& cd) { return cd.name == col_name; };

	auto col_def = std::find_if(begin(m_col_defs), end(m_col_defs), name_cmp);
	if (col_def != std::end(m_col_defs)) {
		return *col_def;
	} else {
		throw Exception("column does not exist (" + col_name + ")");
	}
}

ColDefNode Table::get_column_def(int col_index) {
	if (col_index >= 0 && col_index < columns_count()) {
		return m_col_defs[col_index];
	} else {
		throw Exception("column with this index does not exists (" + std::to_string(col_index) + ")");
	}
}

Row& Table::create_empty_row() {
	m_rows.emplace_back(columns_count());
	return m_rows.back();
}

std::string Table::csv_string() {
	// header
	std::string out_string;
	for(int i = 0; i < m_col_defs.size(); i++) {
		if (i > 0) out_string += ",";
		out_string += m_col_defs[i].name;
	}

	// rows
	for (auto & m_row : m_rows) {
		std::string csv_line{"\n"};
		for(int i = 0; i < m_col_defs.size(); i++) {
			if (i > 0) csv_line += ",";

			auto & col = m_row[i];
			if (!col.isNull()) {
				csv_line += col.getStringValue();	// TODO handle enclosing commas etc
			}
		}
		out_string += csv_line;
	}

	return out_string;
}

int Table::load_csv_string(const std::string &content) {
	std::vector<ColDefNode> &colDefs = m_col_defs;

	CsvReader csvparser{};
	int row_cnt = csvparser.parseCSV2(content, colDefs, *this);

	return row_cnt;
}

int Table::load_csv_file(const std::string &filename) {
	std::vector<ColDefNode> &colDefs = m_col_defs;

	// allocate enough space
	int line_size = 128;

	std::ifstream in(filename, std::ifstream::ate | std::ifstream::binary);
	auto file_size = in.tellg(); 

  	std::ifstream infile(filename);
	if (infile.good()) {
		std::string sLine;
    		std::getline(infile, sLine);
    		line_size = (int)sLine.size();
	}
	infile.close();

	if (file_size > 0) {
		auto new_size = m_rows.size() + int(file_size / line_size * 1.20);
		m_rows.reserve(new_size);
	}

	// load rows
	CsvReader csvparser{};
	int row_cnt = csvparser.parseCSV(filename, colDefs, *this);

	return row_cnt;
}

void Table::create_row_from_vector(const std::vector<ColDefNode> &colDefs, const std::vector<std::string> &csv_line) {
	// prepare empty new_row
	Row& new_row = create_empty_row();

	// copy values
	for (int i = 0; i < std::min<int>(columns_count(), csv_line.size()); i++) {
		const ColDefNode & col_def = colDefs[i];

		if (csv_line[i].empty()) {
			new_row.setColumnNull(col_def.order);
		} else if (col_def.type == ColumnType::integer_type) {
			new_row.setIntColumnValue(col_def.order, string_to_long(csv_line[i]));
		} else if (col_def.type == ColumnType::float_type) {
			new_row.setFloatColumnValue(col_def.order, string_to_double(csv_line[i]));
		} else if (col_def.type == ColumnType::varchar_type) {
			new_row.setStringColumnValue(col_def.order, csv_line[i]);
		} else if (col_def.type == ColumnType::date_type) {
			new_row.setDateColumnValue(col_def.order, csv_line[i]);
		} else if (col_def.type == ColumnType::bool_type) {
			new_row.setBoolColumnValue(col_def.order, csv_line[i]);
		} else
			throw Exception("unsupported column type");
	}

	// append new_row
	commit_row(new_row);
}

double Table::string_to_double(const std::string &s) {
	double result;
	const char * endptr = fast_double_parser::parse_number(s.c_str(), &result);
	if (endptr == nullptr) {
		throw Exception("error parsing as double: " + s);
	}
	return result;
}

long Table::string_to_long(const std::string &s) {
	try {
		return std::stol(s);
	} catch (std::invalid_argument &e) {
		throw Exception("error parsing as integer: " + s);
	}
}

void Table::print() {
	std::string out{"| "};
	std::string out2{"+-"};

	for(const auto& col_def : m_col_defs) {
		int col_size = Row::print_get_column_size(col_def);

		if (col_def.type==ColumnType::integer_type || col_def.type==ColumnType::float_type || col_def.type==ColumnType::bool_type)
			out.append(string_padd(col_def.name, col_size, ' ', false) + " | ");
		else
			out.append(string_padd(col_def.name, col_size, ' ', true) + " | ");

		out2.append(string_padd("-", col_size, '-', true) + "-+ ");
	}

	// std::cout << "** " << m_name << " **" << std::endl;
	std::cout << out << std::endl;
	std::cout << out2 << std::endl;

	for(auto& row : m_rows) {
		row.print(m_col_defs);
	}
	std::cout << std::endl;
}

void Table::commit_row(const Row &row) {
	try {
		validate_row(row);
	} catch (Exception &e) {
		m_rows.erase(m_rows.end() - 1);
		throw e;
	}
}

void Table::commit_copy_of_row(const Row &row) {
	Row& new_row = create_empty_row();

	for(int i = 0; i < m_col_defs.size(); i++) {
		ColValue &ct = row[i];

		if (ct.isNull()) {
			new_row.setColumnNull(i);
		} else {
			if (m_col_defs[i].type == ColumnType::integer_type) {
				new_row.setIntColumnValue(i, row[i].getIntValue());
			} else if (m_col_defs[i].type == ColumnType::float_type) {
				new_row.setFloatColumnValue(i, row[i].getDoubleValue());
			} else if (m_col_defs[i].type == ColumnType::varchar_type) {
				new_row.setStringColumnValue(i, row[i].getStringValue());
			} else if (m_col_defs[i].type == ColumnType::date_type) {
				new_row.setDateColumnValue(i, row[i].getDateValue());
			} else if (m_col_defs[i].type == ColumnType::bool_type) {
				new_row.setBoolColumnValue(i, row[i].getBoolValue());
			} else
				throw Exception("unsupported column type");
		}
	}

	validate_row(new_row);
}

void Table::validate_column(const ColDefNode *col_def, ValueNode *col_val) {
	if (!col_def->null && col_val->isNull()) {
		throw Exception("Column " + col_def->name + " cannot be null");
	}
	if (col_def->type == ColumnType::varchar_type && !col_val->isNull() && col_val->getStringValue().size() > col_def->length) {
		throw Exception("Column value of " + col_def->name + " is too long (" + col_val->getStringValue() + ")");
	}
}

void Table::validate_column(const ColDefNode *col_def, ColValue &col_val) {
	if (!col_def->null && col_val.isNull()) {
		throw Exception("Column " + col_def->name + " cannot be null");
	}
	if (col_def->type == ColumnType::varchar_type && !col_val.isNull() && col_val.getStringValue().size() > col_def->length) {
		throw Exception("Column value of " + col_def->name + " is too long (" + col_val.getStringValue() + ")");
	}
}

void Table::validate_row(const Row &row) {
	for(int i = 0; i < m_col_defs.size(); i++) {
		ColDefNode col_def = m_col_defs[i];
		ColValue &col_val = row[i];

		validate_column(&col_def, col_val);
	}
}

}	// namespace
