<?php
require_once 'app/class-user-manager.php';
require_once "app/class-alert-manager.php";
require_once "app/class-workflow.php";
require_once "app/class-wf-step.php";

require_once "app/class-record.php";
require_once "app/class-cluster.php";
require_once "app/class-cluster-member.php";
require_once "app/class-recordset.php";
require_once "app/class-dataset.php";
require_once "app/class-llo-record.php";

/**
 * Workflow Engine Manager
 * 
 * Performs Data Records, Cluster, cluster members and Recordsets operations according to rule and post actions engine
 * @author tm - Advantis Solutions
 * @version 1.0.1
 * @package Backend\Workflows
 */
class WFEngine {
	public $workflows;
	private $isdataset = false; //
	private $cluster;
	private $dataset;
	private $clustermembers;
	private $recordsets;
	private $recordsetmembers;
	private $recordsetmasters;
	private $record;
	private $llorecord;
	private $context;
	private $context_item_id;
	private $context_parent_item_id;
	
	
	/**
	 * Sets WF by context from DB or session
	 */
	function WFEngine() {
		$this->workflows = $this->get_workflows ();
	}
	
	/**
	 * Sets WF by context from DB or session
	 *
	 * @return boolean
	 */
	public function get_workflows() {
		if (Session::get_workflows ()) {
			
			return Session::get_workflows ();
		} else {
			
			$wfset = array ();
			$um = new UserManager ();
			$roleslist = $um->get_user_roles ();
			
			// get workflows steps and restrict by roles
			$em = new EntityManager ( "vw_worflow_engine_states" );
			$results = $em->load ();
			
			foreach ( $results as $step ) {
				$localrolesfilter = array_filter ( $roleslist, function ($e) {
					return in_array ( $e->role_code, array (
							UserManager::__ROLE_LOCALCOLLECTOR,
							UserManager::__ROLE_LOCALANALYST,
							UserManager::__ROLE_LOCALPUBLISHER 
					) );
				} );
				$nationalrolesfilter = array_filter ( $roleslist, function ($e) {
					return in_array ( $e->role_code, array (
							UserManager::__ROLE_NATIONALCOLLECTOR,
							UserManager::__ROLE_NATIONALANALYST,
							UserManager::__ROLE_NATIONALANALYST 
					) );
				} );
				
				$lrexists = count ( ($localrolesfilter) > 0 );
				$nrexists = count ( ($nationalrolesfilter) > 0 );
				
				if (($step->workflowstates_workflowid == Workflow::__CTX_TYPE_RECORD_LLO && $lrexists) || ($step->workflowstates_workflowid != Workflow::__CTX_TYPE_RECORD_LLO && $nrexists)) {
					$newstep = new WorkflowStep ();
					$newstep->set_step ( $step->workflowstates_fromstatecodeid, $step->fromstate_descriptioncode, $step->workflowstates_tostatecodeid, $step->tostate_descriptioncode, $step->roles_fromrolecode, $step->roles_torolecode, $step->workflowstates_fromroleid, $step->workflowstates_toroleid, $step->alerttype_id, $step->step_validation_rule, $step->post_action, $step->alerttype_descriptioncode, $step->alerttype_contexttypeid );
					
					if (! isset ( $wfset [$step->workflowstates_workflowid] )) {
						$wfset [$step->workflowstates_workflowid] = new Workflow ( $step->workflowstates_workflowid );
					}
					
					$keyStep = $step->roles_fromrolecode . '#' . $step->workflowstates_fromstatecodeid;
					
					if (! is_array ( $wfset [$step->workflowstates_workflowid]->steps [$keyStep] )) {
						$wfset [$step->workflowstates_workflowid]->steps [$keyStep] = array ();
					}
					
					array_push ( $wfset [$step->workflowstates_workflowid]->steps [$keyStep], $newstep );
				}
			}
			
			Session::set_workflows ( $wfset );
			return Session::get_workflows ();
		}
	}
	
