/**
 * 
 */
package eu.dnetlib.dlms.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import eu.dnetlib.dlms.lowlevel.objects.Set;
import eu.dnetlib.dlms.lowlevel.objects.structures.IntBasicValue;
import eu.dnetlib.dlms.lowlevel.objects.structures.StringBasicValue;
import eu.dnetlib.dlms.lowlevel.types.LowLevelType;
import eu.dnetlib.dlms.lowlevel.types.RelationMultiplicity;
import eu.dnetlib.dlms.lowlevel.types.RelationPartiality;
import eu.dnetlib.dlms.lowlevel.types.RelationSetType;
import eu.dnetlib.dlms.lowlevel.types.RelationType;
import eu.dnetlib.dlms.lowlevel.types.SetType;
import eu.dnetlib.dlms.lowlevel.types.structures.BasicDescriptionType;
import eu.dnetlib.dlms.lowlevel.types.structures.BasicType;
import eu.dnetlib.dlms.lowlevel.types.structures.CollectionDescriptionType;
import eu.dnetlib.dlms.lowlevel.types.structures.LabelType;
import eu.dnetlib.dlms.lowlevel.types.structures.StructureType;
import eu.dnetlib.dlms.union.objects.UnionSet;
import eu.dnetlib.dlms.union.types.UnionDorotyOperation;
import eu.dnetlib.dlms.union.types.UnionSetType;
import eu.dnetlib.dlms.union.types.UnionType;

/**
 * Class that generates System Sets.
 * 
 * @author lexis
 * 
 */
public class SystemGeneration extends UnionDorotyOperation {
	/** System Sets: one for each kind. */
	private Set systemObjSet, systemAtomSet, systemStructSet, systemUnionSet, systemRelSet;
	/** System Set which is a UnionSet including the previous System Sets. */
	private UnionSet systemAllSets;
	/** Types to be included by systemAllSets.type. */
	private Collection<LowLevelType> includedTypes = new ArrayList<LowLevelType>();
	/** SetTypes to be included in systemAllSets.setType. */
	private Collection<SetType> includedSetTypes = new ArrayList<SetType>();
	/** Sets to be included in systemAllSets. */
	private Collection<Set> includedSets = new ArrayList<Set>();

	/**
	 * {@inheritDoc}
	 * 
	 * @see eu.dnetlib.dlms.impl.ops.DorotyOperation#execute()
	 */
	@Override
	public void execute() {

		this.generateAtomSets();
		this.generateObjSets();
		this.generateRelSets();
		this.generateStructSets();
		this.generateUnionSets();
		this.generateUnionOfSets();
		this.generateSystemRelations();

	}

	/**
	 * Create and save Set with name SystemObjSets. It will contain structures as {setID:int; name:string} to represent
	 * Sets of kind obj.
	 */
	public void generateObjSets() {
		/*
		 * Set SystemObjSets = create struct({id:long;name:string})
		 */
		final Map<String, LabelType> descrMap = new HashMap<String, LabelType>();
		descrMap.put("setID", new BasicType(IntBasicValue.class));
		descrMap.put("name", new BasicType(StringBasicValue.class));
		final StructureType type = this.createAndSaveStructureType(SystemSetsNames.TYPE_OBJ_SETS, descrMap);
		this.includedTypes.add(type);
		final SetType setType = this.createAndSaveSetType(type, SystemSetsNames.OBJ_SETS);
		this.includedSetTypes.add(setType);
		this.systemObjSet = this.createAndsaveSet(setType, "System Set for Sets of kind obj");
		this.includedSets.add(this.systemObjSet);
	}

