<?php
/**
 * @package aowp.aspect.weaver.command.astutil
 */
/**
 * 
 * @author keiji
 * @package aowp.aspect.weaver.command.astutil
 */
class AOWP_WeavingASTHelper {
	
	private static $_RANDOM_INDEX = 0;
	
	/**
	 * 
	 * @param $joinPoint
	 * @return AOWP_PHPEqualExprElement
	 */
	public static function createJoinPointInstantiationAST(AOWP_JoinPoint $joinPoint) {
		$copiedJoinPoint = clone $joinPoint;
		$copiedJoinPoint->setAST(null);
		$unserializationFunctionElement = new AOWP_PHPFunctionCallElement('unserialize');
		$stripSlashFunctionElement = new AOWP_PHPFunctionCallElement('stripcslashes');
		$stripSlashFunctionElement->addScalarArgument(addslashes(serialize($copiedJoinPoint)));
		$unserializationFunctionElement->addArgument(new AOWP_PHPArgumentElement($stripSlashFunctionElement));
		return new AOWP_PHPEqualExprElement(AOWP_WeavingASTHelper::getRandomName('joinPoint', true), $unserializationFunctionElement);
	}
	
	public static function createIfForFunctionDeclaration(AOWP_PHPFunctionElement &$functionElement) {
		$ifElement = new AOWP_PHPIfStatementElement();
		
		// Ifの条件の定義。
		$conditionElement = new AOWP_PHPPrefixMonadicOperatorExprElement();
		$conditionElement->operatorName = '!';
		$functionExistsCallElement = new AOWP_PHPFunctionCallElement('function_exists');
		$functionExistsCallElement->addScalarArgument($functionElement->getFunctionName());
		$conditionElement->variable = $functionExistsCallElement;
		$ifElement->setCondition($conditionElement);
		
		// 関数定義の追加。 
		$ifElement->addStatement($functionElement);
		
		return $ifElement;
	}
	
	/**
	 * 
	 * @return AOWP_PHPFileIncludeStatementElement
	 */
	public static function createIncludeStatemenetElement($targetSourcePath) {
		$requireOnceStatementElement = new AOWP_PHPFileIncludeStatementElement();
		$requireOnceStatementElement->type = AOWP_PHPFileIncludeStatementElement::TYPE_REQUIRE_ONCE;
		$requireExprElement = new AOWP_PHPBinaryOperatorExprElement();
		$dirnameFunctionCallElement = new AOWP_PHPFunctionCallElement('dirname');
		$dirnameFunctionCallElement->addConstScalarArgument('__FILE__');
		$requireExprElement->setLeftExpr($dirnameFunctionCallElement);
		$requireExprElement->addScalarRightExpr('DIRECTORY_SEPARATOR', AOWP_PHPBinaryOperatorExprElement::DOT);
		$requireExprElement->addScalarRightExpr("'" . AOWP_ConfigurationManager::getRuntimeIncludePath($targetSourcePath) . "'", AOWP_PHPBinaryOperatorExprElement::DOT);
		$requireOnceStatementElement->expr = $requireExprElement;
		return new AOWP_PHPStatementElement($requireOnceStatementElement);
	}
	
	/**
	 * 
	 * @param string $aspectName
	 * @return AOWP_PHPEqualExprElement
	 */
	public static function createAspectInstantiationAST($aspectName) {
		$aspectInstantiationElement = new AOWP_PHPStaticMethodCallElement('AOWP_AspectInstanceManager', 'getInstance');
		$aspectInstantiationElement->addScalarArgument($aspectName);
		return new AOWP_PHPEqualExprElement(AOWP_WeavingASTHelper::getRandomName('aspect', true), $aspectInstantiationElement);
	}
	
