#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<Zone>::TypeImpl()
{ }

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

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

int TypeImpl<Zone>::GetAttributes() const
{
	return 0;
}

Type::Ptr TypeImpl<Zone>::GetBaseType() const
{
	return ConfigObject::TypeInstance;
}

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

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

			break;
		case 101:
			if (name == "endpoints")
				return offset + 1;

			break;
		case 103:
			if (name == "global")
				return offset + 3;

			break;
		case 112:
			if (name == "parent")
				return offset + 0;

			break;
	}

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

Field TypeImpl<Zone>::GetFieldInfo(int id) const
{
	int real_id = id - ConfigObject::TypeInstance->GetFieldCount();
	if (real_id < 0) { return ConfigObject::TypeInstance->GetFieldInfo(id); }
	switch (real_id) {
		case 0:
			return {0, "String", "parent", "parent", "Zone", 1538, 0};
		case 1:
			return {1, "Array", "endpoints", "endpoints", "Endpoint", 2, 1};
		case 2:
			return {2, "Array", "all_parents", "all_parents", nullptr, 1089, 1};
		case 3:
			return {3, "Boolean", "global", "global", nullptr, 2, 0};
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

int TypeImpl<Zone>::GetFieldCount() const
{
	return 4 + ConfigObject::TypeInstance->GetFieldCount();
}

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

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

void TypeImpl<Zone>::RegisterAttributeHandler(int fieldId, const Type::AttributeHandler& callback)
{
	int real_id = fieldId - ConfigObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { ConfigObject::TypeInstance->RegisterAttributeHandler(fieldId, callback); return; }
	switch (real_id) {
		case 0:
			ObjectImpl<Zone>::OnParentRawChanged.connect(callback);
			break;
		case 1:
			ObjectImpl<Zone>::OnEndpointsRawChanged.connect(callback);
			break;
		case 2:
			ObjectImpl<Zone>::OnAllParentsChanged.connect(callback);
			break;
		case 3:
			ObjectImpl<Zone>::OnGlobalChanged.connect(callback);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

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

	if (2 & types)
		ValidateParentRaw(Lazy<String>([this]() { return GetParentRaw(); }), utils);
	if (2 & types)
		ValidateEndpointsRaw(Lazy<Array::Ptr>([this]() { return GetEndpointsRaw(); }), utils);
	if (1 & types)
		ValidateAllParents(Lazy<Array::Ptr>([this]() { return GetAllParents(); }), utils);
	if (2 & types)
		ValidateGlobal(Lazy<bool>([this]() { return GetGlobal(); }), utils);
}

void ObjectImpl<Zone>::SimpleValidateParentRaw(const Lazy<String>& value, const ValidationUtils& utils)
{
			if (!value().IsEmpty() && !utils.ValidateName("Zone", value()))
				BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { "parent" }, "Object '" + value() + "' of type 'Zone' does not exist."));
}

void ObjectImpl<Zone>::SimpleValidateEndpointsRaw(const Lazy<Array::Ptr>& avalue, const ValidationUtils& utils)
{
	if (avalue()) {
		ObjectLock olock(avalue());
		for (const Value& value : avalue()) {
			if (!value.IsEmpty() && !value.IsString())
				BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { "endpoints" }, "It is not allowed to specify '" + value + "' of type '" + value.GetTypeName() + "' as 'Endpoint' name. Expected type of 'Endpoint' name: 'String'."));
			if (value.IsEmpty() || !utils.ValidateName("Endpoint", value))
				BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { "endpoints" }, "Object '" + value + "' of type 'Endpoint' does not exist."));
		}
	}
}

