// SPDX-FileCopyrightText: 2012 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-2.0-or-later

#include "config/expression.hpp"
#include "config/configitem.hpp"
#include "config/configcompiler.hpp"
#include "config/vmops.hpp"
#include "base/array.hpp"
#include "base/json.hpp"
#include "base/object.hpp"
#include "base/logger.hpp"
#include "base/exception.hpp"
#include "base/scriptglobal.hpp"
#include "base/loader.hpp"
#include "base/reference.hpp"
#include "base/namespace.hpp"
#include "base/defer.hpp"
#include <boost/exception_ptr.hpp>
#include <boost/exception/errinfo_nested_exception.hpp>

using namespace icinga;

boost::signals2::signal<void (ScriptFrame&, ScriptError *ex, const DebugInfo&)> Expression::OnBreakpoint;
boost::thread_specific_ptr<bool> l_InBreakpointHandler;

Expression::~Expression()
{ }

void Expression::ScriptBreakpoint(ScriptFrame& frame, ScriptError *ex, const DebugInfo& di)
{
	bool *inHandler = l_InBreakpointHandler.get();
	if (!inHandler || !*inHandler) {
		inHandler = new bool(true);
		l_InBreakpointHandler.reset(inHandler);
		OnBreakpoint(frame, ex, di);
		*inHandler = false;
	}
}

ExpressionResult Expression::Evaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	try {
#ifdef I2_DEBUG
/*		std::ostringstream msgbuf;
		ShowCodeLocation(msgbuf, GetDebugInfo(), false);
		Log(LogDebug, "Expression")
			<< "Executing:\n" << msgbuf.str();*/
#endif /* I2_DEBUG */

		frame.IncreaseStackDepth();

		Defer decreaseStackDepth([&frame]{
			frame.DecreaseStackDepth();
		});

		ExpressionResult result = DoEvaluate(frame, dhint);
		return result;
	} catch (ScriptError& ex) {
		ScriptBreakpoint(frame, &ex, GetDebugInfo());
		throw;
	} catch (const std::exception& ex) {
		BOOST_THROW_EXCEPTION(ScriptError("Error while evaluating expression: " + String(ex.what()), GetDebugInfo())
			<< boost::errinfo_nested_exception(boost::current_exception()));
	}
}

bool Expression::GetReference(ScriptFrame&, [[maybe_unused]] bool init_dict, [[maybe_unused]] Value* parent, [[maybe_unused]] String* index, DebugHint**) const
{
	return false;
}

const DebugInfo& Expression::GetDebugInfo() const
{
	static DebugInfo debugInfo;
	return debugInfo;
}

std::unique_ptr<Expression> icinga::MakeIndexer(ScopeSpecifier scopeSpec, const String& index)
{
	std::unique_ptr<Expression> scope{new GetScopeExpression(scopeSpec)};
	return std::unique_ptr<Expression>(new IndexerExpression(std::move(scope), MakeLiteral(index)));
}

void DictExpression::MakeInline()
{
	m_Inline = true;
}

LiteralExpression::LiteralExpression(Value value)
	: m_Value(std::move(value))
{ }

ExpressionResult LiteralExpression::DoEvaluate(ScriptFrame&, DebugHint*) const
{
	return m_Value;
}

const DebugInfo& DebuggableExpression::GetDebugInfo() const
{
	return m_DebugInfo;
}

VariableExpression::VariableExpression(String variable, std::vector<Expression::Ptr> imports, const DebugInfo& debugInfo)
	: DebuggableExpression(debugInfo), m_Variable(std::move(variable)), m_Imports(std::move(imports))
{
	m_Imports.push_back(MakeIndexer(ScopeGlobal, "System").release());
	m_Imports.push_back(new IndexerExpression(MakeIndexer(ScopeGlobal, "System"), MakeLiteral("Configuration")));
	m_Imports.push_back(MakeIndexer(ScopeGlobal, "Types").release());
	m_Imports.push_back(MakeIndexer(ScopeGlobal, "Icinga").release());
}