	/**
	 * 
	 * @param string $aspectName
	 * @return AOWP_PHPStaticMethodCallElement
	 */
	public static function createAspectInstanceReleaseAST($aspectInstanceName) {
		$aspectInstanceReleaseElement = new AOWP_PHPStaticMethodCallElement('AOWP_AspectInstanceManager', 'releaseInstance');
		$aspectInstanceReleaseElement->addVariableArgument($aspectInstanceName);
		return $aspectInstanceReleaseElement;
	}
	
	/**
	 * 
	 * @param stirng $aspectVariableName
	 * @param int $adviceIndex
	 * @param string $contextVariableName
	 * @return {@link AOWP_PHPIfStatementElement}
	 */
	public static function createAdviceExecutionAST($aspectVariableName, $adviceIndex, $contextVariableName) {
		$adviceExecutionIfStatement = new AOWP_PHPIfStatementElement();
		$runtimeMatchMethodCall = new AOWP_PHPSimpleMethodCallElement($aspectVariableName, 'runtimeMatch');
		$runtimeMatchMethodCall->addArgument(AOWP_PHPArgumentElement::createStringArgument($adviceIndex));
		$runtimeMatchMethodCall->addArgument(AOWP_PHPArgumentElement::createVariableArgument($contextVariableName));
		$adviceExecutionIfStatement->setCondition($runtimeMatchMethodCall);
		$adviceExecutionElement = new AOWP_PHPSimpleMethodCallElement($aspectVariableName, 'executeAdvice');
		$adviceExecutionElement->addArgument(AOWP_PHPArgumentElement::createStringArgument($adviceIndex));
		$adviceExecutionElement->addArgument(AOWP_PHPArgumentElement::createVariableArgument($contextVariableName));
		$adviceExecutionIfStatement->addStatement(new AOWP_PHPStatementElement($adviceExecutionElement));
		$adviceExecutionIfStatement->addStatement(new AOWP_PHPStatementElement(AOWP_WeavingASTHelper::createAspectInstanceReleaseAST($aspectVariableName)));
		$adviceExecutionIfStatement->addElseStatement(new AOWP_PHPStatementElement(AOWP_WeavingASTHelper::createAspectInstanceReleaseAST($aspectVariableName)));
		return $adviceExecutionIfStatement;
	}
	
	/**
	 * 
	 * @param string $aspectVariableName
	 * @param int $adviceIndex
	 * @param string $contextVariableName
	 * @param $originalExecutionElement
	 * @return {@link AOWP_PHPIfStatementElement}
	 */
	public static function createAdviceExecutionASTForAround($aspectVariableName, $adviceIndex, $contextVariableName, AOWP_PHPElement $originalExecutionElement) {
		// アドバイスの実行時評価の為のAST。
		$adviceExecutionIfStatement = new AOWP_PHPIfStatementElement();
		$runtimeMatchMethodCall = new AOWP_PHPSimpleMethodCallElement($aspectVariableName, 'runtimeMatch');
		$runtimeMatchMethodCall->addArgument(AOWP_PHPArgumentElement::createStringArgument($adviceIndex));
		$runtimeMatchMethodCall->addArgument(AOWP_PHPArgumentElement::createVariableArgument($contextVariableName));
		$adviceExecutionIfStatement->setCondition($runtimeMatchMethodCall);
		// アドバイスの実行結果を変数に代入するAST。
		$adviceExecutionElement = new AOWP_PHPSimpleMethodCallElement($aspectVariableName, 'executeAdvice');
		$adviceExecutionElement->addArgument(AOWP_PHPArgumentElement::createStringArgument($adviceIndex));
		$adviceExecutionElement->addArgument(AOWP_PHPArgumentElement::createVariableArgument($contextVariableName));
		$adviceExecutionResultElement = new AOWP_PHPEqualExprElement(AOWP_WeavingASTHelper::getRandomName('result', true), $adviceExecutionElement);
		$adviceExecutionIfStatement->addStatement(new AOWP_PHPStatementElement($adviceExecutionResultElement));
		// アスペクトのインスタンスのリリースを行うAST。
		$adviceExecutionIfStatement->addStatement(new AOWP_PHPStatementElement(AOWP_WeavingASTHelper::createAspectInstanceReleaseAST($aspectVariableName)));
		// アドバイスの実行結果をreturnするAST。
		$adviceExecutionReturnElement = new AOWP_PHPReturnStatementElement();
		$adviceExecutionReturnElement->setVariableExpr($adviceExecutionResultElement->getLeftVarialeName());
		$adviceExecutionIfStatement->addStatement($adviceExecutionReturnElement);
		// 元のコードを実行する為のAST。
		$originalExecutionResultElement = new AOWP_PHPEqualExprElement(AOWP_WeavingASTHelper::getRandomName('result', true), $originalExecutionElement);
		$adviceExecutionIfStatement->addElseStatement(new AOWP_PHPStatementElement($originalExecutionResultElement));
		$adviceExecutionIfStatement->addElseStatement(new AOWP_PHPStatementElement(AOWP_WeavingASTHelper::createAspectInstanceReleaseAST($aspectVariableName)));
		$originalExecutionReturnElement = new AOWP_PHPReturnStatementElement();
		$originalExecutionReturnElement->setVariableExpr($originalExecutionResultElement->getLeftVarialeName());
		$adviceExecutionIfStatement->addElseStatement($originalExecutionReturnElement);		
		return $adviceExecutionIfStatement;
	}
	
