<?php

class RenderStrategy {

	protected $conn = null;

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

	/*! adds mix fields into DataConfig
	 *	@param config
	 *		DataConfig object
	 *	@param mix
	 *		mix structure
	 */
	protected function mix($config, $mix) {
		for ($i = 0; $i < count($mix); $i++) {
			if ($config->is_field($mix[$i]['name'])===-1) {
				$config->add_field($mix[$i]['name']);
			}
		}
	}

	/*! remove mix fields from DataConfig
	 *	@param config
	 *		DataConfig object
	 *	@param mix
	 *		mix structure
	 */
	protected function unmix($config, $mix) {
		for ($i = 0; $i < count($mix); $i++) {
			if ($config->is_field($mix[$i]['name'])!==-1) {
				$config->remove_field_full($mix[$i]['name']);
			}
		}
	}

	/*! adds mix fields in item
	 *	simple mix adds only strings specified by user
	 *	@param mix
	 *		mix structure
	 *	@param data
	 *		array of selected data
	 */
	protected function simple_mix($mix, $data) {
		// get mix details
		for ($i = 0; $i < count($mix); $i++)
			$data[$mix[$i]["name"]] = is_object($mix[$i]["value"]) ? "" : $mix[$i]["value"];
		return $data;
	}

	/*! adds mix fields in item
	 *	complex mix adds strings specified by user and results of subrequests
	 *	@param mix
	 *		mix structure
	 *	@param data
	 *		array of selected data
	 */
	protected function complex_mix($mix, $data) {
		// get mix details
		for ($i = 0; $i < count($mix); $i++) {
			$mixname = $mix[$i]["name"];
			if ($mix[$i]['filter'] !== false) {
				$subconn = $mix[$i]["value"];
				$filter = $mix[$i]["filter"];

				// setting relationships
				$subconn->clear_filter();
				foreach ($filter as $k => $v)
					if (isset($data[$v]))
						$subconn->filter($k, $data[$v], "=");
					else
						throw new Exception('There was no such data field registered as: '.$k);

				$subconn->asString(true);
				$data[$mixname]=$subconn->simple_render();
				if (is_array($data[$mixname]) && count($data[$mixname]) == 1)
					$data[$mixname] = $data[$mixname][0];
			} else {
				$data[$mixname] = $mix[$i]["value"];
			}
		}
		return $data;
	}

	/*! render from DB resultset
		@param res
			DB resultset 
		process commands, output requested data as XML
	*/
	public function render_set($res, $name, $dload, $sep, $config, $mix){
		$output="";
		$index=0;
		$conn = $this->conn;
		$this->mix($config, $mix);
		$conn->event->trigger("beforeRenderSet",$conn,$res,$config);
		while ($data=$conn->sql->get_next($res)){
			$data = $this->simple_mix($mix, $data);

			$data = new $name($data,$config,$index);
			if ($data->get_id()===false)
				$data->set_id($conn->uuid());
			$conn->event->trigger("beforeRender",$data);
			$output.=$data->to_xml().$sep;
			$index++;
		}
		$this->unmix($config, $mix);
		return $output;
	}

}

class JSONRenderStrategy extends RenderStrategy {

	/*! render from DB resultset
		@param res
			DB resultset 
		process commands, output requested data as json
	*/
	public function render_set($res, $name, $dload, $sep, $config, $mix){
		$output=array();
		$index=0;
		$conn = $this->conn;
		$this->mix($config, $mix);
		$conn->event->trigger("beforeRenderSet",$conn,$res,$config);
		while ($data=$conn->sql->get_next($res)){
			$data = $this->complex_mix($mix, $data);
			$data = new $name($data,$config,$index);
			if ($data->get_id()===false)
				$data->set_id($conn->uuid());
			$conn->event->trigger("beforeRender",$data);
            $item = $data->to_xml();
            if ($item !== false)
                $output[]=$item;
			$index++;
		}
		$this->unmix($config, $mix);
		return $output;
	}

}

class TreeRenderStrategy extends RenderStrategy {

	protected $id_swap = array();

	public function __construct($conn) {
		parent::__construct($conn);
		$conn->event->attach("afterInsert",array($this,"parent_id_correction_a"));
		$conn->event->attach("beforeProcessing",array($this,"parent_id_correction_b"));
	}

