#include "duckdb/function/table/system_functions.hpp"
#include "duckdb/common/types/chunk_collection.hpp"
#include "duckdb/main/config.hpp"
#include "duckdb/main/client_context.hpp"

namespace duckdb {

struct DuckDBSettingValue {
	string name;
	string value;
	string description;
	string input_type;
};

struct DuckDBSettingsData : public FunctionOperatorData {
	DuckDBSettingsData() : offset(0) {
	}

	vector<DuckDBSettingValue> settings;
	idx_t offset;
};

static unique_ptr<FunctionData> DuckDBSettingsBind(ClientContext &context, TableFunctionBindInput &input,
                                                   vector<LogicalType> &return_types, vector<string> &names) {
	names.emplace_back("name");
	return_types.emplace_back(LogicalType::VARCHAR);

	names.emplace_back("value");
	return_types.emplace_back(LogicalType::VARCHAR);

	names.emplace_back("description");
	return_types.emplace_back(LogicalType::VARCHAR);

	names.emplace_back("input_type");
	return_types.emplace_back(LogicalType::VARCHAR);

	return nullptr;
}

unique_ptr<FunctionOperatorData> DuckDBSettingsInit(ClientContext &context, const FunctionData *bind_data,
                                                    const vector<column_t> &column_ids,
                                                    TableFilterCollection *filters) {
	auto result = make_unique<DuckDBSettingsData>();

	auto &config = DBConfig::GetConfig(context);
	auto options_count = DBConfig::GetOptionCount();
	for (idx_t i = 0; i < options_count; i++) {
		auto option = DBConfig::GetOptionByIndex(i);
		D_ASSERT(option);
		DuckDBSettingValue value;
		value.name = option->name;
		value.value = option->get_setting(context).ToString();
		value.description = option->description;
		value.input_type = LogicalTypeIdToString(option->parameter_type);

		result->settings.push_back(move(value));
	}
	for (auto &ext_param : config.extension_parameters) {
		Value setting_val;
		string setting_str_val;
		if (context.TryGetCurrentSetting(ext_param.first, setting_val)) {
			setting_str_val = setting_val.ToString();
		}
		DuckDBSettingValue value;
		value.name = ext_param.first;
		value.value = move(setting_str_val);
		value.description = ext_param.second.description;
		value.input_type = ext_param.second.type.ToString();

		result->settings.push_back(move(value));
	}
	return move(result);
}

void DuckDBSettingsFunction(ClientContext &context, const FunctionData *bind_data, FunctionOperatorData *operator_state,
                            DataChunk *input, DataChunk &output) {
	auto &data = (DuckDBSettingsData &)*operator_state;
	if (data.offset >= data.settings.size()) {
		// finished returning values
		return;
	}
	// start returning values
	// either fill up the chunk or return all the remaining columns
	idx_t count = 0;
	while (data.offset < data.settings.size() && count < STANDARD_VECTOR_SIZE) {
		auto &entry = data.settings[data.offset++];

		// return values:
		// name, LogicalType::VARCHAR
		output.SetValue(0, count, Value(entry.name));
		// value, LogicalType::VARCHAR
		output.SetValue(1, count, Value(entry.value));
		// description, LogicalType::VARCHAR
		output.SetValue(2, count, Value(entry.description));
		// input_type, LogicalType::VARCHAR
		output.SetValue(3, count, Value(entry.input_type));
		count++;
	}
	output.SetCardinality(count);
}

void DuckDBSettingsFun::RegisterFunction(BuiltinFunctions &set) {
	set.AddFunction(
	    TableFunction("duckdb_settings", {}, DuckDBSettingsFunction, DuckDBSettingsBind, DuckDBSettingsInit));
}

} // namespace duckdb
