<?php

class ControleExpertManager {

	
	//********************************************************************************************
	//
	// VARIABLES
	// 
	//********************************************************************************************/
	
	private $etat_cible = array(
	
		0 => array(	"name" => 'Tous', 
					"where" => ''), 
					
		1 => array(	"name" => 'Présents', 
					"where" => 'AND ((code_sorti IS DISTINCT FROM 1) OR (date_sortie >= date(now())))'), 
					
		2 => array(	"name" => 'Non facturés', 
					"where" => "AND date_facture = '2099-12-31'::date") 
	);
	
	const JUSTIF_SEPARATOR = ":";
	
	// Nom de la base de données iCTI
	const DATABASE_NAME = 'iCTI';
	// Classe de la connexion à la base iCTI
	const DATABASE_CLASS = 'Database';
	
	// Tables sur lequelles on peut faire tourner des règles
	private $tables = array(
		// séjours
		0 => array(		"name" => 'v_sejours_1', 
						"fictive" => false,
						"from" => 'activite.v_sejours_1', 
						"fields" => array(), 
						"defaults" => array('v_sejours_1.sejour_id', 'v_sejours_1.no_sejour', 'v_sejours_1.date_sortie')), 
		// mouvements
		1 => array(		"name" => 'v_mouvements_sejour_r_1', 
						"fictive" => false,
						"from" => 'activite.v_mouvements_sejour_r_1', 
						"fields" => array(), 
						"defaults" => array('v_mouvements_sejour_r_1.sejour_id', 'v_mouvements_sejour_r_1.no_sejour', 'v_mouvements_sejour_r_1.date_sortie')), 
		// factures
		2 => array(		"name" => 'v_factures_reference_r_2', 
						"fictive" => false,
						"from" => 'activite.v_factures_reference_r_2', 
						"fields" => array(), 
						"defaults" => array('v_factures_reference_r_2.sejour_id', 'v_factures_reference_r_2.no_sejour', 'v_factures_reference_r_2.date_sortie')), 
		// lignes factures honoraires
		3 => array(		"name" => 'v_factures_lignes_h_r_1', 
						"fictive" => false,
						"from" => 'activite.v_factures_lignes_h_r_1', 
						"fields" => array(), 
						"defaults" => array('v_factures_lignes_h_r_1.sejour_id', 'v_factures_lignes_h_r_1.no_sejour', 'v_factures_lignes_h_r_1.date_sortie')), 
		// sql brut
		99 => array(	"name" => '', 
						"fictive" => true,
						"from" => '', 
						"fields" => array(), 
						"defaults" => array('v_sejours_1.sejour_id', 'v_sejours_1.no_sejour', 'v_sejours_1.date_sortie')) 
	);
	
	// Tableau des contrôles
	private $controls;
	
	// Connexion à la base de données
	private $database;
	
	// Indique le que le manager de controle expert est prêt à fonctionner (Connction DB OK et
	// initialisations réalisées
	private $ready;
	
	// Message d'erreur en cas d'erreur justement
	private $error_msg;
	
	// Indique si le module expert est activé
	private $expert_active;
	
	// Date à partir de laquelle les séjours sont contrôlables
	private $expert_date_debut;
	
	// Nombre de résultats positifs aux contrôles expert
	private $nb_results;
	
	// Message de retour
	private $msg;
	
	// Nombre de contrôles chargés
	private $nb_controls;
	
	// Emplacement absolu du fichier de traduction des champs des justificatifs
	private $dictionary;
	
	// Tableau des valeurs du dictionnaire de traduction
	private $dict_array;
	
	// Module qui exécute le contrôle expert (activite, pmsi...)
	private $module;
	
	// Nombre de controles activés
	private $nb_realisables;
	
	// Prestataire
	private $prestataire;
	
	
	//********************************************************************************************
	//
	// PUBLIC FUNCTIONS
	// 
	//********************************************************************************************/
	
	
	/**
	 * Constructeur
	 *
	 */
	function  __construct($schema) {
		
		$this->ready = false;
		$this->error_msg = '';
		$this->msg = '';
		$this->database = null;
		$this->expert_active = false;
		$this->expert_date_debut = '2013-01-01';
		$this->nb_controls = 0;
		$this->nb_results = 0;
		$this->dictionary = '';
		$this->module = $schema;
		$this->init();
	}
	
	
	
