package transform;

import haxe.ds.StringMap;
import model.Relation; //MergedFK
import model.*;

class MLDToDiagram {
	public static function transform (mld : MLD) : Diagram {
		var diagram = new Diagram();
		relationsToEntities(mld, diagram);
		relationsToAssociations(mld, diagram);
		return diagram;
	}

	static function relationsToEntities(mld : MLD, diagram : Diagram) {
		for (r in mld.relations) {
			if (!allForeignKeysBelongToKey(r) || //N-N association
					r.foreignKeys[0].relation.key.length == r.foreignKeys.length) { //subclass
				var e = new Set(r.name);
				diagram.sets.set(e.name, e);
				e.fields = new Array();
				for (f in r.fields) {
					var fk : ForeignKey = Lambda.find(r.foreignKeys, fk -> fk.name == f.name);
					if (fk == null) { //foreign keys are not sets properties
						e.fields.push(f);
						if (r.key.indexOf(f.name) != -1) {
							e.id.push(f.name);
						}
					}
				}
			}
		}
	}

	static function relationsToAssociations(mld : MLD, diagram : Diagram) {
		var relations : Array<Relation> = new Array();
		for (r in mld.relations) {
			relations.push(r);
		}
		while (relations.length != 0) { //aggregations requires that target association is created first
			var r : Relation = relations.pop();
			if (allForeignKeysBelongToKey(r)) {
				if (r.foreignKeys[0].relation.key.length != r.foreignKeys.length) { //N-N association
					if (Lambda.exists(r.foreignKeys, fk -> diagram.getObj(fk.relation.name) == null)) {
						relations.push(r); //at least one the target association is not created yet (aggregation)
					} else {
						addNNAssociation(diagram, r);
					}
				} else { //subclass
					var e = diagram.sets.get(r.name);
					var superset = diagram.sets.get(r.foreignKeys[0].relation.name);
					superset.subsets.push(e);
					e.superset = superset;
				}
			} else {
				for (mfk in r.mergeForeignKeys()) {
					var target : Set = diagram.getObj(mfk.relation.name);
					if (target != null) {
						addBinaryDFAssoc(diagram, r, mfk, target);
					} else {
						relations.push(r);
					}
				}
			}
		}
	}

	static function addBinaryDFAssoc(diagram : Diagram, r : Relation, mfk : MergedFK, target : Set) {
		var rName : String = r.name + mfk.relation.name;
		while (diagram.associations.get(rName) != null) rName += "1";
		var a : Association = new Association(rName);
		diagram.associations.set(a.name, a);
		var isCompound : Bool = r.key.indexOf(mfk.names[0]) != -1; //if a foreignKey belongs to key (and foreignKey != key), link is weak (compound entity)
		var source : Set = diagram.sets.get(r.name);
		a.links.push(new Link(source, "", "0", "1", isCompound, false, false, Field.ACCESS_UNSET));
		if (isCompound) {
			source.compound = cast (target, Set);
		}
		if (!isCompound && mfk.relation.name != r.name && Lambda.exists(mfk.relation.foreignKeys, fk -> r.key.indexOf(fk.name) != -1)) { //TODO: write tests
			//if target relation has a foreign key pointing to source relation, then it could be a (rare) 1-1 link
			a.links.push(new Link(target, "", "0", "1", false, false, false, Field.ACCESS_UNSET));
		} else {
			a.links.push(new Link(target, "", "0", "N", false, false, false, Field.ACCESS_UNSET));
		}
	}

	static function addNNAssociation(diagram : Diagram, r : Relation) {
		var a : Association = new Association(r.name);
		diagram.associations.set(a.name, a);
		for (mfk in r.mergeForeignKeys()) {
			a.links.push(new Link(diagram.getObj(mfk.relation.name), "", "0", "N", false, false, false, Field.ACCESS_UNSET));
		}
		for (f in r.fields) {
			if (r.key.indexOf(f.name) == -1) {
				a.fields.push(f);
			}
		}
	}

	static function allForeignKeysBelongToKey(r : Relation) : Bool { //if true, can be N-N association or subclass
		var result : Bool = false;
		if (r.key.length == r.foreignKeys.length) {
			result = true;
			var i : Int = 0;
			while (result && i < r.foreignKeys.length) {
				if (r.key.indexOf(r.foreignKeys[i].name) == -1) {
					result = false;
				}
				i++;
			}
		}
		return result;
	}
}