ExpressionResult VariableExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	Value value;

	if (frame.Locals && frame.Locals->Get(m_Variable, &value))
		return value;
	else if (frame.Self.IsObject() && frame.Locals != frame.Self.Get<Object::Ptr>() && frame.Self.Get<Object::Ptr>()->GetOwnField(m_Variable, &value))
		return value;
	else if (VMOps::FindVarImport(frame, m_Imports, m_Variable, &value, m_DebugInfo))
		return value;
	else if (frame.GetGlobals()->Get(m_Variable, &value))
		return value;

	BOOST_THROW_EXCEPTION(ScriptError{"Tried to access undefined script variable '" + m_Variable + "'"});
}

bool VariableExpression::GetReference(ScriptFrame& frame, [[maybe_unused]] bool init_dict, Value *parent, String *index, DebugHint **dhint) const
{
	*index = m_Variable;

	if (frame.Locals && frame.Locals->Contains(m_Variable)) {
		*parent = frame.Locals;

		if (dhint)
			*dhint = nullptr;
	} else if (frame.Self.IsObject() && frame.Locals != frame.Self.Get<Object::Ptr>() && frame.Self.Get<Object::Ptr>()->HasOwnField(m_Variable)) {
		*parent = frame.Self;

		if (dhint && *dhint)
			*dhint = new DebugHint((*dhint)->GetChild(m_Variable));
	} else if (VMOps::FindVarImportRef(frame, m_Imports, m_Variable, parent)) {
		return true;
	} else if (frame.GetGlobals()->Contains(m_Variable)) {
		*parent = frame.GetGlobals();

		if (dhint)
			*dhint = nullptr;
	} else
		*parent = frame.Self;

	return true;
}

ExpressionResult RefExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	Value parent;
	String index;

	if (!m_Operand->GetReference(frame, false, &parent, &index, &dhint))
		BOOST_THROW_EXCEPTION(ScriptError("Cannot obtain reference for expression.", m_DebugInfo));

	if (!parent.IsObject())
		BOOST_THROW_EXCEPTION(ScriptError("Cannot obtain reference for expression because parent is not an object.", m_DebugInfo));

	return new Reference(parent, index);
}

ExpressionResult DerefExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand = m_Operand->Evaluate(frame);
	CHECK_RESULT(operand);

	Object::Ptr obj = operand.GetValue();
	Reference::Ptr ref = dynamic_pointer_cast<Reference>(obj);

	if (!ref)
		BOOST_THROW_EXCEPTION(ScriptError("Invalid reference specified.", GetDebugInfo()));

	return ref->Get();
}

bool DerefExpression::GetReference(ScriptFrame& frame, [[maybe_unused]] bool init_dict, Value *parent, String *index, DebugHint**) const
{
	ExpressionResult operand = m_Operand->Evaluate(frame);
	if (operand.GetCode() != ResultOK)
		return false;

	Reference::Ptr ref = operand.GetValue();

	if (!ref) {
		BOOST_THROW_EXCEPTION(ScriptError("Invalid reference specified.", GetDebugInfo()));
	}

	*parent = ref->GetParent();
	*index = ref->GetIndex();
	return true;
}

ExpressionResult NegateExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand = m_Operand->Evaluate(frame);
	CHECK_RESULT(operand);

	return ~(long)operand.GetValue();
}

ExpressionResult LogicalNegateExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand = m_Operand->Evaluate(frame);
	CHECK_RESULT(operand);

	return !operand.GetValue().ToBool();
}

ExpressionResult AddExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() + operand2.GetValue();
}

ExpressionResult SubtractExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() - operand2.GetValue();
}

ExpressionResult MultiplyExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() * operand2.GetValue();
}

ExpressionResult DivideExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() / operand2.GetValue();
}

ExpressionResult ModuloExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() % operand2.GetValue();
}

ExpressionResult XorExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() ^ operand2.GetValue();
}

ExpressionResult BinaryAndExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() & operand2.GetValue();
}

ExpressionResult BinaryOrExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() | operand2.GetValue();
}

ExpressionResult ShiftLeftExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() << operand2.GetValue();
}

ExpressionResult ShiftRightExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() >> operand2.GetValue();
}

ExpressionResult EqualExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() == operand2.GetValue();
}

ExpressionResult NotEqualExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() != operand2.GetValue();
}

ExpressionResult LessThanExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() < operand2.GetValue();
}