	/**
	 * Get WF steps by type of context (Workflow class) for a profile type from a node with a State code
	 * If id of the context item is passed sets in context all objects to run validation rules.
	 * In some cases pass parentid objet
	 *
	 * @param Workflow::const $ctx        	
	 * @param string $fromRole        	
	 * @param integer $fromStateCode        	
	 * @param string $id        	
	 * @param string $parentid        	
	 * @return array (WorkflowStep)
	 */
	private function get_wf_next_states($ctx, $fromRole, $fromStateCode, $id = null, $parentid = null) {
		$wf = $this->workflows [$ctx];
		
		$steps = $wf->get_steps ( $fromRole, $fromStateCode );
		
		if (isset ( $id )) {
			$this->context = $ctx;
			$this->context_item_id = $id;
			$this->context_parent_item_id = $parentid;
			$this->set_contextdata ();
		}
		
		$this->validate_steps ( $steps );
		
		return $steps;
	}
	
	/**
	 * Set WF context properties with all objects relationed with an item id for a specific context
	 */
	private function set_contextdata() {
		unset ( $this->cluster );
		unset ( $this->clustermembers );
		unset ( $this->recordsetmasters );
		unset ( $this->recordsetmembers );
		unset ( $this->recordsets );
		unset ( $this->record );
		unset ( $this->llorecord );
		
		switch ($this->context) {
			
			case Workflow::__CTX_TYPE_RECORDSET :
				$rs = new Recordset ( $this->context_item_id );
				$this->recordsets = array (
						$rs->id => $rs 
				);
				$this->cluster = new Cluster ( $rs->clusterid );
				if ($rs->masterrecordid != '') {
					$recordmaster = new DataRecord ( $rs->masterrecordid );
					$this->recordsetmasters = array (
							$rs->id => $recordmaster 
					);
				}
				$this->isdataset = true;
				break;
			
			case Workflow::__CTX_TYPE_CLUSTER :
				$this->cluster = new Cluster ( $this->context_item_id );
				$this->clustermembers = $this->cluster->get_members ();
				
				$this->recordsets = $this->cluster->get_recordsets ();
				foreach ( $this->recordsets as $rstmp ) {
					$oRecordset = new Recordset ( $rstmp->id );
					$rsmembers = $oRecordset->get_members ();
					
					foreach ( $rsmembers as $rmember ) {
						$oRMember = new DataRecord ( $rmember->record_id );
						if ($oRecordset->masterrecordid == $oRMember->id) {
							$this->recordsetmasters [$oRecordset->id] = $oRMember;
						} else {
							if (! is_array ( $this->recordsetmembers [$oRecordset->id] )) {
								$this->recordsetmembers [$oRecordset->id] = array ();
							}
							array_push ( $this->recordsetmembers [$oRecordset->id], $oRMember );
						}
					}
				}
				$this->isdataset = true;
				break;
			
			case Workflow::__CTX_TYPE_DATASET_EFP :
				$this->dataset = new Dataset ( $this->context_item_id );
				$this->isdataset = true;
				break;
			
			case Workflow::__CTX_TYPE_RECORD_LLO :
				$this->llorecord = new LLORecord ( $this->context_item_id );
				$this->isdataset = true;
				break;
			
			case Workflow::__CTX_TYPE_CLUSTERMEMBER :
				$this->cluster = new Cluster ( $this->context_parent_item_id );
				// $this->clustermembers = $this->cluster->get_members();
				$this->isdataset = true;
				break;
			
			case Workflow::__CTX_TYPE_RECORD_NFP :
				$this->record = new DataRecord ( $this->context_item_id );
				if ($this->record->recordsetid != '') {
					$rs = new Recordset ( $this->record->recordsetid );
					$this->recordsets = array (
							$rs->id => $rs 
					);
					if ($rs->masterrecordid != '') {
						if ($this->record->id != $rs->masterrecordid) {
							$recordmaster = new DataRecord ( $rs->masterrecordid );
						} else {
							$recordmaster = $this->record;
						}
						$this->recordsetmasters = array (
								$rs->id => $recordmaster 
						);
					}
				}
				$this->isdataset = true;
				break;
		}
	}
	
	/**
	 * Get WF steps by 'record NFP' type context (Workflow class) for a profile type from a node with a State code
	 *
	 * @param string $fromRole        	
	 * @param integer $fromStateCode        	
	 * @param string $id        	
	 * @return array (WorkflowStep)
	 */
	public function get_wf_record_next_states($fromRole, $fromStateCode, $id = null, $parentid = null) {
		return $this->get_wf_next_states ( Workflow::__CTX_TYPE_RECORD_NFP, $fromRole, $fromStateCode, $id, $parentid );
	}
	