	public function __set($property, $value) {
		if (property_exists($this, $property)) {
			$this->$property = $value;
		}
		return $this;
	}
	
	
	
	public function __get($property) {
		if (property_exists($this, $property)) {
			return $this->$property;
		}
    }
	
	
	/**
	 * Execute les controles dont la liste est passée en paramètre.
	 * Si aucun paramètre n'est passé, tous les contrôles sont exécutés
	 *
	 * @param $oids : tableau des oid des controles à exécuter
	 */
	public function run_controls($oids=null) {
		
		// SI le module expert n'est pas activé, on sort
		if (!$this->expert_active) {
			$this->error_msg = "Le module expert n'est pas activé";
			return false;
		}
		
		// Récupère la liste des contrôles si ça n'a pas déjà été fait
		if (count($this->controls) == 0) {
			if (!$this->get_controls($oids)) {
				return false;
			};
		}
		
		//  EXECUTION DS CONTROLES	----------------------------------------------------------
		
		// Nombre de contrôles réalisés avec succès (avec ou sans résultats)
		$nb_succes = 0;
		
		// Un contrôle est réalisé s'il est actif et si le prestataire pour lequel il doit s'exécuter est celui configuré dans l'environnement
		$controles_realises = array();
		
		// Détermine si on exécute des contrôles précis via une liste d'oids ou si on les exécute tous
		$specific = is_array($oids);
		
		foreach($this->controls as $key => $control) {
			// Vérifie si le controle est bien dans la liste des controles à exécuter
			if (!$specific || in_array($control->oid, $oids)) {
				// Vérifie que le contrôle est réalisable (activé et bon prestataire)
				if ($control->realisable) {
					if($control->run()) {
						$nb_succes++;
					}
					else {
						// En cas d'erreur, on récupère le message d'erreur du controle
						$this->error_msg .= $control->error_msg;
					}
					$controles_realises[] = $control->oid;
				}
			}
		}
		
		// ECRITURE DES RESULTATS ------------------------------------------------------------
		
		
		// Désactive les indexes de la table de stockage des résultats
		$i = 0;
		while(++$i < 7) {
			$result = $this->database->exec("SELECT base.cti_disable_index('activite', 'i_expert_sejour_controle_$i')");
		}
		
		// Suppression des résultats précédents pour les contrôles exécutés
		if (count($controles_realises) > 0) {
			$request = "DELETE FROM activite.p_expert_sejour_controle WHERE controle_id IN (" . implode(',', $controles_realises) . ")";
			$result = $this->database->exec($request);
		}
		
		// Préparation de la requête de stockage des résultats
		$request = "INSERT INTO activite.p_expert_sejour_controle (sejour_id, no_sejour, date_sortie, controle_id, date_signalement, code_justificatif) VALUES ";
		
		// Nombre de séjours positifs aux controles experts
		$nb_results = 0;
		// Tableau de mise en forme de chaque résultat
		$values = array();
		
		foreach($this->controls as $control_id => $control) {
			
			// Mise à jour des infos du controle
			if ($control->last_execution_timestamp != '' && $control->last_execution_ok != '') {
				$update_request = "	UPDATE activite.t_expert_controle SET 
										last_execution_timestamp = '" . $control->last_execution_timestamp . "'::timestamp, 
										last_execution_ok = '" . $control->last_execution_ok . "'
									WHERE oid = " . $control_id . "::bigint;";
				$result = $this->database->exec($update_request);
				if ($result === false) {
					$this->error_msg .= "Impossible de mettre à jour les informations du contrôle " . $control->code . PHP_EOL;
				}
			}
			
			// Lecture des résultats de chaque contrôle
			foreach($control->results as $sejour_id => $result) {
			
				$nb_results ++;
			
				$date_sortie = $result['infos']['date_sortie'];
				$no_sejour = $result['infos']['no_sejour'];
				
				$justifs = array();
				foreach($result['justificatifs'] as $name => $justif) {
					$justifs[] = $this->decode_key($name) . self::JUSTIF_SEPARATOR . $justif;
				}
				// S'il y a plusieurs justificatifs, ils seront séparés par une barre verticale
				$code_justificatif = implode(' | ', $justifs);
				$values[] = "(" . $sejour_id . "::bigint, '" . $no_sejour . "'::text, '" . $date_sortie . "'::date, " . $control_id . "::bigint, CURRENT_DATE, '" . $code_justificatif . "'::text)";
			}
		}
		
		if ($nb_results > 0) {
			// la commande implode génère une erreur de dépassement de taille mémoire si trop de résultats sont retournés,
			// elle a été remplacée par une boucle sur les résultats
				$request .= implode(', ', $values);
			/*foreach($values as $value) {
				$request .= $value . ', ';
			}
			if (substr($request, -2) == ', ') {
				$request = substr($request, 0, -2);
			}
			*/
			$result = $this->database->exec($request);
			if ($result === false) {
				$this->error_msg = "Impossible de peupler la table des résultats des contrôles";
			}
		}
		
		// Ré-active les indexes de la table de stockage des résultats
		$i = 0;
		while(++$i < 7) {
			$result = $this->database->exec("SELECT base.cti_enable_index('activite', 'i_expert_sejour_controle_$i')");
		}

		
		// Nettoyage et rangement de la table des contrôles experts
		$result = $this->database->exec("VACUUM ANALYSE activite.p_expert_sejour_controle;");
			
		$pluriel_lances = $this->nb_controls > 1 ? " contrôles lancés, " : " contrôle lancé, ";
		$pluriel_realises = $this->nb_realisables > 1 ? " contrôles réalisés, " : " contrôle réalisé, ";
		$pluriel_resultats = $nb_results > 1 ? " résultats positifs. " : " résultat positif. ";
		$this->msg = PHP_EOL . $this->nb_controls . $pluriel_lances . PHP_EOL . $this->nb_realisables . $pluriel_realises . PHP_EOL . $nb_results . $pluriel_resultats;
		
		$this->database->close();
		
		if (strlen($this->error_msg) > 0) {
			return false;
		}
		else {
			return true;
		}
	}
	
	
	