void ObjectImpl<Zone>::SimpleValidateAllParents(const Lazy<Array::Ptr>& avalue, const ValidationUtils& utils)
{
	if (avalue()) {
		ObjectLock olock(avalue());
		for (const Value& value : avalue()) {
	if (value.IsObjectType<Function>()) {
		Function::Ptr func = value;
		if (func->IsDeprecated())
			Log(LogWarning, "Zone") << "Attribute 'all_parents' 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<Zone>::SimpleValidateGlobal(const Lazy<bool>& value, const ValidationUtils& utils)
{
}

ObjectImpl<Zone>::ObjectImpl()
{
	SetParentRaw(GetDefaultParentRaw(), true);
	SetEndpointsRaw(GetDefaultEndpointsRaw(), true);
	SetAllParents(GetDefaultAllParents(), true);
	SetGlobal(GetDefaultGlobal(), true);
}

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

void ObjectImpl<Zone>::SetField(int id, const Value& value, bool suppress_events, const Value& cookie)
{
	int real_id = id - ConfigObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { ConfigObject::SetField(id, value, suppress_events, cookie); return; }
	switch (real_id) {
		case 0:
			SetParentRaw(value, suppress_events, cookie);
			break;
		case 1:
			SetEndpointsRaw(value, suppress_events, cookie);
			break;
		case 2:
			SetAllParents(value, suppress_events, cookie);
			break;
		case 3:
			SetGlobal(value, suppress_events, cookie);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

Value ObjectImpl<Zone>::GetField(int id) const
{
	int real_id = id - ConfigObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { return ConfigObject::GetField(id); }
	switch (real_id) {
		case 0:
			return GetParentRaw();
		case 1:
			return GetEndpointsRaw();
		case 2:
			return GetAllParents();
		case 3:
			return GetGlobal();
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

void ObjectImpl<Zone>::ValidateField(int id, const Lazy<Value>& lvalue, const ValidationUtils& utils)
{
	int real_id = id - ConfigObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { ConfigObject::ValidateField(id, lvalue, utils); return; }
	switch (real_id) {
		case 0:
			ValidateParentRaw(lvalue, utils);
			break;
		case 1:
			ValidateEndpointsRaw(lvalue, utils);
			break;
		case 2:
			ValidateAllParents(lvalue, utils);
			break;
		case 3:
			ValidateGlobal(lvalue, utils);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

void ObjectImpl<Zone>::NotifyField(int id, const Value& cookie)
{
	int real_id = id - ConfigObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { ConfigObject::NotifyField(id, cookie); return; }
	switch (real_id) {
		case 0:
			NotifyParentRaw(cookie);
			break;
		case 1:
			NotifyEndpointsRaw(cookie);
			break;
		case 2:
			NotifyAllParents(cookie);
			break;
		case 3:
			NotifyGlobal(cookie);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

Object::Ptr ObjectImpl<Zone>::NavigateField(int id) const
{
	int real_id = id - ConfigObject::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { return ConfigObject::NavigateField(id); }
	switch (real_id) {
		case 0:
			return NavigateParentRaw();
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

String ObjectImpl<Zone>::GetParentRaw() const
{
	return m_ParentRaw.load();
}

Array::Ptr ObjectImpl<Zone>::GetEndpointsRaw() const
{
	return m_EndpointsRaw.load();
}

bool ObjectImpl<Zone>::GetGlobal() const
{
	return m_Global.load();
}

void ObjectImpl<Zone>::SetParentRaw(const String& value, bool suppress_events, const Value& cookie)
{
	Value oldValue = GetParentRaw();
	auto *dobj = dynamic_cast<ConfigObject *>(this);
	m_ParentRaw.store(value);
	if (!dobj || dobj->IsActive())
		TrackParentRaw(oldValue, value);
	if (!suppress_events) {
		NotifyParentRaw(cookie);
	}

}

void ObjectImpl<Zone>::SetEndpointsRaw(const Array::Ptr& value, bool suppress_events, const Value& cookie)
{
	Value oldValue = GetEndpointsRaw();
	auto *dobj = dynamic_cast<ConfigObject *>(this);
	m_EndpointsRaw.store(value);
	if (!dobj || dobj->IsActive())
		TrackEndpointsRaw(oldValue, value);
	if (!suppress_events) {
		NotifyEndpointsRaw(cookie);
	}

}

void ObjectImpl<Zone>::SetAllParents(const Array::Ptr& value, bool suppress_events, const Value& cookie)
{


	if (!suppress_events) {
		NotifyAllParents(cookie);
	}

}

void ObjectImpl<Zone>::SetGlobal(bool value, bool suppress_events, const Value& cookie)
{
	m_Global.store(value);
	if (!suppress_events) {
		NotifyGlobal(cookie);
	}

}

void ObjectImpl<Zone>::TrackParentRaw(const String& oldValue, const String& newValue)
{
	if (!oldValue.IsEmpty())
		DependencyGraph::RemoveDependency(this, ConfigObject::GetObject<Zone>(oldValue).get());
	if (!newValue.IsEmpty())
		DependencyGraph::AddDependency(this, ConfigObject::GetObject<Zone>(newValue).get());
}

void ObjectImpl<Zone>::TrackEndpointsRaw(const Array::Ptr& oldValue, const Array::Ptr& newValue)
{
	if (oldValue) {
		ObjectLock olock(oldValue);
		for (auto& ref : oldValue) {
			DependencyGraph::RemoveDependency(this, ConfigObject::GetObject<Endpoint>(ref).get());
		}
	}
	if (newValue) {
		ObjectLock olock(newValue);
		for (auto& ref : newValue) {
			DependencyGraph::AddDependency(this, ConfigObject::GetObject<Endpoint>(ref).get());
		}
	}
}

Object::Ptr ObjectImpl<Zone>::NavigateParentRaw() const
{
	
			return Zone::GetByName(GetParentRaw());
		
}

void ObjectImpl<Zone>::Start(bool runtimeCreated)
{
	ConfigObject::Start(runtimeCreated);

	TrackParentRaw(Empty, GetParentRaw());
	TrackEndpointsRaw(Empty, GetEndpointsRaw());
}

void ObjectImpl<Zone>::Stop(bool runtimeRemoved)
{
	ConfigObject::Stop(runtimeRemoved);

	TrackParentRaw(GetParentRaw(), Empty);
	TrackEndpointsRaw(GetEndpointsRaw(), Empty);
}

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

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

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

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

String ObjectImpl<Zone>::GetDefaultParentRaw() const
{
	return String();
}

Array::Ptr ObjectImpl<Zone>::GetDefaultEndpointsRaw() const
{
	return Array::Ptr();
}

Array::Ptr ObjectImpl<Zone>::GetDefaultAllParents() const
{
	return Array::Ptr();
}

bool ObjectImpl<Zone>::GetDefaultGlobal() const
{
	return bool();
}


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


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


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


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

void ObjectImpl<Zone>::ValidateAllParents(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateAllParents(lvalue, utils);
}

void ObjectImpl<Zone>::ValidateEndpointsRaw(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateEndpointsRaw(lvalue, utils);
}

void ObjectImpl<Zone>::ValidateGlobal(const Lazy<bool>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateGlobal(lvalue, utils);
}

void ObjectImpl<Zone>::ValidateParentRaw(const Lazy<String>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateParentRaw(lvalue, utils);
}

}
#pragma GCC diagnostic pop