	/**
	 * Get WF steps by 'record LLO' type context (Workflow class) for a profile type from a node with a State code
	 *
	 * @param string $fromRole        	
	 * @param integer $fromStateCode        	
	 * @param string $id        	
	 * @return array (WorkflowStep)
	 */
	public function get_wf_llorecord_next_states($fromRole, $fromStateCode, $id = null, $parentid = null) {
		return $this->get_wf_next_states ( Workflow::__CTX_TYPE_RECORD_LLO, $fromRole, $fromStateCode, $id, $parentid );
	}
	
	/**
	 * Get WF steps by 'Cluster' type context (Workflow class) for a profile type from a node with a State code
	 *
	 * @param string $fromRole        	
	 * @param integer $fromStateCode        	
	 * @param string $id        	
	 * @return array (WorkflowStep)
	 */
	public function get_wf_cluster_next_states($fromRole, $fromStateCode, $id = null, $parentid = null) {
		return $this->get_wf_next_states ( Workflow::__CTX_TYPE_CLUSTER, $fromRole, $fromStateCode, $id, $parentid );
	}
	
	/**
	 * Get WF steps by 'ClusterMember' type context (Workflow class) for a profile type from a node with a State code
	 *
	 * @param string $fromRole        	
	 * @param integer $fromStateCode        	
	 * @param string $id        	
	 * @return array (WorkflowStep)
	 */
	public function get_wf_clustermember_next_states($fromRole, $fromStateCode, $id = null, $parentid = null) {
		return $this->get_wf_next_states ( Workflow::__CTX_TYPE_CLUSTERMEMBER, $fromRole, $fromStateCode, $id, $parentid );
	}
	
	/**
	 * Get WF steps by 'Recordset' type context (Workflow class) for a profile type from a node with a State code
	 *
	 * @param string $fromRole        	
	 * @param integer $fromStateCode        	
	 * @param string $id        	
	 * @return array (WorkflowStep)
	 */
	public function get_wf_recordset_next_states($fromRole, $fromStateCode, $id = null, $parentid = null) {
		return $this->get_wf_next_states ( Workflow::__CTX_TYPE_RECORDSET, $fromRole, $fromStateCode, $id, $parentid );
	}
	
	/**
	 * Get WF steps by 'Dataset' type context (Workflow class) for a profile type from a node with a State code
	 *
	 * @param string $fromRole        	
	 * @param integer $fromStateCode        	
	 * @param string $id        	
	 * @return array (WorkflowStep)
	 */
	public function get_wf_dataset_next_states($fromRole, $fromStateCode, $id = null, $parentid = null) {
		return $this->get_wf_next_states ( Workflow::__CTX_TYPE_DATASET_EFP, $fromRole, $fromStateCode, $id, $parentid );
	}
	
	/**
	 * Returns from a valid set of steps a specif step as validation and to run post action
	 *
	 * @param
	 *        	array (WorkflowStep)
	 * @param integer $statusto        	
	 * @param integer $statusfrom        	
	 * @param string $role        	
	 * @return WorkflowStep|NULL
	 */
	public static function search_step($steps, $statusto, $statusfrom = null, $role = null) {
		foreach ( $steps as $key => $value ) {
			// $step->to_statecode_id
			if ($value->to_statecode_id == $statusto) {
				return $value;
			}
		}
		
		return null;
	}
	
	/**
	 * removes from array set all workfloe steps not valid according to the defined rules, from the rule engine,
	 * on the proper item set in context
	 *
	 * @param
	 *        	array (WorkflowStep) $steps
	 */
	private function validate_steps(&$steps) {
		foreach ( $steps as $key => $value ) {
			if ($value->step_validation_rule != '') {
				if (! $this->run_rule ( $value->step_validation_rule )) {
					unset ( $steps [$key] );
				}
			}
		}
	}
	
	/**
	 * Runs if specified for the step a post action from the rule engine on the proper item set in context
	 *
	 * @param WorkflowStep $step        	
	 * @return boolean
	 */
	public function run_postaction_step(WorkflowStep $step) {
		$ret = false;
		
		if ($this->isdataset) {
			if ($step->post_action != null) {
				$ret = $this->run_wf_func ( 'post_action', $step->post_action );
			}
			if ($step->alerttypeid != null) {
				AlertManager::new_alert ( $step->alerttypeid, $step->to_role_id, Session::get_org ()->id, $this->context_item_id, null );
			}
		}
		return $ret;
	}
	
