import "../libraries/decent/common.eol";
import "../libraries/decent/logging.eol";
import "../libraries/arff/common.eol";

var selectedType = Native("java.lang.System").getProperty("epsilon.transformation.decent2arffx.type");
var skipSource = Native("java.lang.System").getProperty("epsilon.transformation.decent2arffx.skipSource").asBoolean();

var Type = DECENT!ArtifactType.allInstances().select(a|a.name = selectedType).first();
if (Type.isUndefined()) {
	//handle wrong arguments here ?
}

var types = new OrderedSet();
types.add("code");
//types.add("Class");
//types.add("Method");
//types.add("Function");
//types.add("Module");

//TODO: move to common, use for tagging states?
var nestedAnonymousClassFilter = "\"[\\w]+\\$[\\d]+.*\"";

var Model = DECENT!Model.allInstances().first();

var TouchCountAttribute = addAttribute("TouchCount", "Number of touches to an artifact");
var ArtifactCountAttribute = addAttribute("ArtifactCount", "Number of known artifacts of the same type");
var AuthorCountAttribute = addAttribute("AuthorCount", "Number of known authors for an artifact up to the current state");
var GlobalAuthorCountAttribute = addAttribute("GlobalAuthorCount", "Number of known authors for all artifacts up to the current state");
var FileCountAttribute = addAttribute("FileCount", "Number of existing artifacts up to the current state");

"Running preprocess".log(1);
var start = Native("java.lang.System").currentTimeMillis();

for (type in types) {
	("Processing type "+type).log(1);
	preprocessArtifacts(type);
}
updateDeltas();


var end = Native("java.lang.System").currentTimeMillis();
var duration = end - start;
("Duration: "+duration.toMinutes().round(5)).log(1);


operation updateDeltas() {
	for (a in DECENT!Artifact.allInstances()) {
	  	(""+a.name).log(3);
	  	for (s in a.states.select(x|x.isHit())) {
	  		for (v in s.values.select(v|v.isTypeOf(DoubleValue) or v.isTypeOf(IntegerValue))) {
		  		("["+a.type.name+"]"+a.name+"@"+s.ID+"::"+v.ofAttribute.name).log(4);
	  			s.fromActivity.get(0).addDelta(v.ofAttribute);
	  		}
	  	}
	}
}

operation preprocessArtifacts(typeName : String) {
	var type = DECENT!ArtifactType.allInstances().select(t|t.name = typeName).first();
	if (type.isUndefined()) {
		//TODO: check that type is set
		//handle wrong arguments here ?
		return;
	}
	
	var artifacts = Artifact.allInstances().select(x|x.type = type and not x.name.matches(nestedAnonymousClassFilter)).sortBy(x|x.name);

	var artifactDevelopers = new Set();


	for (a in artifacts) {
		var sCount = 0;
	    for (s in a.states.select(x|x.isHit()).sortBy(x|x.ID)) {
	    	("Processing ["+a.type.name+"]"+a.name+"@"+s.ID).log(4);
	    	sCount = sCount+1; 
	        s.addValue(TouchCountAttribute, sCount);
	        
	        
	        
	        
	        
	        // Count artifacts and put existing ones in filecount value
	        var existingArtifacts = artifacts.select(x|x.states.select(y|y.fromActivity.first().date.time <= s.fromActivity.first().date.time).size() > 0);
			var fileCount = existingArtifacts.size();
			s.addValue(FileCountAttribute, fileCount);
	        
	        //this refers more to the state of the project at a given point in time 
	        //thus the information will be identical in all states of artifacts with the same state.ID
	        //perhaps it should be calculated during the export rather than stored in the model
	        //(note however that it is still artifact type related) 
	        var aCount = artifacts.select(x|x.states.select(y|y.fromActivity.first().date <= s.fromActivity.first().date).size() > 0).size();
	        //(a.name+"@"+s.ID+ " -> Number of known artifacts of type "+a.type.name+": "+aCount).println();
	        s.addValue(ArtifactCountAttribute, aCount);
	        
	        //author count, could also be calculated from all the activities for an artifact up to a point 
	        artifactDevelopers.add(s.fromActivity.first().agent);
	        var dCount = artifactDevelopers.size();
	        s.addValue(AuthorCountAttribute, dCount);
	        
	        //number of all known developers up to the point
	        //NOT restricted to the type of interest!!!
	        DECENT!Agent.allInstances().select(x|x.activities.select(y|y.date <= s.fromActivity.first().date).size() > 0).size();
	        var gdCount = artifactDevelopers.size();
	        s.addValue(GlobalAuthorCountAttribute, gdCount);
	    }
	    artifactDevelopers.clear();
	}
	
	("Processing to arffx ...").log(2);
	
	var arffx = new ARFFx!Model();
	arffx.name = Model.name+"-artifacts-"+type.name;
	arffx.meta.put("DECENT.ArtifactType", type.name);
	arffx.meta.put("ARFFx.Type", "artifact");
	
	for (a in artifacts) {
		("  Artifact " +a.name).log(3);
		
		//we exclude first and last states!
		//careful with the filtering here...
		// for (s in a.states.excluding(a.states.first()).excluding(a.states.last()).select(x|x.isHit() and x.next.first().isHit()).sortBy(x|x.ID)) {
	    for (s in a.states.excluding(a.states.first()).select(x|x.isHit()).sortBy(x|x.ID)) {
			s.processTargetState(arffx);
	    }
	}
	
	//on transformation to Weka instances - either transform to ARFFx and then use Java EMF API on ARFFx 
	//or use Java EMF API on DECENT
}


operation DECENT!State processTargetState (arffx : ARFFx!Model) {
	var p = self.previous.first();
	while (p.isDefined() and not p.isHit()) {
		//TODO: what if more than one?
		p = p.previous.first();
	}
	if (p.isDefined() or skipSource){

    	var i = new ARFFx!Instance();
		arffx.data.add(i);

		i.addValue('\"'+self.artifact.name.asString()+'\"', "Artifact.Name");
		i.addValue(self.ID.asString(), "Artifact.Target.StateID");
		i.addValue('\"'+self.fromActivity.first().agent.name+'\"', "Agent.Name");
			    	
    	for (v in self.values.select(x|x.isTypeOf(DECENT!DoubleValue) or x.isTypeOf(DECENT!IntegerValue))) {
			i.addValue(v.content.asString(), "Artifact.Target."+v.name);
    	}

        var ax = self.fromActivity.first();

    	//TODO: add activity meta-data
    	for (v in ax.values.select(x|x.isTypeOf(DECENT!DoubleValue) or x.isTypeOf(DECENT!IntegerValue))) {
			i.addValue(v.content.asString(), "Activity."+v.name);
    	}

    	for (v in ax.deltas) {
			i.addValue(v.change.asString(), "Artifact.Delta."+v.onAttribute.name);
    	}

    	//TODO: these are notably missing in several places!
    	if (ax.agentState.isDefined()) {
	    	for (v in ax.agentState.values.select(x|x.isTypeOf(DECENT!DoubleValue) or x.isTypeOf(DECENT!IntegerValue))) {
				//i.addValue(v.content.asString(), "Agent."+v.name);
	    	}
    	}

		if (not skipSource) {
			i.addValue(p.ID.asString(), "Artifact.Source.StateID");
	    	
	    	for (v in p.values.select(x|x.isTypeOf(DECENT!DoubleValue) or x.isTypeOf(DECENT!IntegerValue))) {
				i.addValue(v.content.asString(), "Artifact.Source."+v.name);
	    	}
		}
	} else {
	}
}
