#include "base/exception.hpp"
#include "base/objectlock.hpp"
#include "base/utility.hpp"
#include "base/convert.hpp"
#include "base/debug.hpp"
#include "base/dependencygraph.hpp"
#include "base/logger.hpp"
#include "base/function.hpp"
#include "base/configobject.hpp"
#include "base/configtype.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-variable"
namespace icinga
{

TypeImpl<Command>::TypeImpl()
{ }

TypeImpl<Command>::~TypeImpl()
{ }

String TypeImpl<Command>::GetName() const
{
	return "Command";
}

int TypeImpl<Command>::GetAttributes() const
{
	return 1;
}

Type::Ptr TypeImpl<Command>::GetBaseType() const
{
	return CustomVarObject::TypeInstance;
}

int TypeImpl<Command>::GetFieldId(const String& name) const
{
	int offset = CustomVarObject::TypeInstance->GetFieldCount();

	switch (static_cast<int>(Utility::SDBM(name, 1))) {
		case 97:
			if (name == "arguments")
				return offset + 1;

			break;
		case 99:
			if (name == "command")
				return offset + 0;

			break;
		case 101:
			if (name == "env")
				return offset + 2;
			if (name == "execute")
				return offset + 3;

			break;
		case 116:
			if (name == "timeout")
				return offset + 4;

			break;
	}

	return CustomVarObject::TypeInstance->GetFieldId(name);
}

Field TypeImpl<Command>::GetFieldInfo(int id) const
{
	int real_id = id - CustomVarObject::TypeInstance->GetFieldCount();
	if (real_id < 0) { return CustomVarObject::TypeInstance->GetFieldInfo(id); }
	switch (real_id) {
		case 0:
			return {0, "Value", "command", "command", nullptr, 2, 0};
		case 1:
			return {1, "Value", "arguments", "arguments", nullptr, 65538, 0};
		case 2:
			return {2, "Dictionary", "env", "env", nullptr, 65538, 0};
		case 3:
			return {3, "Function", "execute", "execute", nullptr, 258, 0};
		case 4:
			return {4, "Number", "timeout", "timeout", nullptr, 2, 0};
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

int TypeImpl<Command>::GetFieldCount() const
{
	return 5 + CustomVarObject::TypeInstance->GetFieldCount();
}

ObjectFactory TypeImpl<Command>::GetFactory() const
{
	return TypeHelper<Command, false>::GetFactory();
}

int TypeImpl<Command>::GetActivationPriority() const
{
	return 0;
}

void TypeImpl<Command>::RegisterAttributeHandler(int fieldId, const Type::AttributeHandler& callback)
{
	int real_id = fieldId - CustomVarObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { CustomVarObject::TypeInstance->RegisterAttributeHandler(fieldId, callback); return; }
	switch (real_id) {
		case 0:
			ObjectImpl<Command>::OnCommandLineChanged.connect(callback);
			break;
		case 1:
			ObjectImpl<Command>::OnArgumentsChanged.connect(callback);
			break;
		case 2:
			ObjectImpl<Command>::OnEnvChanged.connect(callback);
			break;
		case 3:
			ObjectImpl<Command>::OnExecuteChanged.connect(callback);
			break;
		case 4:
			ObjectImpl<Command>::OnTimeoutChanged.connect(callback);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

void ObjectImpl<Command>::Validate(int types, const ValidationUtils& utils)
{
	CustomVarObject::Validate(types, utils);

	if (2 & types)
		ValidateCommandLine(Lazy<Value>([this]() { return GetCommandLine(); }), utils);
	if (2 & types)
		ValidateArguments(Lazy<Value>([this]() { return GetArguments(); }), utils);
	if (2 & types)
		ValidateEnv(Lazy<Dictionary::Ptr>([this]() { return GetEnv(); }), utils);
	if (2 & types)
		ValidateExecute(Lazy<Function::Ptr>([this]() { return GetExecute(); }), utils);
	if (2 & types)
		ValidateTimeout(Lazy<int>([this]() { return GetTimeout(); }), utils);
}

void ObjectImpl<Command>::SimpleValidateCommandLine(const Lazy<Value>& value, const ValidationUtils& utils)
{
	if (value().IsObjectType<Function>()) {
		Function::Ptr func = value();
		if (func->IsDeprecated())
			Log(LogWarning, "Command") << "Attribute 'command' for object '" << dynamic_cast<ConfigObject *>(this)->GetName() << "' of type '" << dynamic_cast<ConfigObject *>(this)->GetReflectionType()->GetName() << "' is set to a deprecated function: " << func->GetName();
	}

}

void ObjectImpl<Command>::SimpleValidateArguments(const Lazy<Value>& value, const ValidationUtils& utils)
{
	if (value().IsObjectType<Function>()) {
		Function::Ptr func = value();
		if (func->IsDeprecated())
			Log(LogWarning, "Command") << "Attribute 'arguments' for object '" << dynamic_cast<ConfigObject *>(this)->GetName() << "' of type '" << dynamic_cast<ConfigObject *>(this)->GetReflectionType()->GetName() << "' is set to a deprecated function: " << func->GetName();
	}

}

void ObjectImpl<Command>::SimpleValidateEnv(const Lazy<Dictionary::Ptr>& value, const ValidationUtils& utils)
{
}

void ObjectImpl<Command>::SimpleValidateExecute(const Lazy<Function::Ptr>& value, const ValidationUtils& utils)
{
	if (!value())
		BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { "execute" }, "Attribute must not be empty."));

}

void ObjectImpl<Command>::SimpleValidateTimeout(const Lazy<int>& value, const ValidationUtils& utils)
{
}

ObjectImpl<Command>::ObjectImpl()
{
	SetCommandLine(GetDefaultCommandLine(), true);
	SetArguments(GetDefaultArguments(), true);
	SetEnv(GetDefaultEnv(), true);
	SetExecute(GetDefaultExecute(), true);
	SetTimeout(GetDefaultTimeout(), true);
}

ObjectImpl<Command>::~ObjectImpl()
{ }

void ObjectImpl<Command>::SetField(int id, const Value& value, bool suppress_events, const Value& cookie)
{
	int real_id = id - CustomVarObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { CustomVarObject::SetField(id, value, suppress_events, cookie); return; }
	switch (real_id) {
		case 0:
			SetCommandLine(value, suppress_events, cookie);
			break;
		case 1:
			SetArguments(value, suppress_events, cookie);
			break;
		case 2:
			SetEnv(value, suppress_events, cookie);
			break;
		case 3:
			SetExecute(value, suppress_events, cookie);
			break;
		case 4:
			SetTimeout(value, suppress_events, cookie);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

Value ObjectImpl<Command>::GetField(int id) const
{
	int real_id = id - CustomVarObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { return CustomVarObject::GetField(id); }
	switch (real_id) {
		case 0:
			return GetCommandLine();
		case 1:
			return GetArguments();
		case 2:
			return GetEnv();
		case 3:
			return GetExecute();
		case 4:
			return GetTimeout();
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

void ObjectImpl<Command>::ValidateField(int id, const Lazy<Value>& lvalue, const ValidationUtils& utils)
{
	int real_id = id - CustomVarObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { CustomVarObject::ValidateField(id, lvalue, utils); return; }
	switch (real_id) {
		case 0:
			ValidateCommandLine(lvalue, utils);
			break;
		case 1:
			ValidateArguments(lvalue, utils);
			break;
		case 2:
			ValidateEnv(lvalue, utils);
			break;
		case 3:
			ValidateExecute(lvalue, utils);
			break;
		case 4:
			ValidateTimeout(lvalue, utils);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

void ObjectImpl<Command>::NotifyField(int id, const Value& cookie)
{
	int real_id = id - CustomVarObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { CustomVarObject::NotifyField(id, cookie); return; }
	switch (real_id) {
		case 0:
			NotifyCommandLine(cookie);
			break;
		case 1:
			NotifyArguments(cookie);
			break;
		case 2:
			NotifyEnv(cookie);
			break;
		case 3:
			NotifyExecute(cookie);
			break;
		case 4:
			NotifyTimeout(cookie);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

Object::Ptr ObjectImpl<Command>::NavigateField(int id) const
{
	int real_id = id - CustomVarObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { return CustomVarObject::NavigateField(id); }
	throw std::runtime_error("Invalid field ID.");
}

Value ObjectImpl<Command>::GetCommandLine() const
{
	return m_CommandLine.load();
}

Value ObjectImpl<Command>::GetArguments() const
{
	return m_Arguments.load();
}

Dictionary::Ptr ObjectImpl<Command>::GetEnv() const
{
	return m_Env.load();
}

Function::Ptr ObjectImpl<Command>::GetExecute() const
{
	return m_Execute.load();
}

int ObjectImpl<Command>::GetTimeout() const
{
	return m_Timeout.load();
}

void ObjectImpl<Command>::SetCommandLine(const Value& value, bool suppress_events, const Value& cookie)
{
	m_CommandLine.store(value);
	if (!suppress_events) {
		NotifyCommandLine(cookie);
	}

}

void ObjectImpl<Command>::SetArguments(const Value& value, bool suppress_events, const Value& cookie)
{
	Value oldValue = GetArguments();
	auto *dobj = dynamic_cast<ConfigObject *>(this);
	m_Arguments.store(value);
	if (!suppress_events) {
		NotifyArguments(cookie);
		if (!dobj || dobj->IsActive())
			OnArgumentsChangedWithOldValue(static_cast<Command *>(this), oldValue, value);
	}

}

void ObjectImpl<Command>::SetEnv(const Dictionary::Ptr& value, bool suppress_events, const Value& cookie)
{
	Value oldValue = GetEnv();
	auto *dobj = dynamic_cast<ConfigObject *>(this);
	m_Env.store(value);
	if (!suppress_events) {
		NotifyEnv(cookie);
		if (!dobj || dobj->IsActive())
			OnEnvChangedWithOldValue(static_cast<Command *>(this), oldValue, value);
	}

}

void ObjectImpl<Command>::SetExecute(const Function::Ptr& value, bool suppress_events, const Value& cookie)
{
	m_Execute.store(value);
	if (!suppress_events) {
		NotifyExecute(cookie);
	}

}

void ObjectImpl<Command>::SetTimeout(int value, bool suppress_events, const Value& cookie)
{
	m_Timeout.store(value);
	if (!suppress_events) {
		NotifyTimeout(cookie);
	}

}

void ObjectImpl<Command>::NotifyCommandLine(const Value& cookie)
{
	auto *dobj = dynamic_cast<ConfigObject *>(this);
	if (!dobj || dobj->IsActive())
		OnCommandLineChanged(static_cast<Command *>(this), cookie);
}

void ObjectImpl<Command>::NotifyArguments(const Value& cookie)
{
	auto *dobj = dynamic_cast<ConfigObject *>(this);
	if (!dobj || dobj->IsActive())
		OnArgumentsChanged(static_cast<Command *>(this), cookie);
}

void ObjectImpl<Command>::NotifyEnv(const Value& cookie)
{
	auto *dobj = dynamic_cast<ConfigObject *>(this);
	if (!dobj || dobj->IsActive())
		OnEnvChanged(static_cast<Command *>(this), cookie);
}

void ObjectImpl<Command>::NotifyExecute(const Value& cookie)
{
	auto *dobj = dynamic_cast<ConfigObject *>(this);
	if (!dobj || dobj->IsActive())
		OnExecuteChanged(static_cast<Command *>(this), cookie);
}

void ObjectImpl<Command>::NotifyTimeout(const Value& cookie)
{
	auto *dobj = dynamic_cast<ConfigObject *>(this);
	if (!dobj || dobj->IsActive())
		OnTimeoutChanged(static_cast<Command *>(this), cookie);
}

Value ObjectImpl<Command>::GetDefaultCommandLine() const
{
	return Value();
}

Value ObjectImpl<Command>::GetDefaultArguments() const
{
	return Value();
}

Dictionary::Ptr ObjectImpl<Command>::GetDefaultEnv() const
{
	return Dictionary::Ptr();
}

Function::Ptr ObjectImpl<Command>::GetDefaultExecute() const
{
	return Function::Ptr();
}

int ObjectImpl<Command>::GetDefaultTimeout() const
{
	 return 60; 
}


boost::signals2::signal<void (const intrusive_ptr<Command>&, const Value&)> ObjectImpl<Command>::OnCommandLineChanged;


boost::signals2::signal<void (const intrusive_ptr<Command>&, const Value&)> ObjectImpl<Command>::OnArgumentsChanged;


boost::signals2::signal<void (const intrusive_ptr<Command>&, const Value&, const Value&)> ObjectImpl<Command>::OnArgumentsChangedWithOldValue;


boost::signals2::signal<void (const intrusive_ptr<Command>&, const Value&)> ObjectImpl<Command>::OnEnvChanged;


boost::signals2::signal<void (const intrusive_ptr<Command>&, const Value&, const Value&)> ObjectImpl<Command>::OnEnvChangedWithOldValue;


boost::signals2::signal<void (const intrusive_ptr<Command>&, const Value&)> ObjectImpl<Command>::OnExecuteChanged;


boost::signals2::signal<void (const intrusive_ptr<Command>&, const Value&)> ObjectImpl<Command>::OnTimeoutChanged;

static void TIValidateCommand_3(const intrusive_ptr<ObjectImpl<Command> >& object, const String& key, const Value& value, std::vector<String>& location, const ValidationUtils& utils)
{
	bool known_attribute = false;
	do {
		known_attribute = true;
		if (value.IsEmpty() || value.IsScalar())
			return;
	} while (0);

	do {
		known_attribute = true;
		if (value.IsObjectType<Function>()) {
			return;
		}
	} while (0);

	if (!known_attribute)
		BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Invalid attribute: " + key));
	else
		BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Invalid type."));
}

static void TIValidateCommand_4_3(const intrusive_ptr<ObjectImpl<Command> >& object, const String& key, const Value& value, std::vector<String>& location, const ValidationUtils& utils)
{
	bool known_attribute = false;
	do {
		if (key != "key")
			break;
		known_attribute = true;
		if (value.IsEmpty() || value.IsScalar())
			return;
	} while (0);

	do {
		if (key != "value")
			break;
		known_attribute = true;
		if (value.IsEmpty() || value.IsScalar())
			return;
	} while (0);

	do {
		if (key != "value")
			break;
		known_attribute = true;
		if (value.IsObjectType<Function>()) {
			return;
		}
	} while (0);

	do {
		if (key != "description")
			break;
		known_attribute = true;
		if (value.IsEmpty() || value.IsScalar())
			return;
	} while (0);

	do {
		if (key != "required")
			break;
		known_attribute = true;
		try {
			Convert::ToDouble(value);
			return;
		} catch (...) { }
	} while (0);

	do {
		if (key != "skip_key")
			break;
		known_attribute = true;
		try {
			Convert::ToDouble(value);
			return;
		} catch (...) { }
	} while (0);

	do {
		if (key != "repeat_key")
			break;
		known_attribute = true;
		try {
			Convert::ToDouble(value);
			return;
		} catch (...) { }
	} while (0);

	do {
		if (key != "set_if")
			break;
		known_attribute = true;
		if (value.IsEmpty() || value.IsScalar())
			return;
	} while (0);

	do {
		if (key != "set_if")
			break;
		known_attribute = true;
		if (value.IsObjectType<Function>()) {
			return;
		}
	} while (0);

	do {
		if (key != "order")
			break;
		known_attribute = true;
		try {
			Convert::ToDouble(value);
			return;
		} catch (...) { }
	} while (0);

	do {
		if (key != "separator")
			break;
		known_attribute = true;
		if (value.IsEmpty() || value.IsScalar())
			return;
	} while (0);

	if (!known_attribute)
		BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Invalid attribute: " + key));
	else
		BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Invalid type."));
}

static void TIValidateCommand_4(const intrusive_ptr<ObjectImpl<Command> >& object, const String& key, const Value& value, std::vector<String>& location, const ValidationUtils& utils)
{
	bool known_attribute = false;
	do {
		known_attribute = true;
		if (value.IsEmpty() || value.IsScalar())
			return;
	} while (0);

	do {
		known_attribute = true;
		if (value.IsObjectType<Function>()) {
			return;
		}
	} while (0);

	do {
		known_attribute = true;
		if (value.IsObjectType<Dictionary>()) {
			Dictionary::Ptr dict = value;
			{
				ObjectLock olock(dict);
				for (const Dictionary::Pair& kv : dict) {
					const String& akey = kv.first;
					const Value& avalue = kv.second;
					location.emplace_back(akey);
					TIValidateCommand_4_3(object, akey, avalue, location, utils);
					location.pop_back();
				}
			}
			return;
		}
	} while (0);

	if (!known_attribute)
		BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Invalid attribute: " + key));
	else
		BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Invalid type."));
}

static void TIValidateCommand_5(const intrusive_ptr<ObjectImpl<Command> >& object, const String& key, const Value& value, std::vector<String>& location, const ValidationUtils& utils)
{
	bool known_attribute = false;
	do {
		known_attribute = true;
		if (value.IsEmpty() || value.IsScalar())
			return;
	} while (0);

	do {
		known_attribute = true;
		if (value.IsObjectType<Function>()) {
			return;
		}
	} while (0);

	if (!known_attribute)
		BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Invalid attribute: " + key));
	else
		BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Invalid type."));
}

static void TIValidateCommandArguments(const intrusive_ptr<ObjectImpl<Command> >& object, const Value& value, std::vector<String>& location, const ValidationUtils& utils)
{
	if (value.IsEmpty())
		return;

	do {
		if (value.IsObjectType<Dictionary>()) {
			Dictionary::Ptr dict = value;
			{
				ObjectLock olock(dict);
				for (const Dictionary::Pair& kv : dict) {
					const String& akey = kv.first;
					const Value& avalue = kv.second;
					location.emplace_back(akey);
					TIValidateCommand_4(object, akey, avalue, location, utils);
					location.pop_back();
				}
			}
			return;
		}
	} while (0);

	BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Invalid type."));
}

static void TIValidateCommandCommandLine(const intrusive_ptr<ObjectImpl<Command> >& object, const Value& value, std::vector<String>& location, const ValidationUtils& utils)
{
	if (value.IsEmpty())
		return;

	do {
		if (value.IsEmpty() || value.IsScalar())
			return;
	} while (0);

	do {
		if (value.IsObjectType<Function>()) {
			return;
		}
	} while (0);

	do {
		if (value.IsObjectType<Array>()) {
			Array::Ptr arr = value;
			Array::SizeType anum = 0;
			{
				ObjectLock olock(arr);
				for (const Value& avalue : arr) {
					String akey = Convert::ToString(anum);
					location.emplace_back(akey);
					TIValidateCommand_3(object, akey, avalue, location, utils);
					location.pop_back();
					anum++;
				}
			}
			return;
		}
	} while (0);

	BOOST_THROW_EXCEPTION(ValidationError(dynamic_pointer_cast<ConfigObject>(object), location, "Invalid type."));
}

static void TIValidateCommandEnv(const intrusive_ptr<ObjectImpl<Command> >& object, const Dictionary::Ptr& value, std::vector<String>& location, const ValidationUtils& utils)
{
	if (!value)
		return;

	do {
		const Dictionary::Ptr& dict = value;
		{
			ObjectLock olock(dict);
			for (const Dictionary::Pair& kv : dict) {
				const String& akey = kv.first;
				const Value& avalue = kv.second;
				location.emplace_back(akey);
				TIValidateCommand_5(object, akey, avalue, location, utils);
				location.pop_back();
			}
		}
		return;
	} while (0);

}

static void TIValidateCommandExecute(const intrusive_ptr<ObjectImpl<Command> >& object, const Function::Ptr& value, std::vector<String>& location, const ValidationUtils& utils)
{
	if (!value)
		return;

}

static void TIValidateCommandTimeout(const intrusive_ptr<ObjectImpl<Command> >& object, int value, std::vector<String>& location, const ValidationUtils& utils)
{
}

void ObjectImpl<Command>::ValidateArguments(const Lazy<Value>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateArguments(lvalue, utils);
	std::vector<String> location;
	location.emplace_back("arguments");
	TIValidateCommandArguments(this, lvalue(), location, utils);
	location.pop_back();
}

void ObjectImpl<Command>::ValidateCommandLine(const Lazy<Value>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateCommandLine(lvalue, utils);
	std::vector<String> location;
	location.emplace_back("command");
	TIValidateCommandCommandLine(this, lvalue(), location, utils);
	location.pop_back();
}

void ObjectImpl<Command>::ValidateEnv(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateEnv(lvalue, utils);
	std::vector<String> location;
	location.emplace_back("env");
	TIValidateCommandEnv(this, lvalue(), location, utils);
	location.pop_back();
}

void ObjectImpl<Command>::ValidateExecute(const Lazy<Function::Ptr>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateExecute(lvalue, utils);
	std::vector<String> location;
	location.emplace_back("execute");
	TIValidateCommandExecute(this, lvalue(), location, utils);
	location.pop_back();
}

void ObjectImpl<Command>::ValidateTimeout(const Lazy<int>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateTimeout(lvalue, utils);
	std::vector<String> location;
	location.emplace_back("timeout");
	TIValidateCommandTimeout(this, lvalue(), location, utils);
	location.pop_back();
}

}
#pragma GCC diagnostic pop
