<?php
/**
 * Define a workflow.
 * @author Administrateur
 * @category Plugin
 * @license MIT
 */
class Workflow extends Entity{

	public $id;
	public $label = 'Workflow Sans titre'; //libellé (Texte)
	public $icon = 'fas fa-network-wired'; //Icone (Icône)
	public $color = '#222222'; //Couleur (Couleur)


	//Types de workflows
	const TYPE_GLOBAL = 'global';
	const TYPE_ENTITY = 'entity';
	const TYPE_LIST = 'list';
	const TYPE_ALL = 'all';

	public $type = self::TYPE_GLOBAL; //Couleur (Couleur)
	public $entity = ''; //Entité lié au workflow si de type entité ou liste (slug)
	public $cause = '{}'; // Causes (json)

	protected $TABLE_NAME = 'workflow_workflow';
	public $fields = array(
		'id' => 'key',
		'label' => 'string',
		'icon' => 'string',
		'type' => 'string',
		'entity' => 'string',
		'cause' => 'longstring',
		'color' => 'string'
	);

	//Colonnes indexées
	public $indexes = array();

	//Retourn les types de workflow (général, liste, entité) uou un seul type si le slug est spécifié
    public static function types($slug = null){
		$types = array(
			self::TYPE_GLOBAL => array('label' => 'Général' ),
			self::TYPE_LIST => array('label' => 'Lié à une liste' ),
			self::TYPE_ENTITY => array('label' => 'Lié à une entité' ),
			self::TYPE_ALL => array('label' => 'Tous' ),
		);
		if(!isset($slug)) return $types;
		return isset($types[$slug]) ? $types[$slug] : $types[self::TYPE_GLOBAL];
	}

    //Regroupe les causes de possibles en fonction du slug et/ou du type de workflow fournis
    public static function causes($slug = null,$type = null ){
		$types = array();
		//Récupere les causes de bases dans le dossier cause
		foreach (glob(__DIR__.SLASH.'cause'.SLASH.'*.class.php') as $file) {
			require_once($file);
			$class = str_replace('.class.php','',basename($file));
			$infos = $class::manifest();
			if(isset($type) && !in_array($type, $class::manifest('type')) && !in_array(Workflow::TYPE_ALL, $class::manifest('type')) ) continue;
			$types[$infos['slug']] = $class;
		}
		//Récupere les causes en provenances d'autres plugins si existantes
		Plugin::callHook('workflow_cause',array(&$types));
		if(!isset($slug)) return $types;
		return isset($types[$slug]) ? $types[$slug] : $types['date'];
	}



	//Lance le workflow (vérifie les causes et lance les effets si les causes sont validées)
	public function run($causes = null,$parameters = array()){

		$logs = array('Verification des conditions');
		//Récuperation des causes du workflow (stockées en bdd en json)
		$this->cause = json_decode($this->cause,true);
		//Récuperation de tous les types de causes possibles (si non spécifié en premier parametre)
		if(!isset($causes)) $causes = self::causes();

		//Ajout des infos du forwlof courant au tableau de parametres utilisé par les causes et effets
		$parameters['workflow'] = $this->toArray();
		//On enleve le json des causes non utilisé pour gagner en lisibilité/place
		unset($parameters['workflow']['cause']);

		//Si le workflow est de type liste d'entité, on l'applique sur une boucle de cette liste
		if($this->type == self::TYPE_LIST){
			//On vérifie que la liste d'entité sur laquelle boucler est définie (pas le cas dans une execution manuelle)
			if(!isset($parameters['list'])) throw new Exception("Liste non fournie");
			//Pour chaque entité de la boucle
			foreach ($parameters['list'] as $i=>$current) {
				//On ajoute l'objet courant au tableau  de parametres utilisé par les causes et effets
				$parameters['current'] = $current;
				//si pas de condition on valide, sinon on les vérifie
				if(!empty($this->cause)){
					if(!self::cause_recursive_check($this->cause,$causes,0,$logs,$parameters)){
						$logs[] = 'Item #'.$i.'Conditions non valables';
						continue;
					}
				}
				$logs[] = 'Item #'.$i.'Conditions valables';
				$logs[] = 'Lancement des effets';
				//On execute les effets
				$logs = array_merge($logs,$this->run_effects($parameters));
			}
		}else{
			//si pas de condition on valide, sinon on les vérifie
			if(!empty($this->cause)){
				if(!self::cause_recursive_check($this->cause,$causes,0,$logs,$parameters)){
					$logs[] = 'Conditions non valables';
					$logs[] = 'Terminé';
					return $logs;
				}
			}
			$logs[] = 'Conditions valables';
			$logs[] = 'Lancement des effets';
			//On execute les effets
			$logs = array_merge($logs,$this->run_effects($parameters));
		}


		$logs[] = 'Terminé';

		return $logs;
	}