ExpressionResult GreaterThanExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() > operand2.GetValue();
}

ExpressionResult LessThanOrEqualExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() <= operand2.GetValue();
}

ExpressionResult GreaterThanOrEqualExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	return operand1.GetValue() >= operand2.GetValue();
}

ExpressionResult InExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	if (operand2.GetValue().IsEmpty())
		return false;
	else if (!operand2.GetValue().IsObjectType<Array>())
		BOOST_THROW_EXCEPTION(ScriptError("Invalid right side argument for 'in' operator: " + JsonEncode(operand2.GetValue()), m_DebugInfo));

	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1)

	Array::Ptr arr = operand2.GetValue();
	return arr->Contains(operand1.GetValue());
}

ExpressionResult NotInExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	CHECK_RESULT(operand2);

	if (operand2.GetValue().IsEmpty())
		return true;
	else if (!operand2.GetValue().IsObjectType<Array>())
		BOOST_THROW_EXCEPTION(ScriptError("Invalid right side argument for 'in' operator: " + JsonEncode(operand2.GetValue()), m_DebugInfo));

	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	Array::Ptr arr = operand2.GetValue();
	return !arr->Contains(operand1.GetValue());
}

ExpressionResult LogicalAndExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	if (!operand1.GetValue().ToBool())
		return operand1;
	else {
		ExpressionResult operand2 = m_Operand2->Evaluate(frame);
		CHECK_RESULT(operand2);

		return operand2.GetValue();
	}
}

ExpressionResult LogicalOrExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame);
	CHECK_RESULT(operand1);

	if (operand1.GetValue().ToBool())
		return operand1;
	else {
		ExpressionResult operand2 = m_Operand2->Evaluate(frame);
		CHECK_RESULT(operand2);

		return operand2.GetValue();
	}
}

ExpressionResult FunctionCallExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	Value self, vfunc;
	String index;

	if (m_FName->GetReference(frame, false, &self, &index))
		vfunc = VMOps::GetField(self, index, frame.Sandboxed, m_DebugInfo);
	else {
		ExpressionResult vfuncres = m_FName->Evaluate(frame);
		CHECK_RESULT(vfuncres);

		vfunc = vfuncres.GetValue();
	}

	if (vfunc.IsObjectType<Type>()) {
		std::vector<Value> arguments;
		arguments.reserve(m_Args.size());
		for (const auto& arg : m_Args) {
			ExpressionResult argres = arg->Evaluate(frame);
			CHECK_RESULT(argres);

			arguments.push_back(argres.GetValue());
		}

		return VMOps::ConstructorCall(vfunc, arguments);
	}

	if (!vfunc.IsObjectType<Function>())
		BOOST_THROW_EXCEPTION(ScriptError("Argument is not a callable object.", m_DebugInfo));

	Function::Ptr func = vfunc;

	if (!func->IsSideEffectFree() && frame.Sandboxed)
		BOOST_THROW_EXCEPTION(ScriptError("Function is not marked as safe for sandbox mode.", m_DebugInfo));

	std::vector<Value> arguments;
	arguments.reserve(m_Args.size());
	for (const auto& arg : m_Args) {
		ExpressionResult argres = arg->Evaluate(frame);
		CHECK_RESULT(argres);

		arguments.push_back(argres.GetValue());
	}

	return VMOps::FunctionCall(self, func, arguments);
}

ExpressionResult ArrayExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ArrayData result;
	result.reserve(m_Expressions.size());

	for (const auto& aexpr : m_Expressions) {
		ExpressionResult element = aexpr->Evaluate(frame);
		CHECK_RESULT(element);

		result.push_back(element.GetValue());
	}

	return new Array(std::move(result));
}

ExpressionResult DictExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	Value self;

	if (!m_Inline) {
		self = frame.Self;
		frame.Self = new Dictionary();
	}

	Value result;

	try {
		for (const auto& aexpr : m_Expressions) {
			ExpressionResult element = aexpr->Evaluate(frame, m_Inline ? dhint : nullptr);
			CHECK_RESULT(element);
			result = element.GetValue();
		}
	} catch (...) {
		if (!m_Inline)
			std::swap(self, frame.Self);
		throw;
	}

	if (m_Inline)
		return result;
	else {
		std::swap(self, frame.Self);
		return self;
	}
}