	public function render_set($res, $name, $dload, $sep, $config, $mix){
		$output="";
		$index=0;
		$conn = $this->conn;
        $config_copy = new DataConfig($config);
		$this->mix($config, $mix);
		while ($data=$conn->sql->get_next($res)){
			$data = $this->simple_mix($mix, $data);
			$data = new $name($data,$config,$index);
			$conn->event->trigger("beforeRender",$data);
			//there is no info about child elements,
			//if we are using dyn. loading - assume that it has,
			//in normal mode juse exec sub-render routine
			if ($data->has_kids()===-1 && $dload)
					$data->set_kids(true);
			$output.=$data->to_xml_start();
			if ($data->has_kids()===-1 || ( $data->has_kids()==true && !$dload)){
				$sub_request = new DataRequestConfig($conn->get_request());
                //$sub_request->set_fieldset(implode(",",$config_copy->db_names_list($conn->sql)));
				$sub_request->set_relation($data->get_id());
				$output.=$this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config_copy, $mix);
			}
			$output.=$data->to_xml_end();
			$index++;
		}
		$this->unmix($config, $mix);
		return $output;
	}

	/*! store info about ID changes during insert operation
		@param dataAction 
			data action object during insert operation
	*/
	public function parent_id_correction_a($dataAction){
		$this->id_swap[$dataAction->get_id()]=$dataAction->get_new_id();
	}

	/*! update ID if it was affected by previous operation
		@param dataAction 
			data action object, before any processing operation
	*/
	public function parent_id_correction_b($dataAction){
		$relation = $this->conn->get_config()->relation_id["db_name"];
		$value = $dataAction->get_value($relation);

		if (array_key_exists($value,$this->id_swap))
			$dataAction->set_value($relation,$this->id_swap[$value]);
	}
}



class JSONTreeRenderStrategy extends TreeRenderStrategy {

	public function render_set($res, $name, $dload, $sep, $config,$mix){
		$output=array();
		$index=0;
		$conn = $this->conn;
        $config_copy = new DataConfig($config);
		$this->mix($config, $mix);
		while ($data=$conn->sql->get_next($res)){
			$data = $this->complex_mix($mix, $data);
			$data = new $name($data,$config,$index);
			$conn->event->trigger("beforeRender",$data);
			//there is no info about child elements, 
			//if we are using dyn. loading - assume that it has,
			//in normal mode just exec sub-render routine			
			if ($data->has_kids()===-1 && $dload)
					$data->set_kids(true);
			$record = $data->to_xml_start();
			if ($data->has_kids()===-1 || ( $data->has_kids()==true && !$dload)){
				$sub_request = new DataRequestConfig($conn->get_request());
                //$sub_request->set_fieldset(implode(",",$config_copy->db_names_list($conn->sql)));
				$sub_request->set_relation($data->get_id());
                //$sub_request->set_filters(array());
				$temp = $this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config_copy, $mix);
				if (sizeof($temp))
					$record["data"] = $temp;
			}
            if ($record !== false)
			    $output[] = $record;
			$index++;
		}
		$this->unmix($config, $mix);
		return $output;
	}	

}


class MultitableTreeRenderStrategy extends TreeRenderStrategy {

	private $level = 0;
	private $max_level = null;
	protected $sep = "-@level@-";
	
	public function __construct($conn) {
		parent::__construct($conn);
		$conn->event->attach("beforeProcessing", Array($this, 'id_translate_before'));
		$conn->event->attach("afterProcessing", Array($this, 'id_translate_after'));
	}

	public function set_separator($sep) {
		$this->sep = $sep;
	}
	
	public function render_set($res, $name, $dload, $sep, $config, $mix){
		$output="";
		$index=0;
		$conn = $this->conn;
		$this->mix($config, $mix);
		while ($data=$conn->sql->get_next($res)){
			$data = $this->simple_mix($mix, $data);
			$data[$config->id['name']] = $this->level_id($data[$config->id['name']]);
			$data = new $name($data,$config,$index);
			$conn->event->trigger("beforeRender",$data);
			if (($this->max_level !== null)&&($conn->get_level() == $this->max_level)) {
				$data->set_kids(false);
			} else {
				if ($data->has_kids()===-1)
					$data->set_kids(true);
			}
			$output.=$data->to_xml_start();
			$output.=$data->to_xml_end();
			$index++;
		}
		$this->unmix($config, $mix);
		return $output;
	}


	public function level_id($id, $level = null) {
		return ($level === null ? $this->level : $level).$this->sep.$id;
	}


	/*! remove level prefix from id, parent id and set new id before processing
		@param action
			DataAction object
	*/
	public function id_translate_before($action) {
		$id = $action->get_id();
		$id = $this->parse_id($id, false);
		$action->set_id($id);
		$action->set_value('tr_id', $id);
		$action->set_new_id($id);
		$pid = $action->get_value($this->conn->get_config()->relation_id['db_name']);
		$pid = $this->parse_id($pid, false);
		$action->set_value($this->conn->get_config()->relation_id['db_name'], $pid);
	}


	/*! add level prefix in id and new id after processing
		@param action
			DataAction object
	*/
	public function id_translate_after($action) {
		$id = $action->get_id();
		$action->set_id($this->level_id($id));
		$id = $action->get_new_id();
		$action->success($this->level_id($id));
	}


	public function get_level($parent_name) {
		if ($this->level) return $this->level;
		if (!isset($_GET[$parent_name])) {
			if (isset($_POST['ids'])) {
				$ids = explode(",",$_POST["ids"]);
				$id = $this->parse_id($ids[0]);
				$this->level--;
			}
			$this->conn->get_request()->set_relation(false);
		} else {
			$id = $this->parse_id($_GET[$parent_name]);
			$_GET[$parent_name] = $id;
		}
		return $this->level;
	}