	/**
	 * Runs wf engine function
	 *
	 * @param integer $rulenumber        	
	 * @return boolean
	 */
	public function run_wf_func($name, $id) {
		$func = $name . $id;
		
		if (method_exists ( $this, $func )) {
			return $this->$func ();
		} else {
			return false;
		}
	}
	
	/**
	 * Runs rule before step action
	 *
	 * @param integer $rulenumber        	
	 * @return boolean
	 */
	public function run_rule($rulenumber) {
		if ($this->isdataset) {
			return $this->run_wf_func ( 'rule', $rulenumber );
		} else {
			return false;
		}
	}
	
	/**
	 * Cluster Rule
	 * At least one cluster member has status in (__CLUSTER_MEMBER_STATUS_DUPLICATE || __CLUSTER_MEMBER_STATUS_FOLLOWUPREQUIRED)
	 *
	 * @return boolean
	 */
	private function rule1() {
		$return = false;
		
		foreach ( $this->clustermembers as $cm ) {
			
			if (in_array ( $cm->responserecords_statuscodeid, array (
					__CLUSTER_MEMBER_STATUS_DUPLICATE,
					__CLUSTER_MEMBER_STATUS_FOLLOWUPREQUIRED 
			) )) {
				$return = true;
				break;
			}
		}
		return $return;
	}
	
	/**
	 * Cluster Rule
	 * ALL cluster member has status in (__CLUSTER_MEMBER_STATUS_NOTAPOTENCIALDUPLICATE)
	 *
	 * @return boolean
	 */
	private function rule2() {
		$return = false;
		$found = false;
		
		foreach ( $this->clustermembers as $cm ) {
			
			if ($cm->responserecords_statuscodeid != __CLUSTER_MEMBER_STATUS_NOTAPOTENCIALDUPLICATE) {
				$found = true;
				break;
			}
		}
		if (! $found) {
			$return = true;
		}
		
		return $return;
	}
	
	/**
	 * Cluster Rule
	 * No cluster member has status in (__CLUSTER_MEMBER_STATUS_POTENCIALDUPLICATE || __CLUSTER_MEMBER_STATUS_FOLLOWUPREQUIRED)
	 *
	 * @return boolean
	 */
	private function rule3() {
		$return = false;
		$found = false;
		
		foreach ( $this->clustermembers as $cm ) {
			
			if (in_array ( $cm->responserecords_statuscodeid, array (
					__CLUSTER_MEMBER_STATUS_POTENCIALDUPLICATE,
					__CLUSTER_MEMBER_STATUS_FOLLOWUPREQUIRED 
			) )) {
				$found = true;
				break;
			}
		}
		if (! $found) {
			$return = true;
		}
		
		return $return;
	}
	
	/**
	 * Cluster Rule
	 * ALL cluster member has status in (__CLUSTER_MEMBER_STATUS_NOTADUPLICATE || __CLUSTER_MEMBER_STATUS_NOTAPOTENCIALDUPLICATE)
	 *
	 * @return boolean
	 */
	private function rule4() {
		$return = false;
		$found = false;
		
		foreach ( $this->clustermembers as $cm ) {
			
			if (! in_array ( $cm->responserecords_statuscodeid, array (
					__CLUSTER_MEMBER_STATUS_NOTADUPLICATE,
					__CLUSTER_MEMBER_STATUS_NOTAPOTENCIALDUPLICATE 
			) )) {
				$found = true;
				break;
			}
		}
		if (! $found) {
			$return = true;
		}
		
		return $return;
	}
	
	/**
	 * Cluster Rule
	 * All Cluster existing recordsets must not have recordmaster created
	 *
	 * @return boolean
	 */
	private function rule5() {
		$return = false;
		
		if (! is_array ( $this->$recordsetmasters ) || count ( $this->$recordsetmasters ) == 0) {
			$return = true;
		}
	}
	
	/**
	 * Recordset Rule
	 * If master record exists must be in state __RECORD_STATUS_FORREVIEW
	 *
	 * @return boolean
	 */
	private function rule6() {
		$return = false;
		$found = false;
		
		foreach ( $this->$recordsetmasters as $rsm ) {
			
			if ($rsm->statuscodeid != __RECORD_STATUS_FORREVIEW) {
				$found = true;
				break;
			}
		}
		if (! $found) {
			$return = true;
		}
		
		return $return;
	}
	
