<?php

/**
 * General definition of an expression tree (could be OQL, SQL or whatever) 
 *
 * @package     iTopORM
 * @author      Romain Quetiez <romainquetiez@yahoo.fr>
 * @author      Denis Flaven <denisflave@free.fr>
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link        www.itop.com
 * @since       1.0
 * @version     1.1.1.1 $
 */

class MissingQueryArgument extends CoreException
{
}

abstract class Expression
{
	// recursive translation of identifiers
	abstract public function Translate($aTranslationData, $bMatchAll = true);

	// recursive rendering (aArgs used as input by default, or used as output if bRetrofitParams set to True
	abstract public function Render(&$aArgs = null, $bRetrofitParams = false);

	// recursively builds an array of class => fieldname
	abstract public function ListRequiredFields();

	abstract public function IsTrue();

	public function RequiresField($sClass, $sFieldName)
	{
		// #@# todo - optimize : this is called quite often when building a single query !
		$aRequired = $this->ListRequiredFields();
		if (!in_array($sClass.'.'.$sFieldName, $aRequired)) return false;
		return true;
	}

	public function serialize()
	{
		return base64_encode($this->Render());
	}
	
	static public function unserialize($sValue)
	{
		return self::FromOQL(base64_decode($sValue));
	}

	static public function FromOQL($sConditionExpr)
	{
		$oOql = new OqlInterpreter($sConditionExpr);
		$oExpression = $oOql->ParseExpression();
		
		return $oExpression;
	}

	public function LogAnd($oExpr)
	{
		if ($this->IsTrue()) return clone $oExpr;
		if ($oExpr->IsTrue()) return clone $this;
		return new BinaryExpression($this, 'AND', $oExpr);
	}

	public function LogOr($oExpr)
	{
		return new BinaryExpression($this, 'OR', $oExpr);
	}
}


class BinaryExpression extends Expression
{
	protected $m_oLeftExpr; // filter code or an SQL expression (later?)
	protected $m_oRightExpr;
	protected $m_sOperator;

	public function __construct($oLeftExpr, $sOperator, $oRightExpr)
	{
		if (!is_object($oLeftExpr))
		{
			throw new CoreException('Expecting an Expression object on the left hand', array('found_type' => gettype($oLeftExpr)));
		}
		if (!is_object($oRightExpr))
		{
			throw new CoreException('Expecting an Expression object on the right hand', array('found_type' => gettype($oRightExpr)));
		}
		if (!$oLeftExpr instanceof Expression)
		{
			throw new CoreException('Expecting an Expression object on the left hand', array('found_class' => get_class($oLeftExpr)));
		}
		if (!$oRightExpr instanceof Expression)
		{
			throw new CoreException('Expecting an Expression object on the right hand', array('found_class' => get_class($oRightExpr)));
		}
		$this->m_oLeftExpr  = $oLeftExpr;
		$this->m_oRightExpr = $oRightExpr;
		$this->m_sOperator  = $sOperator;
	}

	public function IsTrue()
	{
		// return true if we are certain that it will be true
		if ($this->m_sOperator == 'AND')
		{
			if ($this->m_oLeftExpr->IsTrue() && $this->m_oLeftExpr->IsTrue()) return true;
		}
		return false;
	}

	public function GetLeftExpr()
	{
		return $this->m_oLeftExpr;
	}

	public function GetRightExpr()
	{
		return $this->m_oRightExpr;
	}

	public function GetOperator()
	{
		return $this->m_sOperator;
	}

	// recursive rendering
	public function Render(&$aArgs = null, $bRetrofitParams = false)
	{
		$sOperator = $this->GetOperator();
		$sLeft = $this->GetLeftExpr()->Render($aArgs, $bRetrofitParams);
		$sRight = $this->GetRightExpr()->Render($aArgs, $bRetrofitParams);
		return "($sLeft $sOperator $sRight)";
	}

	public function Translate($aTranslationData, $bMatchAll = true)
	{
		$oLeft = $this->GetLeftExpr()->Translate($aTranslationData, $bMatchAll);
		$oRight = $this->GetRightExpr()->Translate($aTranslationData, $bMatchAll);
		return new BinaryExpression($oLeft, $this->GetOperator(), $oRight);
	}

	public function ListRequiredFields()
	{
		$aLeft = $this->GetLeftExpr()->ListRequiredFields();
		$aRight = $this->GetRightExpr()->ListRequiredFields();
		return array_merge($aLeft, $aRight);
	}
}


class UnaryExpression extends Expression
{
	protected $m_value;

	public function __construct($value)
	{
		$this->m_value = $value;
	}

