<?php
	define( "TASK_STATUS_PENDING" , "pending" );
	define( "TASK_STATUS_ACTIVE" , "active" );
	define( "TASK_STATUS_INACTIVE" , "inactive" );
	define( "TASK_STATUS_CLOSED" , "closed" );
	define( "TASK_STATUS_FAILED" , "failed" );
	define( "TASK_STATUS_UNKNOWN" , "unknown" );

	function TaskManagerErrorHandler( $errno = "0" , $errstr = "" , $errfile = "???" , $errline = "0" , $errcontext = null ) {
		$tm = $GLOBALS["TaskManagerObject"];
		if (!$tm->getTaskId()) return false;
		$params = array();
		$params["errno"]=$errno;
		$params["errstr"]=$errstr;
		$params["errfile"]=$errfile;
		$params["errline"]=$errline;
		if (isset( $errcontext )) $params["errcontext"]=$errcontext;
		$bt = debug_backtrace();
		$rbt = array();
		foreach ($bt as $row) {
			$rbt[]=array(
				"file" => $row["file"],
				"line" => $row["line"]
			);
		}
		// array_shift( $bt );
		$params["backtrace"]=$rbt;
		$ts = $tm->getTaskState();
		$ts["error"]=$params;

		$tm->setTaskError( $params );
		switch ($errno) {
		case E_ERROR:
		case E_PARSE:
		case E_USER_ERROR:
		case E_CORE_ERROR:
		case E_COMPILE_ERROR:
			$tm->closeTask( TASK_STATUS_FAILED );
			die();
		default:
			$tm->updateTask();
			return true;
		}
		// FNTH_da( $tm->prepareFields() );
		return false;
	}


	class TaskManagerModel {
		protected $iTaskId = 0;
		protected $sTaskName = "";
		protected $sTaskDescription = "";
		protected $sTaskType = "";
		protected $iTaskUID = 0;
		protected $aTaskParameters = array();
		protected $sTaskStatus = "";
		protected $aTaskState = array();
		protected $aTaskError = array();
		protected $iTaskPriority = 0;
		protected $iTaskModifyTimestamp = 0;
		protected $iTaskEndTimestamp = 0;

		public function getTaskId() { return $this->iTaskId; }
		public function setTaskName( $v ) { $this->sTaskName = $v; }
		public function getTaskName() { return $this->sTaskName; }
		public function setTaskDescription( $v ) { $this->sTaskDescription = $v; }
		public function getTaskDescription() { $this->sTaskDescription; }
		public function setTaskType( $v ) { $this->sTaskType = $v; }
		public function getTaskType( ) { return $this->sTaskType; }
		public function setTaskUID( $v ) { $this->iTaskUID = $v; }
		public function getTaskUID( ) { return $this->iTaskUID; }
		public function setTaskParameters( $v ) { $this->aTaskParameters = $v; }
		public function getTaskParameters() { return $this->aTaskParameters; }
		public function setTaskStatus( $v ) { $this->sTaskStatus = $v; }
		public function getTaskStatus() { return $this->sTaskStatus; }
		public function setTaskState( $v ) { $this->aTaskState = $v; }
		public function getTaskState() { return $this->aTaskState; }
		public function setTaskError( $v ) { $this->aTaskError = $v; }
		public function getTaskError() { return $this->aTaskError; }
		public function setTaskPriority( $v ) { $this->iTaskPriority = $v; }
		public function getTaskPriority() { return $this->iTaskPriority = $v; }
		public function lowerTaskPriority() { $this->iTaskPriority++; }
		public function raiseTaskPriority() { $this->iTaskPriority--; }

		public function newTask() {
			$this->iTaskId = 0;
			$this->sTaskName = sprintf( "task_%s" , fo3_date( "Y-m-d_H:i:s" ) );
			$this->sTaskDescription = "";
			$this->sTaskType = "general";
			$this->iTaskUID = 0;
			$this->aTaskParameters = array();
			$this->sTaskStatus = TASK_STATUS_PENDING;
			$this->aTaskState = array();
			$this->iTaskModifyTimestamp = 0;
			$this->iTaskEndTimestamp = 0;
		}

		protected function prepareFields() {
			$retval = array();
			// the id
			if ($this->iTaskId) $retval["task_id"]=$this->iTaskId;
			// textual fields
			$retval["task_name"]=sprintf( "'%s'" , FNTH_db_addslashes( $this->sTaskName ) );
			$retval["task_description"]=sprintf( "'%s'" , FNTH_db_addslashes( $this->sTaskDescription ) );
			$retval["task_type"]=sprintf( "'%s'" , FNTH_db_addslashes( $this->sTaskType ) );
			$retval["task_status"]=sprintf( "'%s'" , FNTH_db_addslashes( $this->sTaskStatus ) );
			// parametrical fields
			$retval["task_parameters"]=sprintf( "'%s'" , FNTH_db_addslashes( serialize( $this->aTaskParameters ) ) );
			$retval["task_state"]=sprintf( "'%s'" , FNTH_db_addslashes( serialize( $this->aTaskState ) ) );
			$retval["task_error"]=sprintf( "'%s'" , FNTH_db_addslashes( serialize( $this->aTaskError ) ) );
			// integer
			$retval["task_priority"]=sprintf( "%d" , $this->iTaskPriority );
			
			if ($this->iTaskUID)
				$retval["task_uid"]=sprintf( "%d" , $this->iTaskUID );
			if ($this->iTaskModifyTimestamp)
				$retval["task_mtstamp"]=fo3_date( "'Y-m-d H:i:s'" , $this->iTaskModifyTimestamp );
			if ($this->iTaskEndTimestamp)
				$retval["task_etstamp"]=fo3_date( "'Y-m-d H:i:s'" , $this->iTaskEndTimestamp );
			return $retval;
		}

		public function saveNewTask() {
			$ins = $this->prepareFields();
			$query = FNTH_db_generate_insert_query( "tasks" , $ins );
			FNTH_db_free_result( FNTH_db_query( $query ) );
			$task_id = FNTH_db_get_field( "select currval('tasks_task_id_seq') as task_id" , "task_id" );
			return $task_id;
		}

		public function updateTask() {
			if (!$this->iTaskId) return false;
			$this->iTaskModifyTimestamp = fo3_time();
			$upd = $this->prepareFields();
			$query = FNTH_db_generate_update_query( "tasks" , $upd , sprintf( "task_id=%d" , $this->iTaskId ) );
			FNTH_db_free_result( FNTH_db_query( $query ) );
			return true;
		}

		public function addTask( $p = array() ) {
			$this->newTask();
			if (isset( $p["task_name"] )) $this->setTaskName( $p["task_name"] );
			if (isset( $p["task_description"] )) $this->setTaskDescription( $p["task_description"] );
			if (isset( $p["task_type"] )) $this->setTaskType( $p["task_type"] );
			if (isset( $p["task_uid"] )) $this->setTaskUID( $p["task_uid"] );
			if (isset( $p["task_parameters"] )) $this->setTaskParameters( $p["task_parameters"] );
			if (isset( $p["task_status"] )) $this->setTaskStatus( $p["task_status"] );
			if (isset( $p["task_state"] )) $this->setTaskState( $p["task_state"] );
			return $this->saveNewTask();
		}

		public function getTaskIdsOnStatus( $a = array( TASK_STATUS_PENDING ) ) {
			return FNTH_db_get_column(
				sprintf( "select task_id from tasks where task_status in (%s) and task_ptstamp<now() order by task_priority,task_ptstamp",
				$this->getTaskStatusClause( $a ) ),
				"task_id" );
		}

		public function deleteTask( $task_id ) {
			FNTH_db_free_result( FNTH_db_query( sprintf( "update newsletter_newsletters set activate_task_id=null where activate_task_id=%d" , $task_id ) ) );
			FNTH_db_free_result( FNTH_db_query( sprintf( "update newsletter_newsletters set send_task_id=null where send_task_id=%d" , $task_id ) ) );
			FNTH_db_free_result( FNTH_db_query( sprintf( "update jobs set activate_task_id=null where activate_task_id=%d" , $task_id ) ) );
			FNTH_db_free_result( FNTH_db_query( sprintf( "update jobs set send_task_id=null where send_task_id=%d" , $task_id ) ) );
			FNTH_db_free_result( FNTH_db_query( sprintf( "delete from tasks where task_id=%d" , $task_id ) ) );
		}

		public function getPendingTaskIds( ) {
			return $this->getTaskIdsOnStatus();
		}
		
		public function getPendingInactiveTaskIds( ) {
			return $this->getTaskIdsOnStatus(
				array( TASK_STATUS_PENDING, TASK_STATUS_INACTIVE ) );
		}

		protected function getTaskStatusClause( $a = array() ) {
			return "'".implode( "','" , $a ) . "'";
		}

		protected function getTaskDatas( $task_id ) {
			$query = sprintf( "select * from tasks where task_id=%d",$task_id );
			return FNTH_db_get_row( $query );
		}

		protected function setTaskFromDataRow( $row ) {
			$this->setTaskName( $row["task_name"] );
			$this->setTaskDescription( $row["task_description"] );
			$this->setTaskType( $row["task_type"] );
			if (isset( $row["task_uid"] )) $this->setTaskUID( $row["task_uid"] );
			$this->setTaskParameters( unserialize( $row["task_parameters"] ) );
			$this->setTaskStatus( TASK_STATUS_ACTIVE );
			if (isset( $row["task_state"] ))
				$this->setTaskState( unserialize( $row["task_state"] ) );
		}

		public function acquireTask( $task_id ) {
			// TODO mutexed acquire
			$row = $this->getTaskDatas( $task_id );
			if (!$row) return false;
			if (!in_array( $row["task_status"] , array( TASK_STATUS_PENDING , TASK_STATUS_INACTIVE ))) return false;
			$this->setErrorHandler();
			$this->newTask();	// clear all
			$this->iTaskId = $task_id;
			$this->setTaskFromDataRow( $row );
			$this->setTaskStatus( TASK_STATUS_ACTIVE );
			$this->updateTask();
			return true;
		}
		public function setErrorHandler() {
			$GLOBALS["TaskManagerObject"]=$this;
			set_error_handler( "TaskManagerErrorHandler" , E_ALL | E_STRICT );
		}

		public function clearErrorHandler() {
			restore_error_handler();
		}

		public function releaseTask( ) {
			if (!$this->iTaskId) return false;
			$query = sprintf( "select * from tasks where task_status in (%s) and task_id=%d",
				$this->getTaskStatusClause( array( TASK_STATUS_ACTIVE ) ) , $this->iTaskId );
			$row = FNTH_db_get_row( $query );
			if (!$row) return false;
			$this->setTaskStatus( TASK_STATUS_INACTIVE );
			$this->updateTask();
			$this->clearErrorHandler();
			return true;
		}


		public function closeTask( $status = null ) {
			if (!$this->iTaskId) return false;
			if (!isset( $status )) $status = TASK_STATUS_CLOSED;
			$this->setTaskStatus( $status );
			$this->updateTask();
			$this->newTask();
			$this->clearErrorHandler();
			return true;
		}

		public function reissueTask( $task_id ) {
			$query = sprintf( "update tasks set task_status='pending',task_state='a:0:{}' where task_id=%d" , $task_id );
			FNTH_db_free_result( FNTH_db_query( $query ) );
		}

		public function extractTaskState( $string ) {
			return __unserialize( $string );
		}

		public function returnTasks( $task_ids = array() ) {
			if (empty( $task_ids )) return array();
			$query = sprintf( "select * from tasks where task_id in (%s)",
				implode( ",",$task_ids ) );
			$ds = FNTH_db_get_resultset( $query );
			$tasks = array();
			foreach ($ds as $row) {
				$row["task_parameters"]=__unserialize( $row["task_parameters"] );
				$row["task_state"]=__unserialize( $row["task_state"] );
				$tasks[$row["task_id"]]=$row;
			}
			return $tasks;
		}

		public function getTasks( $p = array() ) {
			if (!isset( $p["limit"] )) $p["limit"] = 5;

			// where clause
			$where_clause = "";
			$filters = array();

			if (isset( $p["task_status"] )) {
				if (is_string( $p["task_status"] )) {
					$not = (isset( $p["task_status_negative"] )) ? "<>" : "=";
					$filters[]=sprintf( "task_status%s'%s'" , $not ,FNTH_db_addslashes( $p["task_status"] ) );
				} else if (is_array( $p["task_status"] )) {
					$ts = array_map( "FNTH_db_addslashes" , $p["task_status"] );
					$statuses = implode( "','" , $ts );
					$not = (isset( $p["task_status_negative"] )) ? "not" : "";
					$filters[]=sprintf( "task_status %s in ('%s')" , $not , $statuses );
				}
			}

			if (isset( $p["task_type"] ))
				$filters[]=sprintf( "task_type='%s'" , FNTH_db_addslashes( $p["task_type"] ) );

			if (!empty( $filters )) 
				$where_clause.=sprintf( " where %s" , implode( " and " , $filters ) );

			// order clause
			$order_clause = "";

			// limit clause
			$limit_clause = sprintf( " limit %d" , $p["limit"] );

			$query = sprintf( "select * from tasks %s",
				$where_clause,
				$order_clause,
				$limit_clause );
			return FNTH_db_get_resultset( $query );
		}

		public function createStatusSendTask( $member_ids = array() ) {
			if (!is_array( $member_ids )) $member_ids = array( $member_ids );
			$this->newTask();
			$this->setTaskName( sprintf( "sending status" ) );
			$this->setTaskDescription( "status sending" );
			$this->setTaskType( "status_send" );
			$this->setTaskState(
				array( "member_ids" => $member_ids ) );
			$task_id = $this->saveNewTask();
			return $task_id;
		}

	}
?>