	public static function getRandomName($prefix = 'name', $isVariableName = false) {
		return ($isVariableName ? '$' : '') . $prefix . time() . AOWP_WeavingASTHelper::$_RANDOM_INDEX++;
	}
	
	/**
	 * {@link AOWP_IPHPContainerElement}を実装するAST要素を、
	 * 親から探します。
	 * 返り値は、0番目が{@link AOWP_IPHPContainerElement}を実装するAST要素、
	 * 1番目がその直前の子のAST要素です。
	 * 
	 * @param $element
	 * @return array
	 */
	public static function getContainerParent(AOWP_PHPElement $element, $checkError = false) {
		$parentElement = null;
		while (($parentElement = $element->getParent()) !== null) {
			if ($parentElement instanceof AOWP_IPHPContainerElement) {
				return array($parentElement, $element);
			}
			else {
				$element = $parentElement;
			}
		}
		if ($checkError) {
			echo "The weaver can not find a container element.\n";
			exit();
		}
		else {
			return null;
		}
	}

	/**
	 * Add array of elements ($addedElement) before $element.
	 * 
	 * @param array $addedElement
	 * @param AOWP_PHPElement $element
	 */
	public static function insertElementsIntoParentContainer(array $addedElement, AOWP_PHPElement $element) {
		$parentElement = null;
		while (($parentElement = $element->getParent()) !== null) {
			if ($parentElement instanceof AOWP_IPHPContainerElement) {
				$parentElement->insertElementArray($addedElement, $element);
				break;
			}
			else {
				$element = $parentElement;
			}
		}
	}
	