	/**
	 * Cluster Member Rule
	 * Cluster must not be in __CLUSTER_STATUS_CLOSED || __CLUSTER_STATUS_RESOLVED
	 *
	 * @return boolean
	 */
	private function rule7() {
		$return = false;
		
		if (! in_array ( $this->cluster->statuscodeid, array (
				__CLUSTER_STATUS_CLOSED,
				__CLUSTER_STATUS_RESOLVED 
		) )) {
			$return = true;
			break;
		}
		
		return $return;
	}
	
	/**
	 * LLO Record Rule
	 * REcord can't be set to __RECORD_STATUS_INVALID if at any time has been published
	 *
	 * @return boolean
	 */
	private function rule8() {
		$return = false;
		
		$em = new EntityManager ( LLORecord::HISTORYENTITY );
		
		$list = $em->load ( "responsestatusid=:responsestatusid AND publischodeid = :publischodeid" , array(':responsestatusid' => $this->llorecord->id, ':publischodeid', __PUBLISHED_STATUS_PUBLISHED) );
		
		if (! isset ( $list ) || count ( $list ) == 0) {
			$return = true;
			break;
		}
		
		return $return;
	}
	
	/**
	 * Cluster Member Post Action
	 * Record set to status __RECORD_STATUS_UNDERREVIEW
	 *
	 * @return boolean
	 */
	private function post_action1() {
		$rec = new DataRecord ( $this->context_item_id );
		$rec->statuscodeid = __RECORD_STATUS_UNDERREVIEW;
		$rec->save ();
		
		return true;
	}
	
	/**
	 * Cluster Member Post Action
	 * Record set to status __RECORD_STATUS_VALID
	 *
	 * @return boolean
	 */
	private function post_action2() {
		$rec = new DataRecord ( $this->context_item_id);
		$rec->statuscodeid = __RECORD_STATUS_VALID;
		$rec->save ();
		
		return true;
	}
	
	/**
	 * Recordset Post Action
	 * All Recordset members status to __RECORD_STATUS_DUPLICATE, Cluster status to __CLUSTER_STATUS_CLOSED and
	 * MasterRecord set to __RECORD_STATUS_VALID
	 *
	 * @return boolean
	 */
	private function post_action3() {
		foreach ( $this->recordsetmembers [$this->context_item_id] as $rs ) {
			$rs->statuscodeid = __RECORD_STATUS_DUPLICATE;
			$rs->save ();
		}
		
		if (isset ( $this->recordsetmasters [$this->context_item_id] )) {
			$this->recordsetmasters [$this->context_item_id]->statuscodeid = __RECORD_STATUS_VALID;
			$this->recordsetmasters [$this->context_item_id]->save ();
		}
		
		$rec = new Cluster ( $this->context_item_id );
		$rec->statuscodeid = __CLUSTER_STATUS_CLOSED;
		$rec->save ();
		
		return true;
	}
	
	/**
	 * Recordset Post Action
	 * Cluster status to __CLUSTER_STATUS_CONFIRMED
	 *
	 * @return boolean
	 */
	private function post_action4() {
		$rec = new Cluster ( $this->context_item_id );
		$rec->statuscodeid = __CLUSTER_STATUS_CONFIRMED;
		$rec->save ();
		
		return true;
	}
	
	/**
	 * Recordset Post Action
	 * All Recordset members status to __RECORD_STATUS_UNDERREVIEW, Cluster status to __CLUSTER_STATUS_RESOLVED
	 *
	 * @return boolean
	 */
	private function post_action5() {
		foreach ( $this->recordsetmembers [$this->context_item_id] as $rs ) {
			$rs->statuscodeid = __RECORD_STATUS_UNDERREVIEW;
			$rs->save ();
		}
		
		$rec = new Cluster ( $this->context_item_id );
		$rec->statuscodeid = __CLUSTER_STATUS_RESOLVED;
		$rec->save ();
		
		return true;
	}
	
	/**
	 * LLO Recordset Post Action
	 * If LLO record status set to __RECORD_STATUS_UNDERREVIEW the publiscodeid set to __PUBLISHED_STATUS_UNPUBLISHED
	 *
	 * @return boolean
	 */
	private function post_action6() {
		$rec = new LLORecord ( $this->context_item_id );
		$rec->publishcodeid = __PUBLISHED_STATUS_UNPUBLISHED;
		$rec->save ();
		
		return true;
	}
}