	public function IsTrue()
	{
		// return true if we are certain that it will be true
		return ($this->m_value == 1);
	}

	public function GetValue()
	{
		return $this->m_value;
	} 

	// recursive rendering
	public function Render(&$aArgs = null, $bRetrofitParams = false)
	{
		if ($bRetrofitParams)
		{
			$iParamIndex = count($aArgs) + 1; // 1-based indexation
			$aArgs['param'.$iParamIndex] = $this->m_value;
			return ':param'.$iParamIndex;
		}
		else
		{
			return CMDBSource::Quote($this->m_value);
		}
	}

	public function Translate($aTranslationData, $bMatchAll = true)
	{
		return clone $this;
	}

	public function ListRequiredFields()
	{
		return array();
	}
}

class ScalarExpression extends UnaryExpression
{
	public function __construct($value)
	{
		if (!is_scalar($value))
		{
			throw new CoreException('Attempt to create a scalar expression from a non scalar', array('var_type'=>gettype($value)));
		}
		parent::__construct($value);
	}
}

class TrueExpression extends ScalarExpression
{
	public function __construct()
	{
		parent::__construct(1);
	}

	public function IsTrue()
	{
		return true;
	}
}

class FieldExpression extends UnaryExpression
{
	protected $m_sParent;
	protected $m_sName;

	public function __construct($sName, $sParent = '')
	{
		parent::__construct("$sParent.$sName");

		$this->m_sParent = $sParent;
		$this->m_sName = $sName;
	}

	public function IsTrue()
	{
		// return true if we are certain that it will be true
		return false;
	}

	public function GetParent() {return $this->m_sParent;}
	public function GetName() {return $this->m_sName;}

	// recursive rendering
	public function Render(&$aArgs = null, $bRetrofitParams = false)
	{
		if (empty($this->m_sParent))
		{
			return "`{$this->m_sName}`";
		}
		return "`{$this->m_sParent}`.`{$this->m_sName}`";
	}

	public function Translate($aTranslationData, $bMatchAll = true)
	{
		if (!array_key_exists($this->m_sParent, $aTranslationData))
		{
			if ($bMatchAll) throw new CoreException('Unknown parent id in translation table', array('parent_id' => $this->m_sParent, 'translation_table' => array_keys($aTranslationData)));
			return clone $this;
		} 
		if (!array_key_exists($this->m_sName, $aTranslationData[$this->m_sParent]))
		{
			if (!array_key_exists('*', $aTranslationData[$this->m_sParent]))
			{
				// #@# debug - if ($bMatchAll) MyHelpers::var_dump_html($aTranslationData, true);
				if ($bMatchAll) throw new CoreException('Unknown name in translation table', array('name' => $this->m_sName, 'parent_id' => $this->m_sParent, 'translation_table' => array_keys($aTranslationData[$this->m_sParent])));
				return clone $this;
			}
			$sNewParent = $aTranslationData[$this->m_sParent]['*'];
			$sNewName = $this->m_sName;
		}
		else
		{
			$sNewParent = $aTranslationData[$this->m_sParent][$this->m_sName][0];
			$sNewName = $aTranslationData[$this->m_sParent][$this->m_sName][1];
		}
		return new FieldExpression($sNewName, $sNewParent);
	}

	public function ListRequiredFields()
	{
		return array($this->m_sParent.'.'.$this->m_sName);
	}
}


class VariableExpression extends UnaryExpression
{
	protected $m_sName;

	public function __construct($sName)
	{
		parent::__construct($sName);

		$this->m_sName = $sName;
	}

	public function IsTrue()
	{
		// return true if we are certain that it will be true
		return false;
	}

	public function GetName() {return $this->m_sName;}

	// recursive rendering
	public function Render(&$aArgs = null, $bRetrofitParams = false)
	{
		if (is_null($aArgs))
		{
			return ':'.$this->m_sName;
		}
		elseif (array_key_exists($this->m_sName, $aArgs))
		{
			return CMDBSource::Quote($aArgs[$this->m_sName]);
		}
		elseif ($bRetrofitParams)
		{
			//$aArgs[$this->m_sName] = null;
			return ':'.$this->m_sName;
		}
		else
		{
			throw new MissingQueryArgument('Missing query argument', array('expecting'=>$this->m_sName, 'available'=>$aArgs));
		}
	}
}

// Temporary, until we implement functions and expression casting!
// ... or until we implement a real full text search based in the MATCH() expression
class ListExpression extends Expression
{
	protected $m_aExpressions;

	public function __construct($aExpressions)
	{
		$this->m_aExpressions = $aExpressions;
	}

