/*
    Ousía
    Copyright (C) 2014, 2015  Benjamin Paaßen, Andreas Stöckel
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see .
*/
#include 
#include 
#include 
#include "Domain.hpp"
namespace ousia {
/* Class FieldDescriptor */
FieldDescriptor::FieldDescriptor(Manager &mgr, Handle parent,
                                 Handle primitiveType, std::string name,
                                 bool optional)
    : Node(mgr, std::move(name), parent),
      children(this),
      fieldType(FieldType::PRIMITIVE),
      primitiveType(acquire(primitiveType)),
      optional(optional)
{
	if (parent != nullptr) {
		parent->addFieldDescriptor(this);
	}
}
FieldDescriptor::FieldDescriptor(Manager &mgr, Handle parent,
                                 FieldType fieldType, std::string name,
                                 bool optional)
    : Node(mgr, std::move(name), parent),
      children(this),
      fieldType(fieldType),
      optional(optional)
{
	if (parent != nullptr) {
		parent->addFieldDescriptor(this);
	}
}
bool FieldDescriptor::doValidate(Logger &logger) const
{
	bool valid = true;
	// check parent type
	if (getParent() == nullptr) {
		logger.error("This field has no parent!", *this);
		valid = false;
	} else if (!getParent()->isa(RttiTypes::Descriptor)) {
		logger.error("The parent of this field is not a descriptor!", *this);
		valid = false;
	}
	// check name
	if (!getName().empty()) {
		valid = valid & validateName(logger);
	}
	// check consistency of FieldType with the rest of the FieldDescriptor.
	if (fieldType == FieldType::PRIMITIVE) {
		if (children.size() > 0) {
			logger.error(
			    "This field is supposed to be primitive but has "
			    "registered child classes!", *this);
			valid = false;
		}
		if (primitiveType == nullptr) {
			logger.error(
			    "This field is supposed to be primitive but has "
			    "no primitive type!", *this);
			valid = false;
		}
	} else {
		if (primitiveType != nullptr) {
			logger.error(
			    "This field is supposed to be non-primitive but has "
			    "a primitive type!", *this);
			valid = false;
		}
	}
	/*
	 * we are not allowed to call the validation functions of each child because
	 * this might lead to cycles. What we should do, however, is to check if
	 * there are no duplicates.
	 */
	std::set names;
	for (Handle c : children) {
		if (!names.insert(c->getName()).second) {
			logger.error(std::string("Field \"") + getName() +
			             "\" had multiple children with the name \"" +
			             c->getName() + "\"", *this);
			valid = false;
		}
	}
	return valid;
}
bool FieldDescriptor::removeChild(Handle c)
{
	auto it = children.find(c);
	if (it != children.end()) {
		invalidate();
		children.erase(it);
		return true;
	}
	return false;
}
/* Class Descriptor */
void Descriptor::doResolve(ResolutionState &state)
{
	if (attributesDescriptor != nullptr) {
		const NodeVector &attributes =
		    attributesDescriptor->getAttributes();
		continueResolveComposita(attributes, attributes.getIndex(), state);
	}
	continueResolveComposita(fieldDescriptors, fieldDescriptors.getIndex(),
	                         state);
}
bool Descriptor::doValidate(Logger &logger) const
{
	bool valid = true;
	// check parent type
	if (getParent() == nullptr) {
		logger.error("This Descriptor has no parent!", *this);
		valid = false;
	} else if (!getParent()->isa(RttiTypes::Domain)) {
		logger.error("The parent of this Descriptor is not a Domain!", *this);
		valid = false;
	}
	// check name
	if (getName().empty()) {
		logger.error("The name of this Descriptor is empty!", *this);
		valid = false;
	} else {
		valid = valid & validateName(logger);
	}
	// check if all FieldDescriptors have this Descriptor as parent.
	for (Handle fd : fieldDescriptors) {
		if (fd->getParent() != this) {
			logger.error(std::string("Descriptor \"") + getName() +
			             "\" has "
			             "field \"" +
			             fd->getName() +
			             "\" as child but the field does not "
			             "have the Descriptor as parent.", *this);
			valid = false;
		}
	}
	// check the FieldDescriptors themselves.
	return valid & continueValidationCheckDuplicates(fieldDescriptors, logger);
}
std::vector> Descriptor::pathTo(
    Handle target) const
{
	std::vector> path;
	continuePath(target, path);
	return path;
}
bool Descriptor::continuePath(Handle target,
                              std::vector> ¤tPath) const
{
	// check if we are at the target already
	if (this == target) {
		return true;
	}
	// a variable to determine if we already found a solution
	bool found = false;
	// the currently optimal path.
	std::vector> optimum;
	// use recursive depth-first search from the top to reach the given child
	// get the list of effective FieldDescriptors.
	NodeVector fields;
	if (isa(RttiTypes::StructuredClass)) {
		const StructuredClass *tis = static_cast(this);
		fields = tis->getEffectiveFieldDescriptors();
	} else {
		fields = getFieldDescriptors();
	}
	for (auto &fd : fields) {
		for (auto &c : fd->getChildren()) {
			// check if a child is the target node.
			if (c == target) {
				// if we have made the connection, stop the search.
				currentPath.push_back(fd);
				return true;
			}
			// look for transparent intermediate nodes.
			if (c->isTransparent()) {
				// copy the path.
				std::vector> cPath = currentPath;
				cPath.push_back(fd);
				cPath.push_back(c);
				// recursion.
				if (c->continuePath(target, cPath) &&
				    (!found || optimum.size() > cPath.size())) {
					// look if this path is better than the current optimum.
					optimum = std::move(cPath);
					found = true;
				}
			}
		}
	}
	if (isa(RttiTypes::StructuredClass)) {
		const StructuredClass *tis = static_cast(this);
		// if this is a StructuredClass we also can call the subclasses.
		for (auto &c : tis->getSubclasses()) {
			// copy the path.
			std::vector> cPath = currentPath;
			if (c->continuePath(target, cPath) &&
			    (!found || optimum.size() > cPath.size())) {
				// look if this path is better than the current optimum.
				optimum = std::move(cPath);
				found = true;
			}
		}
	}
	// put the optimum in the given path reference.
	currentPath = std::move(optimum);
	// return if we found something.
	return found;
}
void Descriptor::addFieldDescriptor(Handle fd)
{
	// only add it if we need to.
	if (fieldDescriptors.find(fd) == fieldDescriptors.end()) {
		invalidate();
		fieldDescriptors.push_back(fd);
	}
	Handle par = fd->getParent();
	if (par != this) {
		if (par != nullptr) {
			// remove the FieldDescriptor from the old parent.
			par.cast()->removeFieldDescriptor(fd);
		}
		fd->setParent(this);
	}
}
void Descriptor::copyFieldDescriptor(Handle fd)
{
	if (fd->getFieldType() == FieldDescriptor::FieldType::PRIMITIVE) {
		/*
		 * To call the "new" operation is enough here, because the
		 * constructor will add the newly constructed FieldDescriptor to this
		 * Descriptor automatically.
		 */
		new FieldDescriptor(getManager(), this, fd->getPrimitiveType(),
		                    fd->getName(), fd->isOptional());
	} else {
		new FieldDescriptor(getManager(), this, fd->getFieldType(),
		                    fd->getName(), fd->isOptional());
	}
}
bool Descriptor::removeFieldDescriptor(Handle fd)
{
	auto it = fieldDescriptors.find(fd);
	if (it != fieldDescriptors.end()) {
		invalidate();
		fieldDescriptors.erase(it);
		fd->setParent(nullptr);
		return true;
	}
	return false;
}
Rooted Descriptor::createPrimitiveFieldDescriptor(
    Handle primitiveType, std::string name, bool optional)
{
	return Rooted{new FieldDescriptor(
	    getManager(), this, primitiveType, std::move(name), optional)};
}
Rooted Descriptor::createFieldDescriptor(
    FieldDescriptor::FieldType fieldType, std::string name, bool optional)
{
	return Rooted{new FieldDescriptor(
	    getManager(), this, fieldType, std::move(name), optional)};
}
/* Class StructuredClass */
StructuredClass::StructuredClass(Manager &mgr, std::string name,
                                 Handle domain, Variant cardinality,
                                 Handle attributesDescriptor,
                                 Handle superclass,
                                 bool transparent, bool root)
    : Descriptor(mgr, std::move(name), domain, attributesDescriptor),
      cardinality(std::move(cardinality)),
      superclass(acquire(superclass)),
      subclasses(this),
      transparent(transparent),
      root(root)
{
	if (superclass != nullptr) {
		superclass->addSubclass(this);
	}
	if (domain != nullptr) {
		domain->addStructuredClass(this);
	}
}
bool StructuredClass::doValidate(Logger &logger) const
{
	bool valid = true;
	// check if all registered subclasses have this StructuredClass as parent.
	for (Handle sub : subclasses) {
		if (sub->getSuperclass() != this) {
			logger.error(std::string("Struct \"") + sub->getName() +
			             "\" is registered as subclass of \"" + getName() +
			             "\" but does not have it as superclass!", *this);
			valid = false;
		}
	}
	// check the cardinality.
	if(!cardinality.isCardinality()){
		logger.error(cardinality.toString() + " is not a cardinality!", *this);
		valid = false;
	}
	// check the validity of this superclass.
	if (superclass != nullptr) {
		valid = valid & superclass->validate(logger);
	}
	// check the validity as a Descriptor.
	/*
	 * Note that we do not check the validity of all subclasses. This is because
	 * it will lead to cycles as the subclasses would call validate on their
	 * superclass, which is this one.
	 */
	return valid & Descriptor::doValidate(logger);
}
void StructuredClass::setSuperclass(Handle sup)
{
	if (superclass == sup) {
		return;
	}
	// remove this subclass from the old superclass.
	if (superclass != nullptr) {
		superclass->removeSubclass(this);
	}
	// set the new superclass
	superclass = acquire(sup);
	invalidate();
	// add this class as new subclass of the new superclass.
	if (sup != nullptr) {
		sup->addSubclass(this);
	}
}
bool StructuredClass::isSubclassOf(Handle c) const
{
	if (c == nullptr || superclass == nullptr) {
		return false;
	}
	if (c == superclass) {
		return true;
	}
	return superclass->isSubclassOf(c);
}
void StructuredClass::addSubclass(Handle sc)
{
	// check if we already have that class.
	if (subclasses.find(sc) == subclasses.end()) {
		invalidate();
		subclasses.push_back(sc);
	}
	sc->setSuperclass(this);
}
void StructuredClass::removeSubclass(Handle sc)
{
	// if we don't have this subclass we can return directly.
	if (sc == nullptr) {
		return;
	}
	auto it = subclasses.find(sc);
	if (it == subclasses.end()) {
		return;
	}
	// otherwise we have to erase it.
	invalidate();
	subclasses.erase(it);
	sc->setSuperclass(nullptr);
}
const void StructuredClass::gatherFieldDescriptors(
    NodeVector ¤t,
    std::set &overriddenFields) const
{
	// append all FieldDescriptors that are not overridden.
	for (auto &f : Descriptor::getFieldDescriptors()) {
		if (overriddenFields.insert(f->getName()).second) {
			current.push_back(f);
		}
	}
	if (superclass != nullptr) {
		superclass->gatherFieldDescriptors(current, overriddenFields);
	}
}
NodeVector StructuredClass::getEffectiveFieldDescriptors()
    const
{
	// in this case we return a NodeVector of Rooted entries without owner.
	NodeVector vec;
	std::set overriddenFields;
	gatherFieldDescriptors(vec, overriddenFields);
	return std::move(vec);
}
/* Class AnnotationClass */
AnnotationClass::AnnotationClass(
    Manager &mgr, std::string name, Handle domain,
    // TODO: What would be a wise default value for attributes?
    Handle attributesDescriptor)
    : Descriptor(mgr, std::move(name), domain, attributesDescriptor)
{
	if (!domain.isNull()) {
		domain->addAnnotationClass(this);
	}
}
/* Class Domain */
void Domain::doResolve(ResolutionState &state)
{
	if (!continueResolveComposita(structuredClasses,
	                              structuredClasses.getIndex(), state) |
	    continueResolveComposita(annotationClasses,
	                             annotationClasses.getIndex(), state)) {
		continueResolveReferences(typesystems, state);
	}
}
bool Domain::doValidate(Logger &logger) const
{
	// check validity of name, of StructuredClasses, of AnnotationClasses and
	// TypeSystems.
	return validateName(logger) &
	       continueValidationCheckDuplicates(structuredClasses, logger) &
	       continueValidationCheckDuplicates(annotationClasses, logger) &
	       continueValidationCheckDuplicates(typesystems, logger);
}
void Domain::addStructuredClass(Handle s)
{
	// only add it if we need to.
	if (structuredClasses.find(s) == structuredClasses.end()) {
		invalidate();
		structuredClasses.push_back(s);
	}
	Handle par = s->getParent();
	if (par != this) {
		if (par != nullptr) {
			// remove the StructuredClass from the old parent.
			par.cast()->removeStructuredClass(s);
		}
		s->setParent(this);
	}
}
bool Domain::removeStructuredClass(Handle s)
{
	auto it = structuredClasses.find(s);
	if (it != structuredClasses.end()) {
		invalidate();
		structuredClasses.erase(it);
		s->setParent(nullptr);
		return true;
	}
	return false;
}
Rooted Domain::createStructuredClass(
    std::string name, Variant cardinality,
    Handle attributesDescriptor, Handle superclass,
    bool transparent, bool root)
{
	return Rooted{new StructuredClass(
	    getManager(), std::move(name), this, std::move(cardinality),
	    attributesDescriptor, superclass, std::move(transparent),
	    std::move(root))};
}
void Domain::addAnnotationClass(Handle a)
{
	// only add it if we need to.
	if (annotationClasses.find(a) == annotationClasses.end()) {
		invalidate();
		annotationClasses.push_back(a);
	}
	Handle par = a->getParent();
	if (par != this) {
		if (par != nullptr) {
			// remove the StructuredClass from the old parent.
			par.cast()->removeAnnotationClass(a);
		}
		a->setParent(this);
	}
}
bool Domain::removeAnnotationClass(Handle a)
{
	auto it = annotationClasses.find(a);
	if (it != annotationClasses.end()) {
		invalidate();
		annotationClasses.erase(it);
		a->setParent(nullptr);
		return true;
	}
	return false;
}
Rooted Domain::createAnnotationClass(
    std::string name, Handle attributesDescriptor)
{
	return Rooted{new AnnotationClass(
	    getManager(), std::move(name), this, attributesDescriptor)};
}
/* Type registrations */
namespace RttiTypes {
const Rtti FieldDescriptor =
    RttiBuilder("FieldDescriptor").parent(&Node);
const Rtti Descriptor =
    RttiBuilder("Descriptor").parent(&Node);
const Rtti StructuredClass =
    RttiBuilder("StructuredClass")
        .parent(&Descriptor)
        .composedOf(&FieldDescriptor);
const Rtti AnnotationClass =
    RttiBuilder("AnnotationClass").parent(&Descriptor);
const Rtti Domain = RttiBuilder("Domain")
                        .parent(&Node)
                        .composedOf({&StructuredClass, &AnnotationClass});
}
}