	/**
	 * Récupère la liste des contrôles expert
	 */
	public function get_controls($oids=null) {
		
		$success = true;
		
		if (!$this->init()) {
            if($this->error_msg === ''){
                $this->error_msg = 'Initialisation des paramètres des contrôles expert en erreur';
            }
			return false;
		}
		
		$this->nb_controls = 0;
		$this->nb_realisables = 0;
		
		// Si aucune liste d'oids n'est passsée en paramètre, on récupère tous les contrôles
		$nb_oids = count($oids);
		if ($nb_oids == 1) {
			$where = " AND t_expert_controle.oid =" . $oids[0] . " ";
		}
		else if ($nb_oids > 1) {
			$where = " AND t_expert_controle.oid IN (" . implode(',', $oids) . ") ";
		}
		else {
			$where = "";
		}
		
		$request = "
			
			SELECT 
				t_expert_controle.oid as control_id,
				t_expert_controle.code,
				t_expert_controle.texte,
				t_expert_controle.date_validite_debut, 
				t_expert_controle.date_validite_fin,
				t_expert_controle.last_execution_timestamp,
				t_expert_controle.last_execution_ok,
				t_expert_controle.gravite_id as gravite,
				COALESCE(t_expert_controle.etat_cible, 0) as etat_cible,
				t_expert_controle.is_active,
				COALESCE(t_prestataires.alias, 'tous') as prestataire
			FROM
				activite.t_expert_controle 
				LEFT JOIN base.t_prestataires ON t_prestataires.oid = t_expert_controle.prestataire_id
			WHERE 1=1
		" . $where;
		$result = $this->database->exec($request);
		if ($result === false) {
			$this->error_msg = 'Erreur pendant la récupération des contrôles. Requête erronée';
			$success = false;
		}
		else {
			while ($row = pg_fetch_array($result)) {
			
				$control = new ControleExpert($this->database);
				$control->oid = $row['control_id'];
				$control->code = $row['code'];
				$control->texte = $row['texte'];
				$control->date_validite_debut = $row['date_validite_debut'];
				$control->date_validite_fin = $row['date_validite_fin'];
				$control->last_execution_timestamp = $row['last_execution_timestamp'];
				$control->last_execution_ok = $row['last_execution_ok'];
				$control->gravite = $row['gravite'];
				$control->actif = $row['is_active'] == "1" ? true : false;
				$control->expert_date_debut = $this->expert_date_debut;
				$control->etat_cible = $this->etat_cible[$row['etat_cible']];
				$control->tables = $this->tables;
				$control->prestataire = $row['prestataire'];
				if(!$control->get_rules()) {
					$this->error_msg = 'Impossible de récupérer la liste des règles du contrôle ' . $control->code;
					$success = false;
					break;
				}
				$this->controls[$row['control_id']] = $control;
				$this->nb_controls++;
				// Vérifie que le contrôle est actif et qu'il s'exécute sur le prestataire de l'environnement qui exécute les contrôles
				if ($control->actif && ($control->prestataire == 'tous' || $control->prestataire == $this->prestataire)) {
					$control->realisable = true;
					$this->nb_realisables++;
				}
				else {
					$control->realisable = false;
				}
			}
		}
		
		return $success;
	}
	
	
	