	public function IsTrue()
	{
		// return true if we are certain that it will be true
		return false;
	}

	public function GetItems()
	{
		return $this->m_aExpressions;
	}

	// recursive rendering
	public function Render(&$aArgs = null, $bRetrofitParams = false)
	{
		$aRes = array();
		foreach ($this->m_aExpressions as $oExpr)
		{
			$aRes[] = $oExpr->Render($aArgs, $bRetrofitParams);
		}
		return '('.implode(', ', $aRes).')';
	}

	public function Translate($aTranslationData, $bMatchAll = true)
	{
		$aRes = array();
		foreach ($this->m_aExpressions as $oExpr)
		{
			$aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll);
		}
		return new ListExpression($aRes);
	}

	public function ListRequiredFields()
	{
		$aRes = array();
		foreach ($this->m_aExpressions as $oExpr)
		{
			$aRes = array_merge($aRes, $oExpr->ListRequiredFields());
		}
		return $aRes;
	}
}


class FunctionExpression extends Expression
{
	protected $m_sVerb;
	protected $m_aArgs; // array of expressions

	public function __construct($sVerb, $aArgExpressions)
	{
		$this->m_sVerb = $sVerb;
		$this->m_aArgs = $aArgExpressions;
	}

	public function IsTrue()
	{
		// return true if we are certain that it will be true
		return false;
	}

	public function GetVerb()
	{
		return $this->m_sVerb;
	}

	public function GetArgs()
	{
		return $this->m_aArgs;
	}

	// recursive rendering
	public function Render(&$aArgs = null, $bRetrofitParams = false)
	{
		$aRes = array();
		foreach ($this->m_aArgs as $oExpr)
		{
			$aRes[] = $oExpr->Render($aArgs, $bRetrofitParams);
		}
		return $this->m_sVerb.'('.implode(', ', $aRes).')';
	}

	public function Translate($aTranslationData, $bMatchAll = true)
	{
		$aRes = array();
		foreach ($this->m_aArgs as $oExpr)
		{
			$aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll);
		}
		return new FunctionExpression($this->m_sVerb, $aRes);
	}

	public function ListRequiredFields()
	{
		$aRes = array();
		foreach ($this->m_aArgs as $oExpr)
		{
			$aRes = array_merge($aRes, $oExpr->ListRequiredFields());
		}
		return $aRes;
	}
}

class IntervalExpression extends Expression
{
	protected $m_oValue; // expression
	protected $m_sUnit;

	public function __construct($oValue, $sUnit)
	{
		$this->m_oValue = $oValue;
		$this->m_sUnit = $sUnit;
	}

	public function IsTrue()
	{
		// return true if we are certain that it will be true
		return false;
	}

	public function GetValue()
	{
		return $this->m_oValue;
	}

	public function GetUnit()
	{
		return $this->m_sUnit;
	}

	// recursive rendering
	public function Render(&$aArgs = null, $bRetrofitParams = false)
	{
		return 'INTERVAL '.$this->m_oValue->Render($aArgs, $bRetrofitParams).' '.$this->m_sUnit;
	}

	public function Translate($aTranslationData, $bMatchAll = true)
	{
		return new IntervalExpression($this->m_oValue->Translate($aTranslationData, $bMatchAll), $this->m_sUnit);
	}

	public function ListRequiredFields()
	{
		return array();
	}
}

class CharConcatExpression extends Expression
{
	protected $m_aExpressions;

	public function __construct($aExpressions)
	{
		$this->m_aExpressions = $aExpressions;
	}

	public function IsTrue()
	{
		// return true if we are certain that it will be true
		return false;
	}

	public function GetItems()
	{
		return $this->m_aExpressions;
	}

	// recursive rendering
	public function Render(&$aArgs = null, $bRetrofitParams = false)
	{
		$aRes = array();
		foreach ($this->m_aExpressions as $oExpr)
		{
			$sCol = $oExpr->Render($aArgs, $bRetrofitParams);
			// Concat will be globally NULL if one single argument is null ! 
			$aRes[] = "COALESCE($sCol, '')";
		}
		return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
	}

	public function Translate($aTranslationData, $bMatchAll = true)
	{
		$aRes = array();
		foreach ($this->m_aExpressions as $oExpr)
		{
			$aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll);
		}
		return new CharConcatExpression($aRes);
	}

	public function ListRequiredFields()
	{
		$aRes = array();
		foreach ($this->m_aExpressions as $oExpr)
		{
			$aRes = array_merge($aRes, $oExpr->ListRequiredFields());
		}
		return $aRes;
	}
}

?>
