#pragma once

#include "lexer.h"
#include "exception.h"
#include "ml_date.h"
#include "settings.h"

#include <string>
#include <vector>

static const int FUNCTION_CALL = -1;

namespace usql {

    enum class ColumnType {
	integer_type,
	float_type,
	varchar_type,
	date_type,
	bool_type
    };

    enum class NodeType {
	true_node,
	null_value,
	int_value,
	float_value,
	string_value,
	bool_value,
	logical_operator,
	relational_operator,
	arithmetical_operator,
	create_table,
	create_table_as_select,
	insert_into,
	select_from,
	delete_from,
	update_table,
	load_table,
	save_table,
	drop_table,
	set,
	show,
	database_value,
	offset_limit,
	column_order,
	column_value,
	function,
	column_def,
	error
    };

    struct Node {
	NodeType node_type;

	explicit Node(const NodeType type) : node_type(type) {}
    };


    struct ColOrderNode : Node {
	std::string col_name;
	int col_index;
	bool ascending;

	ColOrderNode(const std::string& name, bool asc) : Node(NodeType::column_order), col_name(name), col_index(-1), ascending(asc) {}
	ColOrderNode(int index, bool asc) : Node(NodeType::database_value), col_name(""), col_index(index), ascending(asc) {}
    };


   
   struct OffsetLimitNode : Node {
	int	offset;
	int	limit;

	OffsetLimitNode(int off, int lim) : Node(NodeType::offset_limit), offset(off), limit(lim) {}
   };


    struct SelectColNode : Node {
        std::unique_ptr<Node>	value;
        std::string name;

        SelectColNode(std::unique_ptr<Node> column, const std::string &alias) :
        Node(NodeType::database_value), value(std::move(column)), name(alias) {}
    };

    struct ColDefNode : Node {
	std::string name;
	ColumnType type;
	int order;
	int length;
	bool null;

	ColDefNode(const std::string& col_name,  ColumnType col_type, int col_order, int col_len, bool nullable) :
		    Node(NodeType::column_def), name(col_name), type(col_type), order(col_order), length(col_len),
		    null(nullable) {}
    };

    struct FunctionNode : Node {
        std::string				function;	// TODO use enum
        std::vector<std::unique_ptr<Node>>	params;

        FunctionNode(const std::string& func_name, std::vector<std::unique_ptr<Node>> pars) :
        Node(NodeType::function), function(func_name), params(std::move(pars)) {}
    };

    struct TrueNode : Node {
	TrueNode() : Node(NodeType::true_node) {}
    };

    struct ValueNode : Node {
	explicit ValueNode(NodeType type) : Node(type) {}

	virtual bool isNull() { return false; }
	virtual long getIntegerValue() = 0;
	virtual double getDoubleValue() = 0;
	virtual std::string getStringValue() = 0;
	virtual long getDateValue() = 0;
	virtual bool getBooleanValue() = 0;

	virtual ~ValueNode() = default;
    };

    struct NullValueNode : ValueNode {

        NullValueNode() : ValueNode(NodeType::null_value) {}

        bool isNull() override { return true; }

        long getIntegerValue() override { throw Exception("getIntegerValue not supported on NullValueNode"); };
        double getDoubleValue() override { throw Exception("getDoubleValue not supported on NullValueNode"); };
        std::string getStringValue() override { throw Exception("getStringValue not supported on NullValueNode"); };
	long getDateValue() override { throw Exception("getDateValue not supported on NullValueNode"); };
	bool getBooleanValue() override { throw Exception("getBooleanValue not supported on NullValueNode"); };
    };

    struct IntValueNode : ValueNode {
	long value;

	explicit IntValueNode(long value) : ValueNode(NodeType::int_value), value(value) {}

	long getIntegerValue() override { return value; };
	double getDoubleValue() override { return (double) value; };
	std::string getStringValue() override { return std::to_string(value); }
	long getDateValue() override { return value; };
	bool getBooleanValue() override { return value != 0; };
    };

    struct DoubleValueNode : ValueNode {
	double value;