	//********************************************************************************************
	//
	// PRIVATE FUNCTIONS
	// 
	//********************************************************************************************/
	
	
	
	private function connect_db() {
	
		if (get_class($this->database) != self::DATABASE_CLASS) {
		
			$this->database = new Database(self::DATABASE_NAME);
			
			if (get_class($this->database) == self::DATABASE_CLASS) {
				return true;
			}
			else {
				return false;
			}
		}
		else {
			return true;
		}
	}
	
	
	/**
	 *  Remplace le code justificatif par un code à 3 ou 4 lettres plus court.
	 *  Le dictionnaire de traduction (format JSON) peut/doit être complété
	 *  modules/base/php/expert/dictionary.json
	 *
	 *	@param	$key	code à traduire
	 */
	private function decode_key($key) {
		
		// Chargement du dictionnaire de traduction des codes des justificatifs si jamais chargé auparavant
		if (!is_array($this->dict_array)) {
			if (file_exists($this->dictionary)) {
				
				// Parsage du JSON pour ne récupérer que la section du module qui va bien
				// http://stackoverflow.com/questions/4343596/parsing-json-file-with-php?answertab=votes#tab-top
				$string = file_get_contents($this->dictionary);
				$dict = json_decode($string, true);
				
				$jsonIterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($dict), RecursiveIteratorIterator::SELF_FIRST);
				foreach ($jsonIterator as $mod => $entries) {
					if ($mod == $this->module) {
						$this->dict_array = $entries;
						break;
					}
				}
			}
		}
		
		$s = strtolower($key);
		
		// Parcours du dictionnaire pour remplacer le justificatif
		if (is_array($this->dict_array)) {
			foreach ($this->dict_array as $old => $new) {
				$s = str_replace($old, $new, $s);
			}
		}
		