	/**
	 * Create and save Set with name SystemAtomSets. It will contain structures as {setID:int; name:string; mime:string}
	 * to represent Sets of kind atom.
	 */
	public void generateAtomSets() {
		/*
		 * Set SystemAtomSets = create struct({id:long;name:string;mime:string})
		 */
		final Map<String, LabelType> descrMap = new HashMap<String, LabelType>();
		descrMap.put("setID", new BasicType(IntBasicValue.class));
		descrMap.put("name", new BasicType(StringBasicValue.class));
		descrMap.put("mime", new BasicType(StringBasicValue.class));
		final StructureType type = this.createAndSaveStructureType(SystemSetsNames.TYPE_ATOM_SETS, descrMap);
		this.includedTypes.add(type);
		final SetType setType = this.createAndSaveSetType(type, SystemSetsNames.ATOM_SETS);
		this.includedSetTypes.add(setType);
		this.systemAtomSet = this.createAndsaveSet(setType, "System Set for Sets of kind atom");
		this.includedSets.add(this.systemAtomSet);
	}

	/**
	 * Create and save Set with name SystemStructSets. It will contain structures as {setID:int; name:string;
	 * fields:coll({label:string;type:string})} to represent Sets of kind structure.
	 */
	public void generateStructSets() {
		/*
		 * Set SystemStructSets = create struct({id:long;name:str;fields:{label:string;type:string;};})
		 */
		final Map<String, LabelType> fieldsMap = new HashMap<String, LabelType>();
		fieldsMap.put("label", new BasicType(StringBasicValue.class));
		fieldsMap.put("type", new BasicType(StringBasicValue.class));
		final BasicDescriptionType fieldsType = new BasicDescriptionType();
		fieldsType.setDescriptionDefinitionMap(fieldsMap);
		final Map<String, LabelType> descrMap = new HashMap<String, LabelType>();
		descrMap.put("setID", new BasicType(IntBasicValue.class));
		descrMap.put("name", new BasicType(StringBasicValue.class));
		descrMap.put("fields", new CollectionDescriptionType(fieldsType));
		final StructureType type = this.createAndSaveStructureType(SystemSetsNames.TYPE_STRUCT_SETS, descrMap);
		this.includedTypes.add(type);
		final SetType setType = this.createAndSaveSetType(type, SystemSetsNames.STRUCT_SETS);
		this.includedSetTypes.add(setType);
		this.systemStructSet = this.createAndsaveSet(setType, "System Set for Sets of kind struct");
		this.includedSets.add(this.systemStructSet);
	}

	/**
	 * Create and save Set with name SystemUnionSets. It will contain structures as {setID:int; name:string} to
	 * represent Sets of kind union. Instances in the Set will have a relation SystemIncludedSets with an object in
	 * SystemSets for each Set included in the UnionSet they represent.
	 */
	public void generateUnionSets() {
		/*
		 * Set SystemUnionSets = create struct({id:long;name:str;})
		 */
		final Map<String, LabelType> descrMap = new HashMap<String, LabelType>();
		descrMap.put("setID", new BasicType(IntBasicValue.class));
		descrMap.put("name", new BasicType(StringBasicValue.class));
		final StructureType type = this.createAndSaveStructureType(SystemSetsNames.TYPE_UNION_SETS, descrMap);
		this.includedTypes.add(type);
		final SetType setType = this.createAndSaveSetType(type, SystemSetsNames.UNION_SETS);
		this.includedSetTypes.add(setType);
		this.systemUnionSet = this.createAndsaveSet(setType, "System Set for Sets of kind union");
		this.includedSets.add(this.systemUnionSet);
	}

	/**
	 * Create and save Set with name SystemRelSets. It will contain structures as {setID:int; name:string; mult:string;
	 * part:string} to represent Sets of kind relation. SystemRelSets is the first Set for SystemHasFirstSet and
	 * SystemHasSecondset relation Set. Target of both Relation Sets is SystemSets.
	 */
	public void generateRelSets() {
		/*
		 * Set SystemRelSets = create struct({id:long;name:str;mult:string;part:string})
		 */
		final Map<String, LabelType> descrMap = new HashMap<String, LabelType>();
		descrMap.put("setID", new BasicType(IntBasicValue.class));
		descrMap.put("name", new BasicType(StringBasicValue.class));
		descrMap.put("mult", new BasicType(StringBasicValue.class));
		descrMap.put("part", new BasicType(StringBasicValue.class));
		final StructureType type = this.createAndSaveStructureType(SystemSetsNames.TYPE_REL_SETS, descrMap);
		this.includedTypes.add(type);
		final SetType setType = this.createAndSaveSetType(type, SystemSetsNames.REL_SETS);
		this.includedSetTypes.add(setType);
		this.systemRelSet = this.createAndsaveSet(setType, "System Set for Sets of kind rel");
		this.includedSets.add(this.systemRelSet);
	}

