package eu.dnetlib.dlms.union.objects;

import java.util.Collection;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.springframework.beans.factory.annotation.Required;

import eu.dnetlib.dlms.impl.GeneralHibernateDAO;
import eu.dnetlib.dlms.lowlevel.LowLevelException;
import eu.dnetlib.dlms.lowlevel.SetMembershipValidator;
import eu.dnetlib.dlms.lowlevel.objects.LLDigitalObject;
import eu.dnetlib.dlms.lowlevel.objects.Set;
import eu.dnetlib.dlms.lowlevel.objects.SetDAO;
import eu.dnetlib.dlms.lowlevel.objects.Wrapper;
import eu.dnetlib.dlms.lowlevel.objects.WrapperDAO;

/**
 * DAO implementation for UnionSetDAO that stores UnionSet instances and the contained Wrappers in hibernate.
 * 
 * @author lexis
 */
public class UnionSetDAOHibernate extends GeneralHibernateDAO<UnionSet> implements UnionSetDAO {

	/** Logger. */
	private static final Log log = LogFactory.getLog(UnionSetDAOHibernate.class);
	/** SetDAO. */
	private SetDAO setDAO;
	/** DAO for Wrapper instances. */
	private WrapperDAO wrapperDAO;
	/** Instance to validate objects before adding it to sets. */
	private SetMembershipValidator setMembershipValidator;

	@Required
	public void setSetDAO(final SetDAO setDAO) {
		this.setDAO = setDAO;
	}

	public SetDAO getSetDAO() {
		return this.setDAO;
	}

	@Required
	public void setWrapperDAO(final WrapperDAO wrapperDAO) {
		this.wrapperDAO = wrapperDAO;
	}

	public WrapperDAO getWrapperDAO() {
		return this.wrapperDAO;
	}

	@Required
	public void setSetMembershipValidator(final SetMembershipValidator setMembershipValidator) {
		this.setMembershipValidator = setMembershipValidator;
	}

	public SetMembershipValidator getSetMembershipValidator() {
		return this.setMembershipValidator;
	}