ExpressionResult GetScopeExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	if (m_ScopeSpec == ScopeLocal)
		return frame.Locals;
	else if (m_ScopeSpec == ScopeThis)
		return frame.Self;
	else if (m_ScopeSpec == ScopeGlobal)
		return frame.GetGlobals();
	else
		ABORT("Invalid scope.");
}

static inline
void WarnOnImplicitlySetGlobalVar(const std::unique_ptr<Expression>& setLhs, const Value& setLhsParent, CombinedSetOp setOp, const DebugInfo& debug)
{
	auto var (dynamic_cast<VariableExpression*>(setLhs.get()));

	if (var && setLhsParent.IsObject()) {
		auto ns (dynamic_pointer_cast<Namespace>(setLhsParent.Get<Object::Ptr>()));

		if (ns && ns == ScriptGlobal::GetGlobals() && debug.Path.GetLength()) {
			const char *opStr = nullptr;

			switch (setOp) {
				case OpSetLiteral:
					opStr = "=";
					break;
				case OpSetAdd:
					opStr = "+=";
					break;
				case OpSetSubtract:
					opStr = "-=";
					break;
				case OpSetMultiply:
					opStr = "*=";
					break;
				case OpSetDivide:
					opStr = "/=";
					break;
				case OpSetModulo:
					opStr = "%=";
					break;
				case OpSetXor:
					opStr = "^=";
					break;
				case OpSetBinaryAnd:
					opStr = "&=";
					break;
				case OpSetBinaryOr:
					opStr = "|=";
					break;
				default:
					VERIFY(!"Invalid opcode.");
			}

			auto varName (var->GetVariable());

			Log(LogWarning, "config")
				<< "Global variable '" << varName << "' has been set implicitly via '" << varName << ' ' << opStr << " ...' " << debug << "."
				" Please set it explicitly via 'globals." << varName << ' ' << opStr << " ...' instead.";
		}
	}
}

ExpressionResult SetExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	if (frame.Sandboxed)
		BOOST_THROW_EXCEPTION(ScriptError("Assignments are not allowed in sandbox mode.", m_DebugInfo));

	DebugHint *psdhint = dhint;

	Value parent;
	String index;

	if (!m_Operand1->GetReference(frame, true, &parent, &index, &psdhint))
		BOOST_THROW_EXCEPTION(ScriptError("Expression cannot be assigned to.", m_DebugInfo));

	ExpressionResult operand2 = m_Operand2->Evaluate(frame, dhint);
	CHECK_RESULT(operand2);

	if (m_Op != OpSetLiteral) {
		Value object = VMOps::GetField(parent, index, frame.Sandboxed, m_DebugInfo);

		switch (m_Op) {
			case OpSetAdd:
				operand2 = object + operand2;
				break;
			case OpSetSubtract:
				operand2 = object - operand2;
				break;
			case OpSetMultiply:
				operand2 = object * operand2;
				break;
			case OpSetDivide:
				operand2 = object / operand2;
				break;
			case OpSetModulo:
				operand2 = object % operand2;
				break;
			case OpSetXor:
				operand2 = object ^ operand2;
				break;
			case OpSetBinaryAnd:
				operand2 = object & operand2;
				break;
			case OpSetBinaryOr:
				operand2 = object | operand2;
				break;
			default:
				VERIFY(!"Invalid opcode.");
		}
	}

	VMOps::SetField(parent, index, operand2.GetValue(), m_DebugInfo);

	if (psdhint) {
		psdhint->AddMessage("=", m_DebugInfo);

		if (psdhint != dhint)
			delete psdhint;
	}

	WarnOnImplicitlySetGlobalVar(m_Operand1, parent, m_Op, m_DebugInfo);

	return Empty;
}

ExpressionResult SetConstExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	auto globals = ScriptGlobal::GetGlobals();

	ExpressionResult operandres = m_Operand->Evaluate(frame);
	CHECK_RESULT(operandres);
	Value operand = operandres.GetValue();

	globals->Set(m_Name, operand, true);

	return Empty;
}

