package eu.dnetlib.dlms.config;

import java.util.ArrayList;
import java.util.List;

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

import eu.dnetlib.dlms.lowlevel.objects.LLDigitalObject;
import eu.dnetlib.dlms.lowlevel.objects.Relation;
import eu.dnetlib.dlms.lowlevel.objects.RelationDAO;
import eu.dnetlib.dlms.lowlevel.objects.RelationSet;
import eu.dnetlib.dlms.lowlevel.objects.RelationSetDAO;
import eu.dnetlib.dlms.lowlevel.objects.Set;
import eu.dnetlib.dlms.lowlevel.objects.SetDAO;
import eu.dnetlib.dlms.lowlevel.objects.structures.Structure;
import eu.dnetlib.dlms.lowlevel.objects.structures.StructureDAO;
import eu.dnetlib.dlms.lowlevel.types.RelationType;
import eu.dnetlib.dlms.lowlevel.types.RelationTypeDAO;
import eu.dnetlib.dlms.union.objects.UnionSet;
import eu.dnetlib.dlms.union.objects.UnionSetDAO;

/**
 * Instance of this class can be use to refresh the contents of system sets, so that it is ensured they will contained
 * data that reflects the actual state.
 * 
 * @author lexis
 * 
 */
public class SystemSetsRefresher {
	/** Logger. */
	private static final Log log = LogFactory.getLog(SystemSetsRefresher.class);

	/** True to enable the refresh, false otherwise. */
	private boolean refreshEnabled;
	/** Helper class to work with system sets. */
	private SystemSetsHelper systemSetsHelper;
	/** SetDAO instance. */
	private SetDAO simpleSetDAO;
	/** Structure DAO. */
	private StructureDAO structDAO;
	private RelationSetDAO simpleRelSetDAO;
	private RelationTypeDAO relTypeDAO;
	private RelationDAO relDAO;
	private UnionSetDAO unionSetDAO;

	/**
	 * Refreshes the content in system sets. It drops the current content and recreates them from scratch.
	 */
	public void refreshSystemSetsContent() {
		/*
		 * 1. Delete all contents 2. Gets all Set and create corresponding structure in system sets using
		 * SystemSetsHelper
		 */
		log.debug("REFRESH ENABLED = " + this.refreshEnabled);
		if (this.refreshEnabled) {
			this.deleteAllContent(SystemSetsNames.STRUCT_SETS);
			this.deleteAllContent(SystemSetsNames.OBJ_SETS);
			this.deleteAllContent(SystemSetsNames.ATOM_SETS);
			this.deleteAllContent(SystemSetsNames.REL_SETS);
			this.deleteAllContent(SystemSetsNames.UNION_SETS);
			this.fillSystemSetsWithSet();
		} else {
			//TODO: no refresh
		}

	}

	/**
	 * Deletes all objects in Set systemsetName.
	 * 
	 * @param systemSetName
	 *            name of the Set to empty
	 */
	public void deleteAllContent(final String systemSetName) {
		log.debug("CLEARING System Set " + systemSetName);
		final Set systemSet = this.simpleSetDAO.getSetByName(systemSetName);
		final java.util.List<LLDigitalObject> objs = new ArrayList<LLDigitalObject>(systemSet.getObjects());
		for (int i = 0; i < objs.size(); i++) {
			final Structure structObj = (Structure) objs.get(i);
			this.simpleSetDAO.removeFromSet(systemSet, structObj);
			this.structDAO.save(structObj);
			this.simpleSetDAO.save(systemSet);
			this.structDAO.delete(structObj);
		}
		log.debug("System Set " + systemSetName + " CLEARED");
	}

	/**
	 * Fills System sets creating entries for each set currently in the system.
	 */
	public void fillSystemSetsWithSet() {
		log.debug("fillSystemSetsWithSet");
		final List<Set> availableSets = this.simpleSetDAO.list();
		log.info("#Sets to refresh: " + availableSets.size());
		for (final Set s : availableSets) {
			this.createEntryFor(s);
		}
	}

	/**
	 * Create, if it does not yet exists, an entry in the system sets for the representation of the given Set.
	 * 
	 * @param s
	 *            Set.
	 * @return the Structure representing s in system Sets
	 */
	private Structure createEntryFor(final Set s) {
		if (!this.systemSetsHelper.isSystemSet(s)) {
			log.debug("Refreshing Set " + s.getId() + ", " + s.getName());
			Structure struct = this.systemSetsHelper.findSystemStructureFor(s);
			if (struct == null) {
				struct = this.systemSetsHelper.createStructureFor(s);
				//	this.systemSetsHelper.addToAllSetsUnion(struct);
				if (s instanceof UnionSet) {
					this.updateSystemInfoFor((UnionSet) s, struct);
				} else if (s instanceof RelationSet) {
					this.updateSystemInfoFor((RelationSet) s, struct);
				}
			} else
				log.debug("System structure for set " + s.getId() + " already refreshed");
			return struct;
		}
		log.debug("System Sets no need refresh " + s.getName());
		return null;
	}