	explicit DoubleValueNode(double value) : ValueNode(NodeType::float_value), value(value) {}

	long getIntegerValue() override { return (long) value; };
	double getDoubleValue() override { return value; };
	std::string getStringValue() override { return std::to_string(value); }
	long getDateValue() override { return (long) value; };
	bool getBooleanValue() override { return value != 0.0; };
    };

    struct StringValueNode : ValueNode {
	std::string value;

	explicit StringValueNode(const std::string &value) : ValueNode(NodeType::string_value), value(value) {}

	long getIntegerValue() override { return std::stoi(value); };
	double getDoubleValue() override { return std::stod(value); };
	std::string getStringValue() override { return value; };
	long getDateValue() override { return Settings::string_to_date(value); };
	bool getBooleanValue() override { return value == "true"; };
    };

    struct BooleanValueNode : ValueNode {
        bool value;

        explicit BooleanValueNode(bool value) : ValueNode(NodeType::bool_value), value(value) {}

        long getIntegerValue() override { return (long) value; };
        double getDoubleValue() override { return (double) value; };
        std::string getStringValue() override { return value ? "true" : "false"; }
        long getDateValue() override { return (long) value; };
        bool getBooleanValue() override { return value; };
    };


    struct DatabaseValueNode : Node {
	std::string col_name;

	explicit DatabaseValueNode(const std::string &name) : Node(NodeType::database_value), col_name(name) {}
    };

    enum class LogicalOperatorType {
	and_operator,
	or_operator,
	not_operator
    };

    struct LogicalOperatorNode : Node {
	LogicalOperatorType op;
	std::unique_ptr<Node> left;
	std::unique_ptr<Node> right;

	LogicalOperatorNode(LogicalOperatorType op, std::unique_ptr<Node> left, std::unique_ptr<Node> right) :
		    Node(NodeType::logical_operator), op(op), left(std::move(left)), right(std::move(right)) {};
    };

    enum class RelationalOperatorType {
	equal,
	greater,
	greater_equal,
	lesser,
	lesser_equal,
	not_equal
	// like
    };

    struct RelationalOperatorNode : Node {
	RelationalOperatorType op;

	std::unique_ptr<Node> left;
	std::unique_ptr<Node> right;

	RelationalOperatorNode(RelationalOperatorType op, std::unique_ptr<Node> left, std::unique_ptr<Node> right) :
		    Node(NodeType::relational_operator), op(op), left(std::move(left)), right(std::move(right)) {};
    };

    enum class ArithmeticalOperatorType {
	copy_value,     // just copy lef value and do nothing with it
	plus_operator,
	minus_operator,
	multiply_operator,
	divide_operator
    };

    struct ArithmeticalOperatorNode : Node {
	ArithmeticalOperatorType op;

	std::unique_ptr<Node> left;
	std::unique_ptr<Node> right;

	ArithmeticalOperatorNode(ArithmeticalOperatorType op, std::unique_ptr<Node> left, std::unique_ptr<Node> right) :
		    Node(NodeType::arithmetical_operator), op(op), left(std::move(left)), right(std::move(right)) {};
    };

    struct CreateTableNode : Node {
	std::string table_name;
	std::vector<ColDefNode> cols_defs;

	CreateTableNode(const std::string& name, std::vector<ColDefNode> defs) :
		    Node(NodeType::create_table), table_name(name), cols_defs(std::move(defs)) {}
    };

    struct InsertIntoTableNode : Node {
	std::string				table_name;
	std::vector<DatabaseValueNode>		cols_names;
	std::vector<std::unique_ptr<Node>>	cols_values;

	InsertIntoTableNode(const std::string& name, std::vector<DatabaseValueNode> names, std::vector<std::unique_ptr<Node>> values) :
		    Node(NodeType::insert_into), table_name(name), cols_names(std::move(names)), cols_values(std::move(values)) {}
    };