	/** NO args constructor. */
	public UnionSetDAOHibernate() {
		this.setClazz(UnionSet.class);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.dlms.impl.GeneralHibernateDAO#load(eu.dnetlib.dlms.lowlevel.Dorotable)
	 */

	@Override
	public void load(final UnionSet set) {
		this.getHibernateTemplate().lock(set, LockMode.NONE);
		this.getHibernateTemplate().initialize(set);
		this.getHibernateTemplate().initialize(set.getObjects());
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.dlms.impl.GeneralHibernateDAO#delete(eu.dnetlib.dlms.lowlevel.objects.LLDigitalObject)
	 */

	@Override
	public void delete(final UnionSet set) {
		this.setDAO.delete(set);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.dlms.impl.GeneralHibernateDAO#save(eu.dnetlib.dlms.lowlevel.objects.LLDigitalObject)
	 */

	@Override
	public void save(final UnionSet set) {
		this.setDAO.save(set);
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.dlms.union.objects.UnionSetDAO#addToSet(eu.dnetlib.dlms.union.objects.UnionSet,
	 *      eu.dnetlib.dlms.lowlevel.objects.LLDigitalObject, eu.dnetlib.dlms.lowlevel.objects.Set)
	 */
	public Wrapper addToSet(final UnionSet unionSet, final LLDigitalObject obj, final Set setTag) {
		Wrapper unionObj = new Wrapper(obj, setTag);
		unionObj.setObjectType(unionSet.getType());
		this.addToSet(unionSet, unionObj);
		return unionObj;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.dlms.union.objects.UnionSetDAO#addToSet(eu.dnetlib.dlms.union.objects.UnionSet,
	 *      eu.dnetlib.dlms.lowlevel.objects.LLDigitalObject)
	 */
	public Wrapper addToSet(final UnionSet unionSet, final LLDigitalObject obj) {
		int numOfSets = obj.getBelongingSets().size();
		if (numOfSets == 1) {
			Wrapper unionObj = new Wrapper(obj, obj.getBelongingSets().iterator().next());
			unionObj.setObjectType(unionSet.getType());
			unionObj = this.addToSet(unionSet, unionObj);
			return unionObj;
		} else {
			this.getHibernateTemplate().evict(obj);
			throw new LowLevelException("Object " + obj.getId() + " belongs to " + numOfSets + " sets. setTag must be specified!");
		}

	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.dlms.union.objects.UnionSetDAO#addToSet(eu.dnetlib.dlms.union.objects.UnionSet,
	 *      eu.dnetlib.dlms.lowlevel.objects.Wrapper)
	 */
	public Wrapper addToSet(final UnionSet unionSet, final Wrapper unionObject) {
		if (!this.isWrapped(unionObject.getObject(), unionObject.getTagSet(), unionSet)) {
			if (this.setMembershipValidator.validate(unionObject, unionSet)) {
				this.wrapperDAO.save(unionObject);
				this.setDAO.addToSet(unionSet, unionObject);
				return unionObject;
			} else
				throw new LowLevelException("Cannot validate wrapper " + unionObject.getId() + " for UnionSet " + unionSet.getName() + ", id "
						+ unionSet.getId());
		} else {
			//wrapper already in:
			throw new LowLevelException("There's already a wrapper for object " + unionObject.getObject().getId() + " from set "
					+ unionObject.getTagSet().getName() + " in " + unionSet.getName());
		}
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.dlms.union.objects.UnionSetDAO#removeFromSet(eu.dnetlib.dlms.union.objects.UnionSet,
	 *      eu.dnetlib.dlms.lowlevel.objects.LLDigitalObject, eu.dnetlib.dlms.lowlevel.objects.Set)
	 */
	public void removeFromSet(final UnionSet unionSet, final LLDigitalObject obj, final Set setTag) {
		Wrapper w = this.getExistingWrapper(obj, setTag, unionSet);
		if (w != null) {
			//TODO: exception when trying to remove an unexisting wrapper?
			log.debug("No wrapper to removed");
		} else {
			this.removeFromSet(unionSet, w);
			log.debug("Wrapper removed from UnionSet");
		}
	}

	/**
	 * {@inheritDoc}.
	 * 
	 * @see eu.dnetlib.dlms.union.objects.UnionSetDAO#removeFromSet(eu.dnetlib.dlms.union.objects.UnionSet,
	 *      eu.dnetlib.dlms.lowlevel.objects.Wrapper)
	 */
	public void removeFromSet(final UnionSet unionSet, final Wrapper unionObject) {
		// If the passed wrapper is not transient, then call setDAO.remove
		if (unionObject.getId() != 0) {
			this.setDAO.removeFromSet(unionSet, unionObject);
		} else
			// if the passed wrapper is transient, then let's call
			// this.removeFromSet(UnionSet, LLDigitalObject, Set) so to find if
			// there exist a wrapper with the same characteristic of the given
			// one.
			this.removeFromSet(unionSet, unionObject.getObject(), unionObject.getTagSet());
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.dlms.union.objects.UnionSetDAO#getUnionSetByName(java.lang.String)
	 */
	@SuppressWarnings("unchecked")
	public UnionSet getUnionSetByName(final String name) {
		List<UnionSet> res = this.getHibernateTemplate().findByNamedParam("FROM UnionSet WHERE setType.name = :setTypeName", "setTypeName", name);
		if (res.isEmpty()) {
			return null;
		} else
			return res.get(0);
	}

	/**
	 * Checks if there exist a wrapper in unionSet for object wrappedObject coming from set tagSet.
	 * 
	 * @param wrappedObject
	 *            object in the wrappe
	 * @param tagSet
	 *            set from which wrappedo object comes from
	 * @param unionSet
	 *            UnionSet which may contain the wrapper
	 * @return true if unionSet contains a Wrapper for wrappedObject with the given tagSet; false otherwise.
	 */
	protected boolean isWrapped(final LLDigitalObject wrappedObject, final Set tagSet, final UnionSet unionSet) {
		return this.getExistingWrapper(wrappedObject, tagSet, unionSet) != null;
	}

	/**
	 * Gets a Wrapper in set unionSet that wraps a wrappedObject from the set specified in tagSet.
	 * 
	 * @param wrappedObject
	 *            LLDigitalObject wrapped
	 * @param tagSet
	 *            Set from which wrappedObject comes from
	 * @param unionSet
	 *            UnionSet which may contain the wrapper
	 * @return the Wrapper in unionSet, null if no wrapper with the specified properties is in unionSet
	 */
	protected Wrapper getExistingWrapper(final LLDigitalObject wrappedObject, final Set tagSet, final UnionSet unionSet) {
		Collection<Wrapper> existingWrappers = this.wrapperDAO.getWrappersFor(wrappedObject, tagSet);
		if (!existingWrappers.isEmpty()) {
			for (Wrapper w : existingWrappers)
				if (w.isInSet(unionSet))
					return w;
		}
		return null;
	}

}
