#include "GoapExpression.hpp"
#include <cctype>
#include <cstdlib>
#include <cstring>

void GoapExpression::skipWhitespace()
{
	while (*m_pos == ' ' || *m_pos == '\t' || *m_pos == '\n' ||
	       *m_pos == '\r')
		m_pos++;
}

bool GoapExpression::match(const char *s)
{
	skipWhitespace();
	size_t len = strlen(s);
	if (strncmp(m_pos, s, len) == 0) {
		m_pos += len;
		return true;
	}
	return false;
}

GoapExpression::Node *GoapExpression::parsePrimary()
{
	skipWhitespace();

	// Parenthesized expression
	if (match("(")) {
		Node *node = parseExpression();
		if (!node)
			return nullptr;
		if (!match(")")) {
			setError("Expected ')'");
			delete node;
			return nullptr;
		}
		return node;
	}

	// Integer literal
	if (isdigit(*m_pos) || (*m_pos == '-' && isdigit(m_pos[1]))) {
		bool negative = false;
		if (*m_pos == '-') {
			negative = true;
			m_pos++;
		}
		int value = 0;
		while (isdigit(*m_pos)) {
			value = value * 10 + (*m_pos - '0');
			m_pos++;
		}
		Node *node = new Node(Node::Value);
		node->value = negative ? -value : value;
		return node;
	}

	// Variable name
	if (isalpha(*m_pos) || *m_pos == '_') {
		std::string name;
		while (isalnum(*m_pos) || *m_pos == '_') {
			name += *m_pos;
			m_pos++;
		}
		Node *node = new Node(Node::Variable);
		node->name = name;
		return node;
	}

	setError("Unexpected character in expression");
	return nullptr;
}

GoapExpression::Node *GoapExpression::parseComparison()
{
	Node *left = parsePrimary();
	if (!left)
		return nullptr;

	skipWhitespace();
	if (match("==")) {
		Node *right = parsePrimary();
		if (!right) {
			delete left;
			return nullptr;
		}
		Node *node = new Node(Node::Equal);
		node->left = left;
		node->right = right;
		return node;
	} else if (match("!=")) {
		Node *right = parsePrimary();
		if (!right) {
			delete left;
			return nullptr;
		}
		Node *node = new Node(Node::NotEqual);
		node->left = left;
		node->right = right;
		return node;
	} else if (match("<=")) {
		Node *right = parsePrimary();
		if (!right) {
			delete left;
			return nullptr;
		}
		Node *node = new Node(Node::LessEqual);
		node->left = left;
		node->right = right;
		return node;
	} else if (match(">=")) {
		Node *right = parsePrimary();
		if (!right) {
			delete left;
			return nullptr;
		}
		Node *node = new Node(Node::GreaterEqual);
		node->left = left;
		node->right = right;
		return node;
	} else if (match("<")) {
		Node *right = parsePrimary();
		if (!right) {
			delete left;
			return nullptr;
		}
		Node *node = new Node(Node::Less);
		node->left = left;
		node->right = right;
		return node;
	} else if (match(">")) {
		Node *right = parsePrimary();
		if (!right) {
			delete left;
			return nullptr;
		}
		Node *node = new Node(Node::Greater);
		node->left = left;
		node->right = right;
		return node;
	}

	return left;
}

GoapExpression::Node *GoapExpression::parseNot()
{
	skipWhitespace();
	if (match("!")) {
		Node *child = parseNot();
		if (!child)
			return nullptr;
		Node *node = new Node(Node::Not);
		node->left = child;
		return node;
	}
	return parseComparison();
}

GoapExpression::Node *GoapExpression::parseAnd()
{
	Node *left = parseNot();
	if (!left)
		return nullptr;

	while (true) {
		if (match("&&")) {
			Node *right = parseNot();
			if (!right) {
				delete left;
				return nullptr;
			}
			Node *node = new Node(Node::And);
			node->left = left;
			node->right = right;
			left = node;
		} else {
			break;
		}
	}
	return left;
}

GoapExpression::Node *GoapExpression::parseExpression()
{
	Node *left = parseAnd();
	if (!left)
		return nullptr;

	while (true) {
		if (match("||")) {
			Node *right = parseAnd();
			if (!right) {
				delete left;
				return nullptr;
			}
			Node *node = new Node(Node::Or);
			node->left = left;
			node->right = right;
			left = node;
		} else {
			break;
		}
	}
	return left;
}

bool GoapExpression::parse(const char *expr)
{
	clear();
	m_expr = expr;
	m_pos = expr;
	m_root = parseExpression();
	if (!m_root)
		return false;
	skipWhitespace();
	if (*m_pos != '\0') {
		setError("Unexpected trailing characters");
		clear();
		return false;
	}
	return true;
}

int GoapExpression::evalNode(Node *node, const GoapBlackboard &bb) const
{
	if (!node)
		return 0;

	switch (node->type) {
	case Node::Value:
		return node->value;
	case Node::Variable:
		return bb.getValue(node->name, 0);
	case Node::Equal:
		return evalNode(node->left, bb) == evalNode(node->right, bb) ? 1 : 0;
	case Node::NotEqual:
		return evalNode(node->left, bb) != evalNode(node->right, bb) ? 1 : 0;
	case Node::Less:
		return evalNode(node->left, bb) < evalNode(node->right, bb) ? 1 : 0;
	case Node::Greater:
		return evalNode(node->left, bb) > evalNode(node->right, bb) ? 1 : 0;
	case Node::LessEqual:
		return evalNode(node->left, bb) <= evalNode(node->right, bb) ? 1 : 0;
	case Node::GreaterEqual:
		return evalNode(node->left, bb) >= evalNode(node->right, bb) ? 1 : 0;
	case Node::And:
		return evalNode(node->left, bb) && evalNode(node->right, bb) ? 1 : 0;
	case Node::Or:
		return evalNode(node->left, bb) || evalNode(node->right, bb) ? 1 : 0;
	case Node::Not:
		return !evalNode(node->left, bb) ? 1 : 0;
	}
	return 0;
}

bool GoapExpression::evaluate(const GoapBlackboard &blackboard) const
{
	if (!m_root)
		return false;
	return evalNode(m_root, blackboard) != 0;
}

void GoapExpression::setError(const char *msg)
{
	m_error = msg;
	if (m_pos && m_expr) {
		m_error += " at position ";
		m_error += std::to_string(m_pos - m_expr);
		m_error += " near \"";
		m_error += std::string(m_pos, strnlen(m_pos, 20));
		m_error += "\"";
	}
}

void GoapExpression::clear()
{
	delete m_root;
	m_root = nullptr;
	m_expr = nullptr;
	m_pos = nullptr;
	m_error.clear();
}
