package eu.dnetlib.dlms.epub;

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.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.types.LowLevelType;
import eu.dnetlib.dlms.lowlevel.types.LowLevelTypeDAO;
import eu.dnetlib.dlms.lowlevel.types.RelationSetType;
import eu.dnetlib.dlms.lowlevel.types.RelationSetTypeDAO;
import eu.dnetlib.dlms.lowlevel.types.RelationType;
import eu.dnetlib.dlms.lowlevel.types.RelationTypeDAO;
import eu.dnetlib.dlms.lowlevel.types.SetType;
import eu.dnetlib.dlms.lowlevel.types.SetTypeDAO;
import eu.dnetlib.dlms.union.objects.UnionSet;
import eu.dnetlib.dlms.union.objects.UnionSetDAO;
import eu.dnetlib.dlms.union.types.UnionSetType;
import eu.dnetlib.dlms.union.types.UnionSetTypeDAO;
import eu.dnetlib.dlms.union.types.UnionType;
import eu.dnetlib.dlms.union.types.UnionTypeDAO;

/**
 * Instances of this class can be used to generate Sets. Its methods take SetToCreate instances as parameters and they
 * are expected of being not null. Admitted null value is the field info of a SetToCreate instance.
 * 
 * @author lexis
 */
public class SetGenerator {
	/**
	 * logger.
	 */
	private static final Log log = LogFactory.getLog(SetGenerator.class);

	/** DAO for Sets. */
	private SetDAO setDAO;
	/** DAO for SETType. */
	private SetTypeDAO setTypeDAO;

	/** DAO for UnionSetType. */
	private UnionSetTypeDAO unionSetTypeDAO;

	/** DAO for RelationSetType. */
	private RelationSetTypeDAO relSetTypeDAO;
	/** DAO for UnionSets. */
	private UnionSetDAO unionSetDAO;
	/** DAO for RelationSet. */
	private RelationSetDAO relSetDAO;
	/** DAO for UnionType. */
	private UnionTypeDAO unionTypeDAO;
	/** DAO for read operation on LowLevelTypes. */
	private LowLevelTypeDAO llTypeDAO;
	/** DAO for relationTypes. */
	private RelationTypeDAO relTypeDAO;

	public LowLevelTypeDAO getLlTypeDAO() {
		return this.llTypeDAO;
	}

	//********************************************GETTERS/SETTERS*********************************************************//
	@Required
	public void setLlTypeDAO(final LowLevelTypeDAO llTypeDAO) {
		this.llTypeDAO = llTypeDAO;
	}

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

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

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

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

	public SetTypeDAO getSetTypeDAO() {
		return this.setTypeDAO;
	}

	@Required
	public void setSetTypeDAO(final SetTypeDAO setTypeDAO) {
		this.setTypeDAO = setTypeDAO;
	}

	@Required
	public void setRelSetDAO(final RelationSetDAO relSetDAO) {
		this.relSetDAO = relSetDAO;
	}

	public RelationSetDAO getRelSetDAO() {
		return this.relSetDAO;
	}

	@Required
	public void setRelSetTypeDAO(final RelationSetTypeDAO relSetTypeDAO) {
		this.relSetTypeDAO = relSetTypeDAO;
	}

	public RelationSetTypeDAO getRelSetTypeDAO() {
		return this.relSetTypeDAO;
	}

	public UnionSetTypeDAO getUnionSetTypeDAO() {
		return this.unionSetTypeDAO;
	}

	@Required
	public void setUnionSetTypeDAO(final UnionSetTypeDAO unionSetTypeDao) {
		this.unionSetTypeDAO = unionSetTypeDao;
	}

	public UnionTypeDAO getUnionTypeDAO() {
		return this.unionTypeDAO;
	}

	@Required
	public void setUnionTypeDAO(final UnionTypeDAO unionTypeDAO) {
		this.unionTypeDAO = unionTypeDAO;
	}

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

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

	//*********************************************************************************************************//
	/**
	 * Generates a Set with the properties secified by the given SetToCreate instance.
	 * 
	 * @param setToCreate
	 *            SetToCreate instance that describes the Set to create and persist
	 */
	public void generateSet(final SetToCreate setToCreate) {
		LowLevelType type = this.llTypeDAO.getByName(setToCreate.getTypeName());
		this.generateSet(setToCreate.getSetName(), setToCreate.getInfo(), type);
	}

	/**
	 * Generates a Set for each of the given SetToCreate instances.
	 * 
	 * @param sets
	 *            SetToCreate instances holding info about the sets to be created.
	 */
	public void generateSets(final SetToCreate... sets) {
		log.info("GENERATING SETS...");
		for (SetToCreate toCreate : sets)
			this.generateSet(toCreate);
		log.info("SETS GENERATION ENDED...");
	}