	/**
	 * このメソッドは、連続したメソッド呼び出し、フィールド参照を、
	 * 単純なメソッド呼び出し、フィールド参照からなる、複数の記述文に展開します。
	 * 例えば、
	 * <code>
	 * $a->b()->c()->d();
	 * </code>
	 * は、
	 * <code>
	 * $var1 = $a->b();
	 * $var2 = $var1->c();
	 * $var3 = $var2->d();
	 * </code>
	 * に展開します。 
	 * 
	 * @param $objectPropertyElement
	 * @return AOWP_PHPObjectPropertyElement
	 */
	public static function apartMethodCallElement(AOWP_PHPObjectPropertyElement $objectPropertyElement) {
		$objectOperatorElement = $objectPropertyElement->getParent();
		$newObjectPropertyElement = null;
		$objectPropertyIndex = $objectPropertyElement->getObjectPropertyIndex();
		
		// メソッド呼び出し (フィールド参照) を、分解する。
		$apartedMethodCallArray = array();
		$simpleMethodCallOrFieldAccess = null;
		$objectName = $objectOperatorElement->getLeftVariableName();
		$nowObjectPropertyIndex = 0;
		foreach ($objectOperatorElement->objectProperties as $objectProperty) {
			if ($simpleMethodCallOrFieldAccess !== null) {
				$equalExprElement = new AOWP_PHPEqualExprElement(AOWP_WeavingASTHelper::getRandomName('variable', true), $simpleMethodCallOrFieldAccess);
				$objectName = $equalExprElement->getLeftVarialeName();
				$apartedMethodCallArray[] = new AOWP_PHPStatementElement($equalExprElement);
			}
			if ($objectProperty->isMethodCall()) {
				$simpleMethodCallOrFieldAccess = new AOWP_PHPSimpleMethodCallElement($objectName, $objectProperty->getPropertyName());
				foreach ($objectProperty->arguments as $argumentElement) {
					$simpleMethodCallOrFieldAccess->addArgument($argumentElement);
				}
			}
			else {
				$simpleMethodCallOrFieldAccess = new AOWP_PHPSimpleFieldAccessElement($objectName, $objectProperty->getPropertyName());
			}
			if ($objectPropertyIndex === $nowObjectPropertyIndex++) {
				$newObjectPropertyElement = $simpleMethodCallOrFieldAccess->getFirstObjectProperty();
			}
		}
		$equalExprElement = new AOWP_PHPEqualExprElement(AOWP_WeavingASTHelper::getRandomName('variable', true), $simpleMethodCallOrFieldAccess);
		$lastStatementVaribaleName = $equalExprElement->getLeftVarialeName();
		$apartedMethodCallArray[] = new AOWP_PHPStatementElement($equalExprElement);
		
		// 分解した文を、元のコードに追加し、変換対象のメソッド呼び出し等を、分解した文の最後の変数に置き換える。
		$containerElementResult = AOWP_WeavingASTHelper::getContainerParent($objectOperatorElement, true);
		$containerElement = $containerElementResult[0];
		$parentElementNearContainer = $containerElementResult[1];
		$containerElement->insertElementArray($apartedMethodCallArray, $parentElementNearContainer);
		$parentElement = $objectOperatorElement->getParent();
		$parentElement->{$objectOperatorElement->getParentPropertyName()} = new AOWP_PHPVariableElement($lastStatementVaribaleName);
		$parentElement->{$objectOperatorElement->getParentPropertyName()}->setParent($parentElement);
		
		return $newObjectPropertyElement;
	}
	
	/**
	 * 
	 * @param $targetElement
	 * @param $insertedElement
	 * @return void
	 */
	public static function insertElement(AOWP_PHPElement &$targetElement, AOWP_PHPElement &$insertedElement) {
		// ラッピング関数を、元のコードに追加。
		$containerElementResult = AOWP_WeavingASTHelper::getContainerParent($targetElement->getParent(), true);
		$parentContainer = $containerElementResult[0];
		$parentElement = $containerElementResult[1];
		AOWP_Logger::logging('WeavingASTHelper: ' . get_class($parentContainer));
		$parentContainer->insertElement($insertedElement, $parentElement);
	}
	
	public static function getVariableDefinitions(AOWP_PHPElement &$targetElement) {
		$variableDefinitionElements = array();
		foreach ($targetElement->getChildren() as $childElement) {
			if (!($childElement instanceof AOWP_PHPFunctionElement ||
				$childElement instanceof AOWP_PHPClassElement)) {
				$variableDefinitionElements = array_merge($variableDefinitionElements, AOWP_WeavingASTHelper::getVariableDefinitions($childElement));
			}
		}
		if ($targetElement instanceof AOWP_PHPVariableElement) {
			$variableDefinitionElements[] = $targetElement;
		}
		return $variableDefinitionElements;
	}
	
