#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"
#include "icinga/hostgroup.hpp"

namespace icinga
{

TypeImpl<Host>::TypeImpl()
{ }

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

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

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

Type::Ptr TypeImpl<Host>::GetBaseType() const
{
	return Checkable::TypeInstance;
}

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

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

			break;
		case 100:
			if (name == "display_name")
				return offset + 0;

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

			break;
		case 108:
			if (name == "last_state")
				return offset + 5;
			if (name == "last_hard_state")
				return offset + 6;
			if (name == "last_state_up")
				return offset + 7;
			if (name == "last_state_down")
				return offset + 8;

			break;
		case 115:
			if (name == "state")
				return offset + 4;

			break;
	}

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

Field TypeImpl<Host>::GetFieldInfo(int id) const
{
	int real_id = id - Checkable::TypeInstance->GetFieldCount();
	if (real_id < 0) { return Checkable::TypeInstance->GetFieldInfo(id); }
	switch (real_id) {
		case 0:
			return {0, "String", "display_name", "display_name", nullptr, 2, 0};
		case 1:
			return {1, "String", "address", "address", nullptr, 2, 0};
		case 2:
			return {2, "String", "address6", "address6", nullptr, 2, 0};
		case 3:
			return {3, "Array", "groups", "groups", "HostGroup", 66818, 1};
		case 4:
			return {4, "Number", "state", "state", nullptr, 73, 0};
		case 5:
			return {5, "Number", "last_state", "last_state", nullptr, 73, 0};
		case 6:
			return {6, "Number", "last_hard_state", "last_hard_state", nullptr, 73, 0};
		case 7:
			return {7, "Timestamp", "last_state_up", "last_state_up", nullptr, 4, 0};
		case 8:
			return {8, "Timestamp", "last_state_down", "last_state_down", nullptr, 4, 0};
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

int TypeImpl<Host>::GetFieldCount() const
{
	return 9 + Checkable::TypeInstance->GetFieldCount();
}

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

const std::unordered_set<Type*>& TypeImpl<Host>::GetLoadDependencies() const
{
	static const auto deps ([] {
		auto typeApiListener (GetByName("ApiListener").get());
		VERIFY(typeApiListener);
		VERIFY(ConfigObject::TypeInstance->IsAssignableFrom(typeApiListener));

		auto typeEndpoint (GetByName("Endpoint").get());
		VERIFY(typeEndpoint);
		VERIFY(ConfigObject::TypeInstance->IsAssignableFrom(typeEndpoint));

		auto typeZone (GetByName("Zone").get());
		VERIFY(typeZone);
		VERIFY(ConfigObject::TypeInstance->IsAssignableFrom(typeZone));

		return std::unordered_set<Type*>{ typeApiListener, typeEndpoint, typeZone, };
	}());

	return deps;
}

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

void TypeImpl<Host>::RegisterAttributeHandler(int fieldId, const Type::AttributeHandler& callback)
{
	int real_id = fieldId - Checkable::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { Checkable::TypeInstance->RegisterAttributeHandler(fieldId, callback); return; }
	switch (real_id) {
		case 0:
			ObjectImpl<Host>::OnDisplayNameChanged.connect(callback);
			break;
		case 1:
			ObjectImpl<Host>::OnAddressChanged.connect(callback);
			break;
		case 2:
			ObjectImpl<Host>::OnAddress6Changed.connect(callback);
			break;
		case 3:
			ObjectImpl<Host>::OnGroupsChanged.connect(callback);
			break;
		case 4:
			ObjectImpl<Host>::OnStateChanged.connect(callback);
			break;
		case 5:
			ObjectImpl<Host>::OnLastStateChanged.connect(callback);
			break;
		case 6:
			ObjectImpl<Host>::OnLastHardStateChanged.connect(callback);
			break;
		case 7:
			ObjectImpl<Host>::OnLastStateUpChanged.connect(callback);
			break;
		case 8:
			ObjectImpl<Host>::OnLastStateDownChanged.connect(callback);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

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

	if (2 & types)
		ValidateDisplayName(Lazy<String>([this]() { return GetDisplayName(); }), utils);
	if (2 & types)
		ValidateAddress(Lazy<String>([this]() { return GetAddress(); }), utils);
	if (2 & types)
		ValidateAddress6(Lazy<String>([this]() { return GetAddress6(); }), utils);
	if (2 & types)
		ValidateGroups(Lazy<Array::Ptr>([this]() { return GetGroups(); }), utils);
	if (1 & types)
		ValidateState(Lazy<HostState>([this]() { return GetState(); }), utils);
	if (1 & types)
		ValidateLastState(Lazy<HostState>([this]() { return GetLastState(); }), utils);
	if (1 & types)
		ValidateLastHardState(Lazy<HostState>([this]() { return GetLastHardState(); }), utils);
	if (4 & types)
		ValidateLastStateUp(Lazy<Timestamp>([this]() { return GetLastStateUp(); }), utils);
	if (4 & types)
		ValidateLastStateDown(Lazy<Timestamp>([this]() { return GetLastStateDown(); }), utils);
}

void ObjectImpl<Host>::SimpleValidateDisplayName(const Lazy<String>& value, const ValidationUtils& utils)
{
}

void ObjectImpl<Host>::SimpleValidateAddress(const Lazy<String>& value, const ValidationUtils& utils)
{
}

void ObjectImpl<Host>::SimpleValidateAddress6(const Lazy<String>& value, const ValidationUtils& utils)
{
}

void ObjectImpl<Host>::SimpleValidateGroups(const Lazy<Array::Ptr>& avalue, const ValidationUtils& utils)
{
	if (!avalue())
		BOOST_THROW_EXCEPTION(ValidationError(dynamic_cast<ConfigObject *>(this), { "groups" }, "Attribute must not be empty."));

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

void ObjectImpl<Host>::SimpleValidateState(const Lazy<HostState>& value, const ValidationUtils& utils)
{
}

void ObjectImpl<Host>::SimpleValidateLastState(const Lazy<HostState>& value, const ValidationUtils& utils)
{
}

void ObjectImpl<Host>::SimpleValidateLastHardState(const Lazy<HostState>& value, const ValidationUtils& utils)
{
}

void ObjectImpl<Host>::SimpleValidateLastStateUp(const Lazy<Timestamp>& value, const ValidationUtils& utils)
{
}

void ObjectImpl<Host>::SimpleValidateLastStateDown(const Lazy<Timestamp>& value, const ValidationUtils& utils)
{
}

ObjectImpl<Host>::ObjectImpl()
{
	SetDisplayName(GetDefaultDisplayName(), true);
	SetAddress(GetDefaultAddress(), true);
	SetAddress6(GetDefaultAddress6(), true);
	SetGroups(GetDefaultGroups(), true);
	SetState(GetDefaultState(), true);
	SetLastState(GetDefaultLastState(), true);
	SetLastHardState(GetDefaultLastHardState(), true);
	SetLastStateUp(GetDefaultLastStateUp(), true);
	SetLastStateDown(GetDefaultLastStateDown(), true);
}

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

void ObjectImpl<Host>::SetField(int id, const Value& value, bool suppress_events, const Value& cookie)
{
	int real_id = id - Checkable::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { Checkable::SetField(id, value, suppress_events, cookie); return; }
	switch (real_id) {
		case 0:
			SetDisplayName(value, suppress_events, cookie);
			break;
		case 1:
			SetAddress(value, suppress_events, cookie);
			break;
		case 2:
			SetAddress6(value, suppress_events, cookie);
			break;
		case 3:
			SetGroups(value, suppress_events, cookie);
			break;
		case 4:
			SetState(static_cast<HostState>(static_cast<int>(value)), suppress_events, cookie);
			break;
		case 5:
			SetLastState(static_cast<HostState>(static_cast<int>(value)), suppress_events, cookie);
			break;
		case 6:
			SetLastHardState(static_cast<HostState>(static_cast<int>(value)), suppress_events, cookie);
			break;
		case 7:
			SetLastStateUp(value, suppress_events, cookie);
			break;
		case 8:
			SetLastStateDown(value, suppress_events, cookie);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

Value ObjectImpl<Host>::GetField(int id) const
{
	int real_id = id - Checkable::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { return Checkable::GetField(id); }
	switch (real_id) {
		case 0:
			return GetDisplayName();
		case 1:
			return GetAddress();
		case 2:
			return GetAddress6();
		case 3:
			return GetGroups();
		case 4:
			return GetState();
		case 5:
			return GetLastState();
		case 6:
			return GetLastHardState();
		case 7:
			return GetLastStateUp();
		case 8:
			return GetLastStateDown();
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

void ObjectImpl<Host>::ValidateField(int id, const Lazy<Value>& lvalue, const ValidationUtils& utils)
{
	int real_id = id - Checkable::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { Checkable::ValidateField(id, lvalue, utils); return; }
	switch (real_id) {
		case 0:
			ValidateDisplayName(lvalue, utils);
			break;
		case 1:
			ValidateAddress(lvalue, utils);
			break;
		case 2:
			ValidateAddress6(lvalue, utils);
			break;
		case 3:
			ValidateGroups(lvalue, utils);
			break;
		case 4:
			ValidateState(static_cast<Lazy<HostState> >(static_cast<Lazy<int> >(lvalue)), utils);
			break;
		case 5:
			ValidateLastState(static_cast<Lazy<HostState> >(static_cast<Lazy<int> >(lvalue)), utils);
			break;
		case 6:
			ValidateLastHardState(static_cast<Lazy<HostState> >(static_cast<Lazy<int> >(lvalue)), utils);
			break;
		case 7:
			ValidateLastStateUp(lvalue, utils);
			break;
		case 8:
			ValidateLastStateDown(lvalue, utils);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

void ObjectImpl<Host>::NotifyField(int id, const Value& cookie)
{
	int real_id = id - Checkable::TypeInstance->GetFieldCount(); 
	if (real_id < 0) { Checkable::NotifyField(id, cookie); return; }
	switch (real_id) {
		case 0:
			NotifyDisplayName(cookie);
			break;
		case 1:
			NotifyAddress(cookie);
			break;
		case 2:
			NotifyAddress6(cookie);
			break;
		case 3:
			NotifyGroups(cookie);
			break;
		case 4:
			NotifyState(cookie);
			break;
		case 5:
			NotifyLastState(cookie);
			break;
		case 6:
			NotifyLastHardState(cookie);
			break;
		case 7:
			NotifyLastStateUp(cookie);
			break;
		case 8:
			NotifyLastStateDown(cookie);
			break;
		default:
			throw std::runtime_error("Invalid field ID.");
	}
}

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

String ObjectImpl<Host>::GetDisplayName() const
{

			String displayName = m_DisplayName.load();
			if (displayName.IsEmpty())
				return GetName();
			else
				return displayName;
		
}

String ObjectImpl<Host>::GetAddress() const
{
	return m_Address.load();
}

String ObjectImpl<Host>::GetAddress6() const
{
	return m_Address6.load();
}

Array::Ptr ObjectImpl<Host>::GetGroups() const
{
	return m_Groups.load();
}

Timestamp ObjectImpl<Host>::GetLastStateUp() const
{
	return m_LastStateUp.load();
}

Timestamp ObjectImpl<Host>::GetLastStateDown() const
{
	return m_LastStateDown.load();
}

void ObjectImpl<Host>::SetDisplayName(const String& value, bool suppress_events, const Value& cookie)
{
	m_DisplayName.store(value);
	if (!suppress_events) {
		NotifyDisplayName(cookie);
	}

}

void ObjectImpl<Host>::SetAddress(const String& value, bool suppress_events, const Value& cookie)
{
	m_Address.store(value);
	if (!suppress_events) {
		NotifyAddress(cookie);
	}

}

void ObjectImpl<Host>::SetAddress6(const String& value, bool suppress_events, const Value& cookie)
{
	m_Address6.store(value);
	if (!suppress_events) {
		NotifyAddress6(cookie);
	}

}

void ObjectImpl<Host>::SetGroups(const Array::Ptr& value, bool suppress_events, const Value& cookie)
{
	Value oldValue = GetGroups();
	auto *dobj = dynamic_cast<ConfigObject *>(this);
	m_Groups.store(value);
	if (!dobj || dobj->IsActive())
		TrackGroups(oldValue, value);
	if (!suppress_events) {
		NotifyGroups(cookie);
		if (!dobj || dobj->IsActive())
			OnGroupsChangedWithOldValue(static_cast<Host *>(this), oldValue, value);
	}

}

void ObjectImpl<Host>::SetState(const HostState& value, bool suppress_events, const Value& cookie)
{


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

}

void ObjectImpl<Host>::SetLastState(const HostState& value, bool suppress_events, const Value& cookie)
{


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

}

void ObjectImpl<Host>::SetLastHardState(const HostState& value, bool suppress_events, const Value& cookie)
{


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

}

void ObjectImpl<Host>::SetLastStateUp(const Timestamp& value, bool suppress_events, const Value& cookie)
{
	m_LastStateUp.store(value);
	if (!suppress_events) {
		NotifyLastStateUp(cookie);
	}

}

void ObjectImpl<Host>::SetLastStateDown(const Timestamp& value, bool suppress_events, const Value& cookie)
{
	m_LastStateDown.store(value);
	if (!suppress_events) {
		NotifyLastStateDown(cookie);
	}

}

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

void ObjectImpl<Host>::Start(bool runtimeCreated)
{
	Checkable::Start(runtimeCreated);

	TrackGroups(Empty, GetGroups());
}

void ObjectImpl<Host>::Stop(bool runtimeRemoved)
{
	Checkable::Stop(runtimeRemoved);

	TrackGroups(GetGroups(), Empty);
}

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

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

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

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

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

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

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

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

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

String ObjectImpl<Host>::GetDefaultDisplayName() const
{
	return String();
}

String ObjectImpl<Host>::GetDefaultAddress() const
{
	return String();
}

String ObjectImpl<Host>::GetDefaultAddress6() const
{
	return String();
}

Array::Ptr ObjectImpl<Host>::GetDefaultGroups() const
{
	 return new Array(); 
}

HostState ObjectImpl<Host>::GetDefaultState() const
{
	return HostState();
}

HostState ObjectImpl<Host>::GetDefaultLastState() const
{
	return HostState();
}

HostState ObjectImpl<Host>::GetDefaultLastHardState() const
{
	return HostState();
}

Timestamp ObjectImpl<Host>::GetDefaultLastStateUp() const
{
	return Timestamp();
}

Timestamp ObjectImpl<Host>::GetDefaultLastStateDown() const
{
	return Timestamp();
}


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


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


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


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


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


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


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


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


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


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

void ObjectImpl<Host>::ValidateAddress(const Lazy<String>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateAddress(lvalue, utils);
}

void ObjectImpl<Host>::ValidateAddress6(const Lazy<String>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateAddress6(lvalue, utils);
}

void ObjectImpl<Host>::ValidateDisplayName(const Lazy<String>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateDisplayName(lvalue, utils);
}

void ObjectImpl<Host>::ValidateGroups(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateGroups(lvalue, utils);
}

void ObjectImpl<Host>::ValidateLastHardState(const Lazy<HostState>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateLastHardState(lvalue, utils);
}

void ObjectImpl<Host>::ValidateLastState(const Lazy<HostState>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateLastState(lvalue, utils);
}

void ObjectImpl<Host>::ValidateLastStateDown(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateLastStateDown(lvalue, utils);
}

void ObjectImpl<Host>::ValidateLastStateUp(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateLastStateUp(lvalue, utils);
}

void ObjectImpl<Host>::ValidateState(const Lazy<HostState>& lvalue, const ValidationUtils& utils)
{
	SimpleValidateState(lvalue, utils);
}

}
#pragma GCC diagnostic pop