ExpressionResult ConditionalExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	ExpressionResult condition = m_Condition->Evaluate(frame, dhint);
	CHECK_RESULT(condition);

	if (condition.GetValue().ToBool())
		return m_TrueBranch->Evaluate(frame, dhint);
	else if (m_FalseBranch)
		return m_FalseBranch->Evaluate(frame, dhint);

	return Empty;
}

ExpressionResult WhileExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	if (frame.Sandboxed)
		BOOST_THROW_EXCEPTION(ScriptError("While loops are not allowed in sandbox mode.", m_DebugInfo));

	for (;;) {
		ExpressionResult condition = m_Condition->Evaluate(frame, dhint);
		CHECK_RESULT(condition);

		if (!condition.GetValue().ToBool())
			break;

		ExpressionResult loop_body = m_LoopBody->Evaluate(frame, dhint);
		CHECK_RESULT_LOOP(loop_body);
	}

	return Empty;
}

ExpressionResult ReturnExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult operand = m_Operand->Evaluate(frame);
	CHECK_RESULT(operand);

	return ExpressionResult(operand.GetValue(), ResultReturn);
}

ExpressionResult BreakExpression::DoEvaluate(ScriptFrame&, DebugHint*) const
{
	return ExpressionResult(Empty, ResultBreak);
}

ExpressionResult ContinueExpression::DoEvaluate(ScriptFrame&, DebugHint*) const
{
	return ExpressionResult(Empty, ResultContinue);
}

ExpressionResult IndexerExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	ExpressionResult operand1 = m_Operand1->Evaluate(frame, dhint);
	CHECK_RESULT(operand1);

	ExpressionResult operand2 = m_Operand2->Evaluate(frame, dhint);
	CHECK_RESULT(operand2);

	return VMOps::GetField(operand1.GetValue(), operand2.GetValue(), frame.Sandboxed, m_DebugInfo);
}

bool IndexerExpression::GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint) const
{
	Value vparent;
	String vindex;
	DebugHint *psdhint = nullptr;
	bool free_psd = false;

	if (dhint)
		psdhint = *dhint;

	if (frame.Sandboxed)
		init_dict = false;

	if (m_Operand1->GetReference(frame, init_dict, &vparent, &vindex, &psdhint)) {
		if (init_dict) {
			Value old_value;
			bool has_field = true;

			if (vparent.IsObject()) {
				Object::Ptr oparent = vparent;
				has_field = oparent->HasOwnField(vindex);
			}

			if (has_field)
				old_value = VMOps::GetField(vparent, vindex, frame.Sandboxed, m_Operand1->GetDebugInfo());

			if (old_value.IsEmpty() && !old_value.IsString())
				VMOps::SetField(vparent, vindex, new Dictionary(), m_Operand1->GetDebugInfo());
		}

		*parent = VMOps::GetField(vparent, vindex, frame.Sandboxed, m_DebugInfo);
		free_psd = true;
	} else {
		ExpressionResult operand1 = m_Operand1->Evaluate(frame);
		*parent = operand1.GetValue();
	}

	ExpressionResult operand2 = m_Operand2->Evaluate(frame);
	*index = operand2.GetValue();

	if (dhint) {
		if (psdhint)
			*dhint = new DebugHint(psdhint->GetChild(*index));
		else
			*dhint = nullptr;
	}

	if (free_psd)
		delete psdhint;

	return true;
}

void icinga::BindToScope(std::unique_ptr<Expression>& expr, ScopeSpecifier scopeSpec)
{
	auto *dexpr = dynamic_cast<DictExpression *>(expr.get());

	if (dexpr) {
		for (auto& expr : dexpr->m_Expressions)
			BindToScope(expr, scopeSpec);

		return;
	}

	auto *aexpr = dynamic_cast<SetExpression *>(expr.get());

	if (aexpr) {
		BindToScope(aexpr->m_Operand1, scopeSpec);

		return;
	}

	auto *iexpr = dynamic_cast<IndexerExpression *>(expr.get());

	if (iexpr) {
		BindToScope(iexpr->m_Operand1, scopeSpec);
		return;
	}

	auto *lexpr = dynamic_cast<LiteralExpression *>(expr.get());

	if (lexpr && lexpr->GetValue().IsString()) {
		std::unique_ptr<Expression> scope{new GetScopeExpression(scopeSpec)};
		expr.reset(new IndexerExpression(std::move(scope), std::move(expr), lexpr->GetDebugInfo()));
	}

	auto *vexpr = dynamic_cast<VariableExpression *>(expr.get());

	if (vexpr) {
		std::unique_ptr<Expression> scope{new GetScopeExpression(scopeSpec)};
		expr.reset(new IndexerExpression(std::move(scope), MakeLiteral(vexpr->GetVariable()), vexpr->GetDebugInfo()));
	}
}