	public function is_max_level() {
		if (($this->max_level !== null) && ($this->level >= $this->max_level))
			return true;
		return false;
	}
	public function set_max_level($max_level) {
		$this->max_level = $max_level;
	}
	public function parse_id($id, $set_level = true) {
		$parts = explode($this->sep, $id, 2);
		if (count($parts) === 2) {
			$level = $parts[0] + 1;
			$id = $parts[1];
		} else {
			$level = 0;
			$id = '';
		}
		if ($set_level) $this->level = $level;
		return $id;
	}

}


class JSONMultitableTreeRenderStrategy extends MultitableTreeRenderStrategy {

	public function render_set($res, $name, $dload, $sep, $config, $mix){
		$output=array();
		$index=0;
		$conn = $this->conn;
		$this->mix($config, $mix);
		while ($data=$conn->sql->get_next($res)){
			$data = $this->complex_mix($mix, $data);
			$data[$config->id['name']] = $this->level_id($data[$config->id['name']]);
			$data = new $name($data,$config,$index);
			$conn->event->trigger("beforeRender",$data);

			if ($this->is_max_level()) {
				$data->set_kids(false);
			} else {
				if ($data->has_kids()===-1)
					$data->set_kids(true);
			}
			$record = $data->to_xml_start($output);
			$output[] = $record;
			$index++;
		}
		$this->unmix($config, $mix);
		return $output;
	}

}


class GroupRenderStrategy extends RenderStrategy {

	protected $id_postfix = '__{group_param}';

	public function __construct($conn) {
		parent::__construct($conn);
		$conn->event->attach("beforeProcessing", Array($this, 'check_id'));
		$conn->event->attach("onInit", Array($this, 'replace_postfix'));
	}

	public function render_set($res, $name, $dload, $sep, $config, $mix, $usemix = false){
		$output="";
		$index=0;
		$conn = $this->conn;
		if ($usemix) $this->mix($config, $mix);
		while ($data=$conn->sql->get_next($res)){
			if (isset($data[$config->id['name']])) {
				$this->simple_mix($mix, $data);
				$has_kids = false;
			} else {
				$data[$config->id['name']] = $data['value'].$this->id_postfix;
				$data[$config->text[0]['name']] = $data['value'];
				$has_kids = true;
			}
			$data = new $name($data,$config,$index);
			$conn->event->trigger("beforeRender",$data);
			if ($has_kids === false) {
				$data->set_kids(false);
			}

			if ($data->has_kids()===-1 && $dload)
				$data->set_kids(true);
			$output.=$data->to_xml_start();
			if (($data->has_kids()===-1 || ( $data->has_kids()==true && !$dload))&&($has_kids == true)){
				$sub_request = new DataRequestConfig($conn->get_request());
				$sub_request->set_relation(str_replace($this->id_postfix, "", $data->get_id()));
				$output.=$this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config, $mix, true);
			}
			$output.=$data->to_xml_end();
			$index++;
		}
		if ($usemix) $this->unmix($config, $mix);
		return $output;
	}

	public function check_id($action) {
		if (isset($_GET['editing'])) {
			$config = $this->conn->get_config();
			$id = $action->get_id();
			$pid = $action->get_value($config->relation_id['name']);
			$pid = str_replace($this->id_postfix, "", $pid);
			$action->set_value($config->relation_id['name'], $pid);
			if (!empty($pid)) {
				return $action;
			} else {
				$action->error();
				$action->set_response_text("This record can't be updated!");
				return $action;
			}
		} else {
			return $action;
		}
	}

	public function replace_postfix() {
		if (isset($_GET['id'])) {
			$_GET['id'] = str_replace($this->id_postfix, "", $_GET['id']);
		}
	}

	public function get_postfix() {
		return $this->id_postfix;
	}

}


class JSONGroupRenderStrategy extends GroupRenderStrategy {

	public function render_set($res, $name, $dload, $sep, $config, $mix, $usemix = false){
		$output=array();
		$index=0;
		$conn = $this->conn;
		if ($usemix) $this->mix($config, $mix);
		while ($data=$conn->sql->get_next($res)){
			if (isset($data[$config->id['name']])) {
				$data = $this->complex_mix($mix, $data);
				$has_kids = false;
			} else {
				$data[$config->id['name']] = $data['value'].$this->id_postfix;
				$data[$config->text[0]['name']] = $data['value'];
				$has_kids = true;
			}
			$data = new $name($data,$config,$index);
			$conn->event->trigger("beforeRender",$data);
			if ($has_kids === false) {
				$data->set_kids(false);
			}

			if ($data->has_kids()===-1 && $dload)
				$data->set_kids(true);
			$record = $data->to_xml_start();
			if (($data->has_kids()===-1 || ( $data->has_kids()==true && !$dload))&&($has_kids == true)){
				$sub_request = new DataRequestConfig($conn->get_request());
				$sub_request->set_relation(str_replace($this->id_postfix, "", $data->get_id()));
				$temp = $this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config, $mix, true);
				if (sizeof($temp))
					$record["data"] = $temp;
			}
			$output[] = $record;
			$index++;
		}
		if ($usemix) $this->unmix($config, $mix);
		return $output;
	}

}


?>