#include "planner/expression_binder/select_binder.hpp"

#include "parser/expression/columnref_expression.hpp"
#include "parser/expression/window_expression.hpp"
#include "parser/parsed_expression_iterator.hpp"
#include "planner/expression/bound_columnref_expression.hpp"
#include "planner/expression/bound_window_expression.hpp"
#include "planner/expression_binder/aggregate_binder.hpp"
#include "planner/query_node/bound_select_node.hpp"

using namespace duckdb;
using namespace std;

SelectBinder::SelectBinder(Binder &binder, ClientContext &context, BoundSelectNode &node, BoundGroupInformation &info)
    : ExpressionBinder(binder, context), inside_window(false), node(node), info(info) {
}

BindResult SelectBinder::BindExpression(ParsedExpression &expr, index_t depth, bool root_expression) {
	// check if the expression binds to one of the groups
	auto group_index = TryBindGroup(expr, depth);
	if (group_index != INVALID_INDEX) {
		return BindGroup(expr, depth, group_index);
	}
	switch (expr.expression_class) {
	case ExpressionClass::DEFAULT:
		return BindResult("SELECT clause cannot contain DEFAULT clause");
	case ExpressionClass::WINDOW:
		return BindWindow((WindowExpression &)expr, depth);
	default:
		return ExpressionBinder::BindExpression(expr, depth);
	}
}

index_t SelectBinder::TryBindGroup(ParsedExpression &expr, index_t depth) {
	// first check the group alias map, if expr is a ColumnRefExpression
	if (expr.type == ExpressionType::COLUMN_REF) {
		auto &colref = (ColumnRefExpression &)expr;
		if (colref.table_name.empty()) {
			auto alias_entry = info.alias_map.find(colref.column_name);
			if (alias_entry != info.alias_map.end()) {
				// found entry!
				return alias_entry->second;
			}
		}
	}
	// no alias reference found
	// check the list of group columns for a match
	auto entry = info.map.find(&expr);
	if (entry != info.map.end()) {
		return entry->second;
	}
	return INVALID_INDEX;
}

BindResult SelectBinder::BindGroup(ParsedExpression &expr, index_t depth, index_t group_index) {
	auto &group = node.groups[group_index];

	return BindResult(make_unique<BoundColumnRefExpression>(expr.GetName(), group->return_type,
	                                                        ColumnBinding(node.group_index, group_index), depth),
	                  info.group_types[group_index]);
}