	/**
	 * For weaving for around advice targetting script execution join points.
	 * 
	 * @param array $originalElements
	 * @param unknown_type $proceedLabel
	 * @param unknown_type $flagVariableName
	 * @param array $proceedReturnLabels
	 */
	public static function convertOriginalElementsForScriptExecutionAroundAdvice(array $originalElements, $proceedLabel, $flagVariableName, array $proceedReturnLabels, $proceedEndLabel) {
		$proceedBodyElements = array();
		// Labeling for call of proceed.
		$proceedBodyElements[] = new AOWP_PHPLabelElement($proceedLabel);
		// Set in proceed execution.
		$proceedInIfStatement = new AOWP_PHPIfStatementElement();
		$proceedInIfConditionFunctionCall = new AOWP_PHPFunctionCallElement('isset');
		$proceedInIfConditionFunctionCall->addVariableArgument($flagVariableName);
		$proceedInIfStatement->setCondition($proceedInIfConditionFunctionCall);
		$proceedInIfStatement->setIfStatements(array(new AOWP_PHPStatementElement(new AOWP_PHPStaticMethodCallElement('AOWP_RequestAroundManageAspect', 'inRequestProceed'))));
		$proceedBodyElements[] = $proceedInIfStatement;

		// Divide original elements into procedual elements and definition elements.
		$procedualElements = array();
		$definitionElements = array();
		foreach ($originalElements as $element) {
			if ($element instanceof AOWP_PHPClassElement || $element instanceof AOWP_PHPFunctionElement) {
				$definitionElements[] = $element;
			}
			else {
				$procedualElements[] = $element;
			}
		}
		$originalElements = null;
		
		// Elements for original program execution.
		// Add procedual elements.
		$tryElement = new AOWP_PHPTryCatchStatementElement();
		$tryInnerElements = array_merge(array(AOWP_PHPInnerHTMLStatementElement::createPHPCloseTag()), $procedualElements);
		$tryInnerElements[] = AOWP_PHPInnerHTMLStatementElement::createPHPOpenTag();
		$tryElement->setElements($tryInnerElements);
		// For proceed of retuest join point's proceed.
		$tryElement->addCatchStatement('AOWP_RequestAroundEndException', '$exception', array());
		$proceedBodyElements[] = $tryElement;
		// Add definition elements.
		foreach ($definitionElements as $definitionElement) {
			$proceedBodyElements[] = $definitionElement;
		}
//		$proceedBodyElements = array_merge($proceedBodyElements, $definitionElements);
		
		// If element for checking flag for proceed return is defined.
		$ifFlagDefinitionElement = new AOWP_PHPIfStatementElement();
		$issetFunctionElement = new AOWP_PHPFunctionCallElement('isset');
		$issetFunctionElement->addVariableArgument($flagVariableName);
		$ifFlagDefinitionElement->setCondition($issetFunctionElement);
		// Switch statement for returning into advice at end of proceed.
		$switchElement = new AOWP_PHPSwitchStatementElement();
		$switchElement->setVariableExpression($flagVariableName);
		foreach ($proceedReturnLabels as $proceedReturnLabel) {
			$caseElement = new AOWP_PHPCaseStatementElement();
			$caseElement->setScalarExpression("'" . $proceedReturnLabel . "'");
			$caseElement->setElement(new AOWP_PHPStatementElement(new AOWP_PHPStaticMethodCallElement('AOWP_RequestAroundManageAspect', 'outRequestProceed')));
			$caseElement->setElement(new AOWP_PHPStatementElement(new AOWP_PHPGotoElement($proceedReturnLabel)));
			$caseElement->setElement(new AOWP_PHPBreakStatementElement());
			$switchElement->addCaseStatement($caseElement);
		}
		$ifFlagDefinitionElement->setIfStatements(array($switchElement));
		$proceedBodyElements[] = $ifFlagDefinitionElement;
		
		// Add a label which indicates the end of original execution.
		$proceedBodyElements[] = new AOWP_PHPLabelElement($proceedEndLabel);
		
		return $proceedBodyElements;
	}
	
}
?>