		return $s;
	}
	
	
	
	private function init() {
		
		
		if ($this->ready) {
			return true;
		}
		else {
			if (!$this->connect_db()) {
				$this->ready = false;
				$this->error_msg = 'Connexion impossible à la base CTI';
				return false;
			}
		}
		
		// Initialisation des variables
		$this->controls = array();
		$this->ready = true;
		
		// Récupération des champs pour les justificatifs. 
		// Pour chaque table, on récupère et on stocke le nom de chaque colonne
		// La table fictive 'SQL brut' est ignorée par cette boucle
		foreach($this->tables as $key => $table) {
			if (strlen($table['from']) > 0) {
				$fields = array();
				$request = "SELECT array_to_string(base.cti_array_accum(column_name),'|') AS fields
						FROM
						(
						SELECT column_name::text
						FROM information_schema.columns
						WHERE (table_schema || '.' || table_name) = '" . $table['from'] . "'
						ORDER BY ordinal_position
						) subview";
				$result = $this->database->exec($request);
				if ($result === false) {
					$this->error_msg = 'Récupération des tables _ requête erronée';
					$this->ready = false;
					return false;
				}
				$record = $this->database->nextRecordAssoc();
				$fields = explode("|",trim($record["fields"]));
				$this->tables[$key]['fields'] = $fields;
			}
		}
		
		// Initialisation des paramètres Expert
		$request = "	INSERT INTO activite.t_divers (code, texte, valeur, description)
						SELECT
							'EXPERT_ACTIVE', 
							'Module EXPERT activé', 
							'0', 
							'1=Activé'
						WHERE 'EXPERT_ACTIVE' NOT IN (SELECT code FROM activite.t_divers); ";
		$result = $this->database->exec($request);
		if ($result === false) {
			$this->error_msg = 'Initilisation des paramètres : Erreur d\'insertion dans la table activite.t_divers';
		}
		
		$request = "	INSERT INTO activite.t_divers (code, texte, valeur, description, valeur_date)
						SELECT
							'EXPERT_DATE_DEBUT',
							'Date séjours à contrôler',
							'20130101',
							'Date de début des séjours à contrôler',
							'20130101'
						WHERE 'EXPERT_DATE_DEBUT' NOT IN (SELECT code FROM activite.t_divers); ";
		$result = $this->database->exec($request);
		if ($result === false) {
			$this->error_msg = 'Initilisation des paramètres : Erreur d\'insertion de la date de début des séjours à contrôler';
		}
		
		// Désactivation controles inutiles
		$request = "	
			UPDATE activite.t_expert_controle
			SET is_active = subview.is_active
			FROM
			(
			SELECT t_expert_controle.oid, CASE WHEN MAX(to_id) > 0 THEN '1' ELSE '0' END AS is_active
			FROM activite.t_expert_controle
			JOIN
			(
			SELECT 'CTI_SEJ_AMB_03'::text AS controle_code, 'CTI_UF_AMBU'::text AS liste_code
			UNION
			SELECT 'CTI_MVT_SER_KO', 'CTI_SER_KO'
			UNION
			SELECT 'CTI_MVT_ETA_KO', 'CTI_ETA_KO'
			UNION
			SELECT 'CTI_SEJ_MED_KO', 'CTI_MED_KO'
			) subview
			ON t_expert_controle.code = controle_code
			LEFT JOIN activite.t_listes ON t_listes.code = liste_code
			LEFT JOIN activite.t_listes_contenu ON liste_id = t_listes.oid
			GROUP BY 1
			) subview
			WHERE  t_expert_controle.oid = subview.oid AND
				t_expert_controle.is_active <> subview.is_active
			;
			
			UPDATE activite.t_expert_controle
			SET is_active = '0'
			WHERE t_expert_controle.code = 'CTI_SEJ_SER_FINESS' AND
				is_active = '1' AND
				(SELECT MAX(finess_id) FROM activite.t_services_facturation ) = 0
			;
			";
		$result = $this->database->exec($request);
		if ($result === false) {
			$this->error_msg = 'Initilisation des paramètres : La désactivation des controles inutiles a échoué';
		}
		
		// Ménage / désactivés
		$request = "	
			DELETE
			FROM activite.p_expert_sejour_controle
			WHERE controle_id IN (SELECT oid FROM activite.t_expert_controle WHERE is_active <> '1')
			";
		$result = $this->database->exec($request);
		if ($result === false) {
			$this->error_msg = 'Initilisation des paramètres : La suppression des contrôles désactivés a échoué';
		}
		
		// Vérification de l'activation du module Expert
		$request = "	SELECT * FROM activite.t_divers WHERE code = 'EXPERT_ACTIVE' AND valeur = '1'; ";
		$result = $this->database->exec($request);
		if ($result !== false) {
			if (pg_num_rows($result) == 0) {
				$this->error_msg =  'Module Expert inactif';
				$this->expert_active = false;
			}
			else {
				$this->expert_active = true;
			}
		}
		
		// Récupération de la date de départ des controles expert
		$request = "	SELECT valeur_date FROM activite.t_divers WHERE code = 'EXPERT_DATE_DEBUT'; ";
		$result = $this->database->exec($request);
		if ($result !== false) {
			while ($row = pg_fetch_array($result)) {
				$this->expert_date_debut = $row['valeur_date'];
			}
		}
		
		// Vérification de l'existance de règles
		$request = "	SELECT * FROM pmsi.t_expert_controle_rule LIMIT 1; ";
		$result = $this->database->exec($request);
		if ($result !== false && pg_num_rows($result) == 0) {
			$this->error_msg =  'Pas de règles';
		}
		
		if (strlen($this->error_msg) > 0) {
			$this->ready = false;
		}
		return $this->ready;
	}
}

?>