	/*
	Vérification récursive d'un tableau de causes pour le workflow courant
	 - $filters = le tableau de causes du workflow
	 - $causes = le tableau de stype de causes possibles
	 - iteration = le niveau de récursivité sur lequel on se trouve
	 - logs = tableau de logs retourné en fin de workflow (pour l'historique)
	 - parameters = tabeau de données contextuel utilisé par les causes/effets et pour le templating mustache des causes/effets
	*/
	function cause_recursive_check($filters,$causes,$iteration = 0,&$logs = array(),$parameters = array()){
		$filterResults = array();
		foreach ($filters as $i=>$filter) {
			//Si on est sur un sous groupe de causes, on execute une recursivité
			if(isset($filter['group'])){
				$filterResults['group'.$iteration] = self::cause_recursive_check($filter['group'],$causes,$iteration+1,$logs,$parameters) ? 'true' : 'false';
				continue;
			}else{
				//Si on est sur une cause
				$filter['operator'] = html_entity_decode($filter['operator']);
				//On récuepre la classe du type de la cause courante
				$cause = $causes[$filter['column']];
				//On execute la méthode de veritication (check) de la cause
				//définie dans la classe de type de cause qui retourne vrai ou faux
				// en fonction des données en parametres (filter : données de la cause, parameters : données du workflow/entité courant)
				$check = $cause::check($filter,$parameters);
				//On stocke la vérification bolééene sous forme de string dans un tableau des résultats de check ($filterResults)
				$filterResults[$cause::manifest('label').$i] = $check ? 'true' : 'false';
				if(isset($filter['join'])) $filterResults[] = $filter['join'];
			}
		}
		// Chaining des booléens de résultats
		// On récupere toutes les valeurs vrai / faux stockées dans $filterResults et on les concatene aux jointures (et/ou)
		//dans une seule chaine
		$evalChain = '$result = ';
		foreach($filterResults as $filterResult){
			if($filterResult==='true') $evalChain .= ' true ';
			if($filterResult==='false') $evalChain .= ' false ';
			if($filterResult=='and') $evalChain .= ' && ';
			if($filterResult=='or') $evalChain .= '|| ';
		}
		$evalChain .= ';';
		//Evaluation des booléens de la chaine pour determiner la réponse finale et stockage dans la variable $result
		eval($evalChain);
		$logs[] = '> Niveau '.$iteration.' : '. ($result ?  'VRAI': 'FAUX').' <small class="text-muted">'.json_encode($filterResults,JSON_PRETTY_PRINT).'</small>';
		return $result;
	}

	//Lancement des effets du workflow
	public function run_effects($parameters = array()){
		require_once(__DIR__.SLASH.'WorkflowEffect.class.php');
		//Récuperation des effets a executer dans l'ordre
		$effects = WorkflowEffect::loadAll(array('workflow'=>$this->id),array('sort'));
		$logs = array();
		foreach($effects as $effect){
			//Récuperation du tableau des champs de formulaire pré-setté lors de la creation de l'effet
			$effect->values = json_decode($effect->values,true);
			//execution de l'effet via la méthode run définie dans la classe de type effect ciblée
			try{
				$logs = array_merge($logs,$effect->run($parameters));
			}catch(Exception $e){
				$logs[] = 'Erreur: '.$e->getMessage();
			}

		}
		return $logs;
	}

}
?>