<?php
/**
 * @copyright  2013 Roberto Perpuly
 *
 * This source may not be redistributed without prior consent.  Access to the 
 * source does not imply that you have consent to redistribute either this source
 * code or the generated binary. See LICENSE file for details.
 */

/**
 * This table loads the call stack; the statements that are executed
 * by a single controller / action combination. The call stack
 * is generated by Triumph when the user wants to see template 
 * variables; this class will load the call stack into memory so that
 * we can investigate the statements.
 */
class Triumph_CallStackTable extends Zend_Db_Table_Abstract {

	protected $_name = 'call_stacks';

	/**
	 * loads the call stack. call stack is sorted in the order that it is executed.
	 * @return array of Triumph_CallStack instances. These are the 
	 *         list of variables and function calls that need to be examined
	 *         and template variables need to be extracted from these.
	 *            
	 */
	public function load() {
		$select = $this->select()
			->order('step_number');
		$stmt = $select->query(Zend_Db::FETCH_ASSOC);
		$callStacks = array();
		while ($row = $stmt->fetch()) {
			$call = new Triumph_CallStack();
			if ($call->fromLine($row['step_type'], $row['expression'])) {
				$callStacks [] = $call;
			}
		}
		return $callStacks;
	}
	
	/**
	 * Split a single long list of calls in array of calls per 
	 * each scope (method/function).
	 *
	 * @param $callStacks array of Triumph_CallStack instances
	 * @return array of arrays Each item in the array is an array of 
	 *         Triumph_CallStack instances. 
	 */
	public function splitScopes($callStacks) {
		$scopes = array();
		$scopeIndex = "";
		foreach ($callStacks as $i => $call) {		
			if (Triumph_CallStack::BEGIN_METHOD == $call->type || Triumph_CallStack::BEGIN_FUNCTION == $call->type) {
			
				// these items signal a new scope
				$scopeIndex = $i;
			}
			else if (!isset($scopes[$scopeIndex])) {
				$scopes[$scopeIndex] = array();
			}
			else {
				$scopes[$scopeIndex][] = $call;
			}
		}
		return $scopes;
	}
	
	/**
	 * Extract the method calls from the given call stack
	 *
	 * @param array of Triumph_CallStack class the list to filter
	 * @return array of Triumph_CallStack class the list
	 *         method calls from the given calls
	 */
	public function getMethodCalls($callStacks) {
		$methodCalls = array();
		foreach ($callStacks as $call) {
			if (Triumph_CallStack::METHOD_CALL == $call->type) {
				$methodCalls[$call->destinationVariable] = $call;
			}
		}
		return $methodCalls;
	}
	
	/**
	 * Extract the property calls from the given call stack
	 *
	 * @param array of Triumph_CallStack class the list to filter
	 * @return array of Triumph_CallStack class the list
	 *         method calls from the given calls
	 */
	public function getPropertyCalls($callStacks) {
		$propertyCalls = array();
		foreach ($callStacks as $call) {
			if (Triumph_CallStack::PROPERTY == $call->type) {
				$propertyCalls[$call->destinationVariable] = $call;
			}
		}
		return $propertyCalls;
	}
	
	/**
	 * Extract the variable declaration and assignments from the given call stack
	 *
	 * @param array of Triumph_CallStack class the list to filter
	 * @return array of Triumph_CallStack class the list
	 *         method calls from the given calls
	 */
	public function getVariables($callStacks) {
		$variableCalls = array();
		$arrayKeys = array();
		foreach ($callStacks as $call) {
			if (Triumph_CallStack::T_ARRAY == $call->type 
				|| Triumph_CallStack::SCALAR == $call->type
				|| Triumph_CallStack::NEW_OBJECT == $call->type
				|| Triumph_CallStack::ASSIGN == $call->type) {
				$variableCalls[$call->destinationVariable] = $call;
			}
		}
		return $variableCalls;
	}
	
	/**
	 * Extract all of the keys that were assigned to an array variable.
	 *
	 * @param  array of Triumph_CallStack class the list to search in
	 * @return array of string the array keys for the given
	 *         array variable.
	 */
	public function getArrayKeys($callStacks, $variableName) {
		$keys = array();
		foreach ($callStacks as $call) {
			if (Triumph_CallStack::ARRAY_KEY == $call->type && \opstring\compare($variableName, $call->destinationVariable) == 0) {
				$keys[$call->arrayKey] = 1;
			}
		}
		return array_keys($keys);
	}
	
	/**
	 * method that pretty-prints a list of Triumph_CallStack objects
	 *
	 * @param array of Triumph_CallStack instances to print
	 */
	public function printScope($callStacks) {
		foreach ($callStacks as $call) {
			if (Triumph_Callstack::T_ARRAY == $call->type) {
				echo $call->type, "\t\t\t\t", $call->destinationVariable, "\n";
			}
			if (Triumph_Callstack::METHOD_CALL == $call->type) {
				echo $call->type, "\t\t\t\t", $call->destinationVariable, "=", $call->objectName . "->" . $call->methodName . "(" . join(',', $call->functionArguments). ")" , "\n";
			}
			else if (Triumph_Callstack::PROPERTY == $call->type) {
				echo $call->type, "\t\t\t\t", $call->destinationVariable, "=", $call->objectName . "->" . $call->propertyName, "\n";
			}
			else if (Triumph_Callstack::FUNCTION_CALL == $call->type) {
				echo $call->type, "\t\t\t\t", $call->destinationVariable, "=", $call->functioName . "(" . join(',', $call->functionArguments). ")" , "\n";
			}
			else if (Triumph_Callstack::SCALAR == $call->type) {
				echo $call->type, "\t\t\t\t", $call->destinationVariable, ",", $call->scalarValue , "\n";
			}
			else if (Triumph_Callstack::ASSIGN == $call->type) {
				echo $call->type, "\t\t\t\t", $call->destinationVariable . "=" . $call->sourceVariable, "\n";
			}
			else if (Triumph_Callstack::ARRAY_KEY == $call->type) {
				echo $call->type, "\t\t\t\t", $call->destinationVariable . "[" . $call->arrayKey . "]", "\n";
			}
			else if (Triumph_Callstack::NEW_OBJECT == $call->type) {
				echo $call->type, "\t\t\t\t", $call->destinationVariable . "=new " . $call->className . "()", "\n";
			}
		}
	}
}