ExpressionResult ThrowExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ExpressionResult messageres = m_Message->Evaluate(frame);
	CHECK_RESULT(messageres);
	Value message = messageres.GetValue();
	BOOST_THROW_EXCEPTION(ScriptError(message, m_DebugInfo, m_IncompleteExpr));
}

ExpressionResult ImportExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	if (frame.Sandboxed)
		BOOST_THROW_EXCEPTION(ScriptError("Imports are not allowed in sandbox mode.", m_DebugInfo));

	String type = VMOps::GetField(frame.Self, "type", frame.Sandboxed, m_DebugInfo);
	ExpressionResult nameres = m_Name->Evaluate(frame);
	CHECK_RESULT(nameres);
	Value name = nameres.GetValue();

	if (!name.IsString())
		BOOST_THROW_EXCEPTION(ScriptError("Template/object name must be a string", m_DebugInfo));

	ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(Type::GetByName(type), name);

	if (!item)
		BOOST_THROW_EXCEPTION(ScriptError("Import references unknown template: '" + name + "'", m_DebugInfo));

	Dictionary::Ptr scope = item->GetScope();

	if (scope)
		scope->CopyTo(frame.Locals);

	ExpressionResult result = item->GetExpression()->Evaluate(frame, dhint);
	CHECK_RESULT(result);

	return Empty;
}

ExpressionResult ImportDefaultTemplatesExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	if (frame.Sandboxed)
		BOOST_THROW_EXCEPTION(ScriptError("Imports are not allowed in sandbox mode.", m_DebugInfo));

	String type = VMOps::GetField(frame.Self, "type", frame.Sandboxed, m_DebugInfo);
	Type::Ptr ptype = Type::GetByName(type);

	for (const ConfigItem::Ptr& item : ConfigItem::GetDefaultTemplates(ptype)) {
		Dictionary::Ptr scope = item->GetScope();

		if (scope)
			scope->CopyTo(frame.Locals);

		ExpressionResult result = item->GetExpression()->Evaluate(frame, dhint);
		CHECK_RESULT(result);
	}

	return Empty;
}

ExpressionResult FunctionExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	return VMOps::NewFunction(frame, m_Name, m_Args, m_ClosedVars, m_Expression);
}

ExpressionResult ApplyExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	if (frame.Sandboxed)
		BOOST_THROW_EXCEPTION(ScriptError("Apply rules are not allowed in sandbox mode.", m_DebugInfo));

	ExpressionResult nameres = m_Name->Evaluate(frame);
	CHECK_RESULT(nameres);

	return VMOps::NewApply(frame, m_Type, m_Target, nameres.GetValue(), m_Filter,
		m_Package, m_FKVar, m_FVVar, m_FTerm, m_ClosedVars, m_IgnoreOnError, m_Expression, m_DebugInfo);
}

ExpressionResult NamespaceExpression::DoEvaluate(ScriptFrame&, DebugHint*) const
{
	DebugInfo adjustedDI = m_DebugInfo;
	adjustedDI.LastLine = adjustedDI.FirstLine;
	adjustedDI.LastColumn = 0;

	std::ostringstream oss;
	ShowCodeLocation(oss, adjustedDI, false);

	Log(LogWarning, "config")
		<< "User-defined namespaces are deprecated and will be removed in v2.18:\n"
		<< oss.str();

	Namespace::Ptr ns = new Namespace(true);

	ScriptFrame innerFrame(true, ns);
	ExpressionResult result = m_Expression->Evaluate(innerFrame);
	CHECK_RESULT(result);

	return ns;
}