    struct SelectFromTableNode : Node {
	std::string table_name;
	std::unique_ptr<std::vector<SelectColNode>> cols_names;
	std::unique_ptr<Node> where;
	std::vector<ColOrderNode> order_by;
	OffsetLimitNode	offset_limit;
	bool distinct;

	SelectFromTableNode(std::string name, std::unique_ptr<std::vector<SelectColNode>> names, std::unique_ptr<Node> where_clause, std::vector<ColOrderNode> orderby, OffsetLimitNode offlim, bool distinct_):
	Node(NodeType::select_from), table_name(std::move(name)), cols_names(std::move(names)), where(std::move(where_clause)), order_by(std::move(orderby)), offset_limit(offlim), distinct(distinct_) {}
    };

    struct CreateTableAsSelectNode : Node {
        std::string		table_name;
        std::unique_ptr<Node>	select_table;

        CreateTableAsSelectNode(const std::string& name, std::unique_ptr<Node> table) :
        Node(NodeType::create_table_as_select), table_name(name), select_table(std::move(table)) {}
    };

    struct UpdateTableNode : Node {
	std::string table_name;
	std::vector<DatabaseValueNode> cols_names;
	std::vector<std::unique_ptr<Node>> values;
	std::unique_ptr<Node> where;

	UpdateTableNode(const std::string &name, std::vector<DatabaseValueNode> names, std::vector<std::unique_ptr<Node>> vals,
			std::unique_ptr<Node> where_clause) :
		    Node(NodeType::update_table), table_name(name), cols_names(names), values(std::move(vals)),
		    where(std::move(where_clause)) {}
    };

    struct LoadIntoTableNode : Node {
	std::string table_name;
	std::string filename;

	LoadIntoTableNode(const std::string& name, const std::string &file) :
		    Node(NodeType::load_table), table_name(name), filename(file) {}
    };

    struct SaveTableNode : Node {
        std::string table_name;
        std::string filename;

        SaveTableNode(const std::string& name, const std::string &file) :
        Node(NodeType::save_table), table_name(name), filename(file) {}
    };

    struct DropTableNode : Node {
        std::string table_name;

        explicit DropTableNode(const std::string& name) : Node(NodeType::drop_table), table_name(name) {}
    };

    struct DeleteFromTableNode : Node {
	std::string table_name;
	std::unique_ptr<Node> where;

	DeleteFromTableNode(const std::string& name, std::unique_ptr<Node> where_clause) :
		    Node(NodeType::delete_from), table_name(name), where(std::move(where_clause)) {}
    };

    struct SetNode : Node {
	std::string name;
	std::string value;

	SetNode(const std::string& name_, const std::string& value_) :
		    Node(NodeType::set), name(name_), value(value_) {}
    };

    struct ShowNode : Node {
	std::string name;

	explicit ShowNode(const std::string& name_) : Node(NodeType::show), name(name_) {}
    };



    class Parser {
    private:

    public:
	Parser();

	std::unique_ptr<Node> parse(const std::string &code);

    private:
	std::unique_ptr<Node> parse_create_table();
	std::unique_ptr<Node> parse_drop_table();
	std::unique_ptr<Node> parse_load_table();
	std::unique_ptr<Node> parse_save_table();
	std::unique_ptr<Node> parse_set();
	std::unique_ptr<Node> parse_show();

	std::unique_ptr<Node> parse_insert_into_table();
	std::unique_ptr<Node> parse_select_from_table();
	std::unique_ptr<Node> parse_delete_from_table();
	std::unique_ptr<Node> parse_update_table();

	std::vector<ColOrderNode>  parse_order_by_clause();
	OffsetLimitNode parse_offset_limit_clause();

	std::unique_ptr<Node> parse_where_clause();
	std::unique_ptr<Node> parse_expression();
	std::unique_ptr<Node> parse_expression(std::unique_ptr<Node> left);

	std::unique_ptr<Node> parse_value();
	RelationalOperatorType parse_relational_operator();
	LogicalOperatorType parse_logical_operator();
	ArithmeticalOperatorType parse_arithmetical_operator();

    private:
	Lexer m_lexer;
    };

}	// namespace