	/**
	 * Creates and saves the UnionSet of all System Sets of kind structure. Its name is SystemSets.
	 */
	public void generateUnionOfSets() {
		/*
		 * Set SystemSets = create union(SystemObjSets, SystemAtomSets, SystemStructSets, SystemUnionSets,
		 * SystemRelSets)
		 */

		final UnionType type = this.createAndsaveUnionType(SystemSetsNames.TYPE_ALL_SETS, this.includedTypes);
		final UnionSetType setType = this.createAndSaveUnionSetType(type, SystemSetsNames.ALL_SETS, this.includedSetTypes);
		this.systemAllSets = this.createAndSaveUnionSet(setType, "Union Set for all System Sets", this.includedSets);
	}

	/**
	 * Creates and saves System Relation Sets: SystemIncludedSets: SystemUnionSets --> SystemSets (which sets are
	 * included by a Union Set); SystemHasFirstSet: SystemRelSets --> SystemSets (which is the first Set of a Relation
	 * Set); SystemHasSecondSet: SystemRelSets --> SystemSets (which is the second Set of a Relation Set).
	 */
	public void generateSystemRelations() {
		/*
		 * 1. SystemIncludedSets is the relation between unionsets and the sets included. 2. SystemHasFirstSet is the
		 * relation between a Relation set and its first set. 3. SystemHasSecondSet is the relation between a Relation
		 * set and its second set.
		 */
		final RelationType includedSetsType = this.createAndSaveRelType(SystemSetsNames.TYPE_INCLUDES_SET, this.systemUnionSet.getType(),
				this.systemAllSets.getType(), RelationPartiality.tot_part, RelationMultiplicity.many_to_many);
		final RelationSetType includedSetsSetType = this.createAndSaveRelSetType(includedSetsType, SystemSetsNames.INCLUDES_SET, this.systemUnionSet
				.getObjectType(), this.systemAllSets.getObjectType());
		this.createAndSaveRelationSet(includedSetsSetType, "Relation between the representation of a union set and its included Sets",
				this.systemUnionSet, this.systemAllSets);

		final RelationType firstType = this.createAndSaveRelType(SystemSetsNames.TYPE_HAS_FST_SET, this.systemRelSet.getType(), this.systemAllSets
				.getType(), RelationPartiality.tot_part, RelationMultiplicity.many_to_one);
		final RelationSetType firstTypeSetType = this.createAndSaveRelSetType(firstType, SystemSetsNames.HAS_FST_SET, this.systemRelSet.getObjectType(),
				this.systemAllSets.getObjectType());
		this.createAndSaveRelationSet(firstTypeSetType, "Relation between the representation of a relation set and its first Set", this.systemRelSet,
				this.systemAllSets);

		final RelationType sndType = this.createAndSaveRelType(SystemSetsNames.TYPE_HAS_SND_SET, this.systemRelSet.getType(), this.systemAllSets
				.getType(), RelationPartiality.tot_part, RelationMultiplicity.many_to_many);
		final RelationSetType sndTypeSetType = this.createAndSaveRelSetType(sndType, SystemSetsNames.HAS_SND_SET, this.systemRelSet.getObjectType(),
				this.systemAllSets.getObjectType());
		this.createAndSaveRelationSet(sndTypeSetType, "Relation between the representation of a relation set and its second Sets", this.systemRelSet,
				this.systemAllSets);
	}

}
