/**
 * transformation AspectTransformation
 * Implements MOFScript Aspects
 *
 */

texttransformation AspectTransformation (in baseModel:"http://org.sintef.mofscript.model", in aspectModel:"http://org.sintef.mofscript.model") {

  var theAspectModel:aspectModel.MOFScriptAspect
  var theBaseTransformation:baseModel.MOFScriptTransformation
  var adviceMap: Hashtable
  var outputModelRoot:String = "C:/Working/MOFScript/MOFScript Test/Aspects/"
  var outputModelName:String = "outputModel.mofscript"
  var outputEnvDir:String = ""
  
  main(){
    theBaseTransformation = baseModel.objectsOfType(baseModel.MOFScriptTransformation).first()
    theAspectModel = aspectModel.objectsOfType(aspectModel.MOFScriptAspect).first()    
    baseModel.objectsOfType(baseModel.MOFScriptTransformation)->forEach (tr:baseModel.MOFScriptTransformation) {      
        tr.matchTransformation ()
    }   
    //
    // Find CALL pointcuts
    //
    theAspectModel.matchCallingPointCuts()
    
    //
    // Map the aspect transformation rules, if any
    //    
    theAspectModel.mapAspectRules ()
     
    //
    // Store the new model
    //
    outputEnvDir = getenv ("MOSCRIPTASPECT_DIR")
    if (outputEnvDir != "")
    	outputModelRoot = outputEnvDir
    	
    outputModelName = theBaseTransformation.name + ".aspectized.mofscript"
    baseModel.store(outputModelRoot + "/" + outputModelName) // Stores a copy of the baseModel
  }
  
  /**
   *
   *
   */
  baseModel.MOFScriptTransformation::matchTransformation () {   
    self.transformationrules->forEach(r:aspectModel.TransformationRule)
      r.matchTransformationRule()          
  }
  
  baseModel.TransformationRule::matchTransformationRule () {
    var a:List   
     theAspectModel.getMatchingPointCut(self)        
  }
  
  aspectModel.MOFScriptAspect::getMatchingPointCut (rule:baseModel.TransformationRule) {   
    property ruleName:String = rule.name
    property ruleType:String = rule.context.type
    var hasTypeMatch:Boolean = true
    //
    // Looking for matching pointcuts
    //
    
    
    self._getFeature("pointcut")->forEach(pc:aspectModel.PointCut) {
      
      //
      // EXECUTES
      //
      if (pc.pointcutexpression.operator = "EXECUTE") {      
	      if (ruleName.matches (pc.pointcutexpression.expressionString)) {
	        // assume that there is a type match as well
	        hasTypeMatch = true        
	        '
	        RuleName: ' ruleName ' matches pointcut ' pc.name ' with exp='pc.pointcutexpression.expressionString '
	        '        
	        //
	        // also check the type of the pointcut
	        // if there is a type specified, the pointcut only matches rules with this context type
	        //
	        if (pc.typeMatch != "") {
	          if (!(ruleType.substringAfter(".").equals(pc.typeMatch))) {
	            hasTypeMatch = false
	          } else {
	            '
	        Matches on type: ' pc.typeMatch '
	            '
	          }
	        }
	        if (hasTypeMatch)
		        self.matchAdvices(pc, rule)                
	      }	      	      
      } // end executes     
      
      //
      //
      //
    }
  }
  
  /**
   *
   * Calling point cuts (CALL)
   */
  aspectModel.MOFScriptAspect::matchCallingPointCuts () {
    var ruleCallList:List      
	self._getFeature("pointcut")->forEach(pc:aspectModel.PointCut | pc.pointcutexpression.operator = "CALL") {
	    var ruleCallName:String = ""
	    //
	    // Look at all the statements to find a call that matches the pointcut
	    //
	    ruleCallList = baseModel.objectsOfType (baseModel.FunctionCallStatement)
	    ruleCallList->forEach(ruleCall:baseModel.FunctionCallStatement) {
	      ruleCallName = ruleCall.function.name.substringAfter(".")
	      if (ruleCallName.matches (pc.pointcutexpression.expressionString)) {
	        '
	        	Rule Call matches pointcut: ' ruleCallName ' - pointcut: ' pc.name '
	        '
//	        ruleCall.owner.matchAdvicesForCall
			self.matchAdvicesForCallPointCut(pc, ruleCall)
	      }
	    }
	} 
  }
  
  
  /**
   * matchAdvices
   */
  aspectModel.PointCut::test() {
    self.name ''
  }
  aspectModel.MOFScriptAspect::matchAdvices (pcut:aspectModel.PointCut, rule:baseModel.TransformationRule) {
    ' Looking for advices: '
    self.advice->forEach(advice:aspectModel.Advice) {
      if (advice.pointCutRef.equalsIgnoreCase(pcut.name)) {
        //
        // This advice matches. Now apply the advice on the matched rule
        //
        print (advice.operator + " " + advice.pointCutRef + ", ")
        if (advice.operator = "BEFORE") {
          rule.statements.addAllFirst(advice.statements)
        } else if (advice.operator = "AFTER") {
           rule.statements.addAll(advice.statements)
        } else if (advice.operator = "AROUND") {
          rule.statements.clear()
          rule.statements.addAll(advice.statements)
        } else {
          
          // NOOP
        }
      }
    }
    newline
  }
    
  /////////////////////////////////////////////////////
  /**
   * matchAdvicesForCall
   */
   aspectModel.MOFScriptAspect::matchAdvicesForCallPointCut (pcut:aspectModel.PointCut, ruleCall:baseModel.FunctionCallStatement) {
     '  Looking for advices for CALL pointcut: '
     self.advice->forEach(advice:aspectModel.Advice) {
      if (advice.pointCutRef.equalsIgnoreCase(pcut.name)) {
        print (advice.operator + " " + advice.pointCutRef + ", ")
        if (advice.operator = "BEFORE") {
          ruleCall.owner.statements.addBefore (ruleCall, advice.statements)
        } else if (advice.operator = "AFTER") {
          ruleCall.owner.statements.addAfter (ruleCall, advice.statements)
        } else if (advice.operator = "AROUND") {
          ruleCall.owner.statements.addAfter(ruleCall, advice.statements)
          ruleCall.owner.statements.remove(ruleCall)
        } else {
        }
      }       
     }
     newline
   }
   
   
   /**
    * map the rules of the aspect into the target
    */
   aspectModel.MOFScriptAspect::mapAspectRules () {
     theBaseTransformation.transformationrules.addAll (self.transformationrules)
   }
  
}