ExpressionResult ObjectExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	if (frame.Sandboxed)
		BOOST_THROW_EXCEPTION(ScriptError("Object definitions are not allowed in sandbox mode.", m_DebugInfo));

	ExpressionResult typeres = m_Type->Evaluate(frame, dhint);
	CHECK_RESULT(typeres);
	Type::Ptr type = typeres.GetValue();

	String name;

	if (m_Name) {
		ExpressionResult nameres = m_Name->Evaluate(frame, dhint);
		CHECK_RESULT(nameres);

		name = nameres.GetValue();
	}

	return VMOps::NewObject(frame, m_Abstract, type, name, m_Filter, m_Zone,
		m_Package, m_DefaultTmpl, m_IgnoreOnError, m_ClosedVars, m_Expression, m_DebugInfo);
}

ExpressionResult ForExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	if (frame.Sandboxed)
		BOOST_THROW_EXCEPTION(ScriptError("For loops are not allowed in sandbox mode.", m_DebugInfo));

	ExpressionResult valueres = m_Value->Evaluate(frame, dhint);
	CHECK_RESULT(valueres);

	return VMOps::For(frame, m_FKVar, m_FVVar, valueres.GetValue(), m_Expression, m_DebugInfo);
}

ExpressionResult LibraryExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	if (frame.Sandboxed)
		BOOST_THROW_EXCEPTION(ScriptError("Loading libraries is not allowed in sandbox mode.", m_DebugInfo));

	ExpressionResult libres = m_Operand->Evaluate(frame, dhint);
	CHECK_RESULT(libres);

	Log(LogNotice, "config")
		<< "Ignoring explicit load request for library \"" << libres << "\".";

	return Empty;
}

ExpressionResult IncludeExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	if (frame.Sandboxed)
		BOOST_THROW_EXCEPTION(ScriptError("Includes are not allowed in sandbox mode.", m_DebugInfo));

	std::unique_ptr<Expression> expr;
	String name, path, pattern;

	switch (m_Type) {
		case IncludeRegular:
			{
				ExpressionResult pathres = m_Path->Evaluate(frame, dhint);
				CHECK_RESULT(pathres);
				path = pathres.GetValue();
			}

			expr = ConfigCompiler::HandleInclude(m_RelativeBase, path, m_SearchIncludes, m_Zone, m_Package, m_DebugInfo);
			break;

		case IncludeRecursive:
			{
				ExpressionResult pathres = m_Path->Evaluate(frame, dhint);
				CHECK_RESULT(pathres);
				path = pathres.GetValue();
			}

			{
				ExpressionResult patternres = m_Pattern->Evaluate(frame, dhint);
				CHECK_RESULT(patternres);
				pattern = patternres.GetValue();
			}

			expr = ConfigCompiler::HandleIncludeRecursive(m_RelativeBase, path, pattern, m_Zone, m_Package, m_DebugInfo);
			break;

		case IncludeZones:
			{
				ExpressionResult nameres = m_Name->Evaluate(frame, dhint);
				CHECK_RESULT(nameres);
				name = nameres.GetValue();
			}

			{
				ExpressionResult pathres = m_Path->Evaluate(frame, dhint);
				CHECK_RESULT(pathres);
				path = pathres.GetValue();
			}

			{
				ExpressionResult patternres = m_Pattern->Evaluate(frame, dhint);
				CHECK_RESULT(patternres);
				pattern = patternres.GetValue();
			}

			expr = ConfigCompiler::HandleIncludeZones(m_RelativeBase, name, path, pattern, m_Package, m_DebugInfo);
			break;
	}

	ExpressionResult res(Empty);

	try {
		res = expr->Evaluate(frame, dhint);
	} catch (const std::exception&) {
		throw;
	}

	return res;
}

ExpressionResult BreakpointExpression::DoEvaluate(ScriptFrame& frame, DebugHint*) const
{
	ScriptBreakpoint(frame, nullptr, GetDebugInfo());

	return Empty;
}

ExpressionResult TryExceptExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const
{
	try {
		ExpressionResult tryResult = m_TryBody->Evaluate(frame, dhint);
		CHECK_RESULT(tryResult);
	} catch (const std::exception&) {
		ExpressionResult exceptResult = m_ExceptBody->Evaluate(frame, dhint);
		CHECK_RESULT(exceptResult);
	}

	return Empty;
}
