#include "CodeBuilder.h"
#include "FormatToStream.h"
#include "Pgsql_Result.h"
#include "IndentationConfig.h"
#include "LanguageConfig.h"
#include "ResultLoopTemplate.h"
#include "StructureTemplate.h"
#include "util.h"
#include <QTextStream>

void CodeBuilder::GenCodeForExecutingQuery(QTextStream &q, const QString &query, const Pgsql::Result &result, QString structname)
{
	QString loop_template = m_configuration->resultLoopTemplate()->m_loopTemplate;;
	QString query_string_literal = ConvertToMultiLineCString(query);

	ColumnDataList columnData = createColumnDataList(result);

	auto get_var_func = [&] (QTextStream &out, QString var) {
		if (var == "structname") out << structname;
		else if (var == "queryliteral") out << query;
		else if (var == "assignfields") genFieldRetrieval(out, columnData);
	};


	// [optional] Gen declaration of result structure
	GenReturnStructDefinition(q, columnData, structname);

	// assume we have connection? What name???
	FormatToStream(q, loop_template, get_var_func);

	// gen code for creating and executing statement
	// - bind parameters

	// Gen code for iterating through result and filling the result struct/or local variables


}

void CodeBuilder::GenReturnStructDefinition(QTextStream &q, const ColumnDataList &columns, QString structname) const
{
	std::shared_ptr<const StructureTemplate> templ = m_configuration->structureTemplate();

	auto struct_callback = [&] (QTextStream &out, QString var) {
		if (var == "structname") out << structname;
		else if (var == "structfields") genStructFields(out, columns);
	};

	FormatToStream(q, templ->m_structTemplate, struct_callback);
}

void CodeBuilder::genStructFields(QTextStream &q, const ColumnDataList &columns) const
{
	std::shared_ptr<const StructureTemplate> templ = m_configuration->structureTemplate();
	QString field_format = templ->m_fieldTemplate;
	const int field_indent_levels = templ->m_fieldIndentation;
	QString field_indent_string = m_configuration->indentationConfig()->getIndentString(field_indent_levels);

	for (auto col : columns) {
		QString field_name = col.varName;
		Oid type_oid = col.oid;
		q << field_indent_string;
		genFieldDeclaration(q, field_format, field_name, type_oid);
		q << "\n";
		// Unfortunatly there is no easy reliable way to determine if a column can contain null values
		// For simple result columns we can have a look at the original column and if that has a NOT NULL constraint
		// then we can assume NOT NULL for the result if there are no LEFT/RIGHT JOINS or other constructs that can introduce NULL
		// values

		// Any way at generation time we might want to be able to specify the null handle
		// - exception/error return
		// - magic value
		// - std::optional
		// - boolean flags
		// - null pointer (useful for languages where this has no cost, other cases boolean flags will be more performant)
	}
}

QString CodeBuilder::columnNameToVariableName(QString column_name) const
{
	return m_configuration->columnNameToFieldName(column_name);
}

TypeMappingResult CodeBuilder::getTypeName(Oid dbtype) const
{
	return m_configuration->getTypeName(dbtype);
}

void CodeBuilder::genFieldRetrieval(QTextStream &q, const ColumnDataList &columns) const
{
	QString assign_result_field_template = m_configuration->resultLoopTemplate()->m_retrieveValueTemplate;
	for (auto col : columns) {
		FormatToStream(q, assign_result_field_template, [&col] (QTextStream &out, QString var) {
			if (var == "varname") out << col.varName;
		});
	}
}

void CodeBuilder::genFieldDeclaration(QTextStream &q, const QString &format, const QString &field_name, Oid column_type) const
{
	TypeMappingResult type_name = getTypeName(column_type);
	FormatToStream(q, format, [&] (QTextStream &out, QString var) {
		if (var == "typename") out << type_name.codeType();
		else if (var == "varname") out << field_name;
		else if (var == "dbtype") out << type_name.dbType();
	});
}

CodeBuilder::ColumnDataList CodeBuilder::createColumnDataList(const Pgsql::Result &result) const
{
	ColumnDataList list;
	list.reserve(static_cast<size_t>(result.cols()));
	for (int column = 0; column < result.cols(); ++column) {
		Oid type_oid = result.type(column);
		QString column_name = result.getColName(column);
		QString field_name = columnNameToVariableName(column_name);
		list.push_back({type_oid, column_name, field_name});
	}
	return list;
}