	/**
	 * Generates the Set of type Union having the properties specified in unionSet with the sets whose names are
	 * specified in setnames.
	 * 
	 * @param unionSet
	 *            SetToCreate that descirbes the UnionSet to be created.
	 * @param includedSets
	 *            sets to be included in the unionset
	 */
	public void generateUnionSet(final SetToCreate unionSet, final SetToCreate... includedSets) {
		log.info("GENERATING UNIONSET with name " + unionSet.getSetName() + "...");
		UnionType type = this.unionTypeDAO.getByName(unionSet.getTypeName());
		if (type == null)
			throw new EPubException("Cannot generate UnionSet with name " + unionSet.getSetName() + ": UnionType with name " + unionSet.getTypeName()
					+ " does not exist!");
		//first the SetType:
		UnionSetType unionSetType = this.unionSetTypeDAO.create(type);
		unionSetType.setName(unionSet.getSetName());
		for (SetToCreate setToCreate : includedSets) {
			Set s = this.setDAO.getSetByName(setToCreate.getSetName());
			if (s == null)
				throw new EPubException("Cannot generate UnionSet with name " + unionSet.getSetName() + ": Set with name " + setToCreate.getSetName()
						+ " must be saved first");
			else {
				unionSetType.addSetType(s.getObjectType());
			}
		}
		this.unionSetTypeDAO.save(unionSetType);
		log.debug("SetType for " + unionSet.getSetName() + " of type " + type.getName() + " created");
		//now the UnionSet
		UnionSet uSet = this.unionSetDAO.create();
		uSet.setObjectType(unionSetType);
		uSet.setInfo(unionSet.getInfo());
		for (int i = 0; i < includedSets.length; i++) {
			uSet.addSet(this.setDAO.getSetByName(includedSets[i].getSetName()));
		}
		this.unionSetDAO.save(uSet);
		log.info("UNIONSET GENERATION ENDED...");
	}

	/**
	 * Generates the RelationSet accordingly to the info in the given RelationSetToCreate parameter.
	 * 
	 * @param relSetsToCreate
	 *            RelationSetToCreate instances that define all useful aspects to build the wanted RelationSets
	 */
	public void generateRelationSet(final RelationSetToCreate... relSetsToCreate) {
		log.info("GENERATING RELATIONSETS...");
		for (RelationSetToCreate relSetToCreate : relSetsToCreate) {
			log.debug("GENERATING RELATIONSET with name " + relSetToCreate.getSetName() + " and type " + relSetToCreate.getTypeName() + "...");
			RelationType rType = this.relTypeDAO.getByName(relSetToCreate.getTypeName());
			Set fstSet = this.setDAO.getSetByName(relSetToCreate.getFstSetName());
			Set sndSet = this.setDAO.getSetByName(relSetToCreate.getSndSetName());
			this.generateRelationSet(relSetToCreate.getSetName(), rType, fstSet, sndSet, relSetToCreate.getInfo());
			log.debug("RELATIONSET GENERATION ENDED...");
		}
		log.info("RELATIONSETS GENERATION ENDED...");
	}

	/**
	 * Generates a RelationSetType and a relationSet configured accordingly to the given parameters.
	 * 
	 * @param relsetName
	 *            mandatory name of the RelationSet (RelationSetType's name)
	 * @param relType
	 *            mandatory RelationType instance, type of the Set
	 * @param fst
	 *            mandatory first Set involved in relations of the Set to create
	 * @param snd
	 *            mandatory second Set involved in relations of the Set to create
	 * @param info
	 *            optional short description of the set to create
	 */
	private void generateRelationSet(final String relsetName, final RelationType relType, final Set fst, final Set snd, final String info) {
		if (relType == null)
			throw new EPubException("Cannot create relation set with name " + relsetName + " and NULL type");
		if (fst == null || snd == null)
			throw new EPubException("Cannot create relation set with name " + relsetName + ": involved sets are null or not yet persisted, fst = " + fst
					+ ", snd = " + snd);
		RelationSetType rst = this.relSetTypeDAO.create(relType);
		rst.setFstSetType(fst.getObjectType());
		rst.setSndSetType(snd.getObjectType());
		rst.setName(relsetName);
		this.relSetTypeDAO.save(rst);
		log.debug("RelationSetType for " + relsetName + " of type " + relType.getName() + " created");
		RelationSet relSet = this.relSetDAO.create();
		relSet.setObjectType(rst);
		relSet.setFstSet(fst);
		relSet.setSndSet(snd);
		relSet.setInfo(info);
		this.relSetDAO.save(relSet);
		log.debug("RelationSet created");
	}

	/**
	 * Generates a Set with the given set name and info, whose contentType will be type.
	 * 
	 * @param setName
	 *            mandatory name of the set
	 * @param info
	 *            optional info about the set
	 * @param type
	 *            mandatory contentType of the set
	 */
	private void generateSet(final String setName, final String info, final LowLevelType type) {
		log.info("GENERATING SET with name " + setName + " and type " + type.getName() + "...");
		if (setName == null || type == null)
			throw new EPubException("Cannot generate Set, one or more mandatory parameters are null: setName =  " + setName + ", type = " + type);
		//first the SetType:
		SetType setType = this.setTypeDAO.create(type);
		setType.setName(setName);
		this.setTypeDAO.save(setType);
		log.debug("SetType for " + setName + " of type " + type.getName() + " created");
		//now the set:
		Set set = this.setDAO.create();
		set.setObjectType(setType);
		set.setInfo(info);
		this.setDAO.save(set);
		log.info("SET GENERATION ENDED...");
	}

}