	/**
	 * Connect the given structure (that represent the given UnionSet in the system Sets) to the representation of the
	 * Sets included by the union set.
	 * 
	 * @param us
	 *            UnionSet to represent in system Sets
	 * @param unionSetStructure
	 *            Structure that represents us in SystemUnionSets
	 */
	private void updateSystemInfoFor(final UnionSet us, final Structure unionSetStructure) {
		log.debug("UNION SET REFRESHING.....");
		final RelationType includedType = this.relTypeDAO.getByName(SystemSetsNames.TYPE_INCLUDES_SET);
		final RelationSet includesSet = this.simpleRelSetDAO.getRelationSetByLabel(SystemSetsNames.INCLUDES_SET);
		for (final Set includedSet : us.getSets()) {
			final Structure inclStructure = this.createEntryFor(includedSet);
			//	final Wrapper inclWrapper = this.wrapperDAO.getWrappersFor(inclStructure).iterator().next();
			final Relation includesRel = this.relDAO.create();
			includesRel.setFstObj(unionSetStructure);
			includesRel.setSndObj(inclStructure);
			includesRel.setObjectType(includedType);
			this.relDAO.save(includesRel);
			this.simpleRelSetDAO.addToSet(includesSet, includesRel);
			this.relDAO.save(includesRel);
		}
		this.simpleRelSetDAO.save(includesSet);
		log.debug("END OF UNION SET REFRESHING.");
	}

	/**
	 * Connect the given structure (that represent the given RelationSet in the system Sets) to the representation of
	 * the first and second Sets linked by the relationship.
	 * 
	 * @param relSet
	 *            relationSet to represent in system Sets
	 * @param relSetStructure
	 *            Structure that represents relSet in SystemRelationSets
	 */
	private void updateSystemInfoFor(final RelationSet relSet, final Structure relSetStructure) {
		log.debug("RELATION SET REFRESHING.....");
		final RelationType hasFstSetType = this.relTypeDAO.getByName(SystemSetsNames.TYPE_HAS_FST_SET);
		final RelationSet hasFstRelSet = this.simpleRelSetDAO.getRelationSetByLabel(SystemSetsNames.HAS_FST_SET);

		final Structure fstSystemStruct = this.createEntryFor(relSet.getFstSet());
		//	final Wrapper fstSystemWrapper = this.wrapperDAO.getWrappersFor(fstSystemStruct).iterator().next();
		//relation:
		final Relation hasFstRel = this.relDAO.create();
		hasFstRel.setFstObj(relSetStructure);
		hasFstRel.setSndObj(fstSystemStruct);
		hasFstRel.setObjectType(hasFstSetType);
		this.relDAO.save(hasFstRel);
		this.simpleRelSetDAO.addToSet(hasFstRelSet, hasFstRel);
		this.relDAO.save(hasFstRel);
		this.simpleRelSetDAO.save(hasFstRelSet);

		//2b. adds a relation to the structure that represents snd set
		final Structure sndSystemStruct = this.createEntryFor(relSet.getSndSet());
		//final Wrapper sndSystemWrapper = this.wrapperDAO.getWrappersFor(sndSystemStruct).iterator().next();

		final RelationType hasSndSetType = this.relTypeDAO.getByName(SystemSetsNames.TYPE_HAS_SND_SET);
		final RelationSet hasSndRelSet = this.simpleRelSetDAO.getRelationSetByLabel(SystemSetsNames.HAS_SND_SET);
		//relation:
		final Relation hasSndRel = this.relDAO.create();
		hasSndRel.setFstObj(relSetStructure);
		hasSndRel.setSndObj(sndSystemStruct);
		hasSndRel.setObjectType(hasSndSetType);
		this.relDAO.save(hasSndRel);
		this.simpleRelSetDAO.addToSet(hasSndRelSet, hasSndRel);
		this.relDAO.save(hasSndRel);
		this.simpleRelSetDAO.save(hasSndRelSet);
		log.debug("END OF RELATION SET REFRESHING.");
	}

	@Required
	public void setRefreshEnabled(final boolean refreshEnabled) {
		this.refreshEnabled = refreshEnabled;
	}

	public boolean isRefreshEnabled() {
		return this.refreshEnabled;
	}

	@Required
	public void setSystemSetsHelper(final SystemSetsHelper systemSetsHelper) {
		this.systemSetsHelper = systemSetsHelper;
	}

	public SystemSetsHelper getSystemSetsHelper() {
		return this.systemSetsHelper;
	}

	public void setSimpleSetDAO(final SetDAO simpleSetDAO) {
		this.simpleSetDAO = simpleSetDAO;
	}

	public SetDAO getSimpleSetDAO() {
		return this.simpleSetDAO;
	}

	@Required
	public void setStructDAO(final StructureDAO structDAO) {
		this.structDAO = structDAO;
	}

	public StructureDAO getStructDAO() {
		return this.structDAO;
	}

	@Required
	public void setSimpleRelSetDAO(final RelationSetDAO simpleRelSetDAO) {
		this.simpleRelSetDAO = simpleRelSetDAO;
	}

	public RelationSetDAO getSimpleRelSetDAO() {
		return this.simpleRelSetDAO;
	}

	@Required
	public void setRelTypeDAO(final RelationTypeDAO relTypeDAO) {
		this.relTypeDAO = relTypeDAO;
	}

	public RelationTypeDAO getRelTypeDAO() {
		return this.relTypeDAO;
	}

	@Required
	public void setRelDAO(final RelationDAO relDAO) {
		this.relDAO = relDAO;
	}

	public RelationDAO getRelDAO() {
		return this.relDAO;
	}

	public UnionSetDAO getUnionSetDAO() {
		return this.unionSetDAO;
	}

	public void setUnionSetDAO(final UnionSetDAO unionSetDAO) {
		this.unionSetDAO = unionSetDAO;
	}

}
