<?php
/**
 * **********************************************************************
 * Classe base para Model, em arquiteturas MVC
 * 
 * Todas as models da aplicacao devem extender esta classe base,
 * para que a integracao com Lumine seja feita.
 * 
 * No construtor da classe filha o objeto que a model representa
 * sera instanciado para utilizacao. 
 * 
 * @author Hugo Ferreira da Silva
 * @link http://www.hufersil.com.br
 * @package Lumine
 * 
 * **********************************************************************
 */ 

/**
 * Classe abstrata para servir como base para Models
 * 
 * @author Hugo Silva
 * @link http://www.hufersil.com.br
 * @package Lumine
 */
abstract class Lumine_Model extends Lumine_EventListener {
	
	/**
	 * Objeto que sera usado nos metodos padroes 
	 * @var Lumine_Base
	 */
	protected $obj;
	
	/**
	 * Numero de linhas encontradas
	 * @var int
	 */
	protected $rows = 0;
	
	/**
	 * Variaveis geradas dinamicamente
	 * @var array
	 */
	protected $vars = array();
	
	/**
	 * Tipos de eventos disparados
	 * @var array
	 */
	protected $_event_types  = array(
		Lumine_Event::PRE_INSERT,
		Lumine_Event::POS_INSERT,
		Lumine_Event::PRE_SAVE,
		Lumine_Event::POS_SAVE,
		Lumine_Event::PRE_GET,
		Lumine_Event::POS_GET,
		Lumine_Event::PRE_UPDATE,
		Lumine_Event::POS_UPDATE,
		Lumine_Event::PRE_DELETE,
		Lumine_Event::POS_DELETE,
		Lumine_Event::PRE_FIND,
		Lumine_Event::POS_FIND,
	);
	
	/**
	 * Inicia as configuracoes
	 * 
	 * <p>Sera sobrescrito nas classes filhas.<br>
	 * Utilizado primariamente para iniciar o objeto<br>
	 * de persistencia.</p>
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @return void
	 */
	public function __construct(){
		
		$this->obj->addEventListener(Lumine_Event::PRE_INSERT, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::POS_INSERT, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::PRE_GET, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::POS_GET, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::PRE_FIND, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::POS_FIND, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::PRE_SAVE, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::POS_SAVE, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::PRE_DELETE, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::POS_DELETE, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::PRE_UPDATE, array($this,'redispatchSQLEvent'));
		$this->obj->addEventListener(Lumine_Event::POS_UPDATE, array($this,'redispatchSQLEvent'));
		
	}

	/**
	 * Recupera um objeto pela chave primaria ou chave => valor
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param mixed $pk Nome da chave ou valor
	 * @param mixed $pkValue Valor do campo
	 * @return array Dados encontrados em formato de array associativo
	 */
	public function get($pk, $pkValue=null){
		$this->obj->reset();
		$this->obj->get($pk, $pkValue);
		
		return $this->obj->toArray();
	}
	
	/**
	 * Recupera uma lista de itens
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param array $filters Filtros a serem usados
	 * @param string $order Ordenacao dos resultados
	 * @param int $offset Inicio dos resultados
	 * @param int $limit Limite de itens
	 * @return array Lista de itens encontrados
	 */
	public function find(array $filters = array(), $order = '', $offset = null, $limit = null){
		$this->obj->reset();
		$this->obj->alias('o');
		
		foreach($filters as $key => $value){
			try {
				$field = $this->obj->_getField($key);
				switch($field['type']){
					case 'char':
					case 'varchar':
					case 'text':
					case 'enum':
					case 'blob':
					case 'longblob':
					case 'tinyblob':
						$this->obj->where('o.'.$key.' like ?', $value);
					break;
					
					// se nao for texto, nao fazemos por like
					// fazemos uma comparacao direta
					default:
						$this->obj->where('o.'.$key.' = ?', $value);
				}
			
			} catch(Exception $e) {
				// quando o campo que a pessoa tentou pegar nao existe
				// eh disparada uma excecao, mas neste caso nao eh um erro
				// por isso capturamos a excecao para que nao de problemas para o usuario
			}
		}
		
		// conta os registros
		$this->rows = $this->obj->count();
			
		// se informou uma ordem
		if(!empty($order)){
			$this->obj->order($order);
		}
		
		// limita e executa a consulta
		$this->obj->limit($offset, $limit)
			->find();
		
		return $this->obj->allToArray();
	}
	
	
	/**
	 * Insere os dados no banco
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param array $data Dados a serem persistidos
	 * @return int Codigo do registro inserido
	 */
	public function insert(array $data){
		$this->obj->reset();
		$this->obj->setFrom($data);
		$this->obj->insert();
		
		// pegamos a(s) chave(s) primaria(s)
		// so retornamos quando tem uma unica chave primaria
		$pk = $this->obj->_getPrimaryKeys();
		
		if(count($pk) == 1){
			$key = $pk[0]['name'];
			
			return $this->obj->$key;
		}
		
		return 0;
		
	}
	
	/**
	 * Salva os dados do registro
	 * 
	 * <p>Insere ou atualiza. <br>
	 * Caso exista a chave primaria, atualiza.<br>
	 * Se nao tiver, insere</p>
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param array $data Dados a serem persistidos
	 * @return int Codigo do registro salvo
	 */
	public function save(array $data){
		$this->obj->reset();
		$this->obj->setFrom($data);
		$this->obj->save();
		
		// pegamos a(s) chave(s) primaria(s)
		// so retornamos quando tem uma unica chave primaria
		$pk = $this->obj->_getPrimaryKeys();
		
		if(count($pk) == 1){
			$key = $pk[0]['name'];
			
			return $this->obj->$key;
		}
		
		return 0;
	}
	
	/**
	 * Remove registros pelo id
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param int $id Codigo do registro a ser removido
	 * @return void
	 */
	public function delete($id){
		$this->obj->reset();
		
		// pegamos a(s) chave(s) primaria(s)
		// so removemos quando tem uma unica chave primaria
		$pk = $this->obj->_getPrimaryKeys();
		
		if(count($pk) == 1){
			$key = $pk[0]['name'];
			
			$this->obj->$key = $id;
			$this->obj->delete();
		}
	}
	
	/**
	 * Atualiza registros baseados pelo id
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param int $id Codigo do registro a ser atualizado
	 * @param array $data Dados a serem atualizados
	 * @return void
	 */
	public function update($id, array $data){
		$this->obj->reset();
		$this->obj->setFrom($data);
		
		// pegamos a(s) chave(s) primaria(s)
		// so removemos quando tem uma unica chave primaria
		$pk = $this->obj->_getPrimaryKeys();
		
		if(count($pk) == 1){
			$key = $pk[0]['name'];
			
			$this->obj->$key = $id;
			$this->obj->update();
		}
	}
	
	/**
	 * Efetua um delete baseado em clausulas where
	 * 
	 * <p>Qualquer parametro depois de $clause sera usado como prepared statement
	 * para remocao dos dados.
	 * 
	 * Caso for usar prepared statement, colocar o alias do objeto como a letra "o" 
	 * </p>
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param string $clause condicao para remocao
	 * @return void
	 */
	public function deleteWhere($clause){
		$this->obj->reset();
		$this->obj->alias('o');
		$args = func_get_args();
		
		// se a pessoa passou parametros a mais do que a clausula
		if($args > 1){
			// entao eh prepared statement, chamamos o where com os argumentos
			call_user_func_array(array($this->obj,'where'), $args);
			
		} else {
			// NAO eh prepared statement, chamamos o where 
			$this->obj->where($clause);
		}
		
		$this->obj->delete(true);
	}
	
	/**
	 * Efetua um update baseado em clausulas where
	 * 
	 * <p>Qualquer parametro depois de $clause sera usado como prepared statement
	 * para atualizacao dos dados.
	 * Caso for usar prepared statement, colocar o alias do objeto como a letra "o" 
	 * </p>
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param array $data Dados a serem atualizados
	 * @param string $clause condicao para atualizacao
	 * @return void
	 */
	public function updateWhere(array $data, $clause){
		$this->obj->reset();
		$this->obj->setFrom($data);
		$this->obj->alias('o');
		
		$args = func_get_args();
		array_shift($args);
		
		// se a pessoa passou parametros a mais do que a clausula
		if($args > 1){
			// entao eh prepared statement, chamamos o where com os argumentos
			call_user_func_array(array($this->obj,'where'), $args);
			
		} else {
			// NAO eh prepared statement, chamamos o where 
			$this->obj->where($clause);
		}
		
		$this->obj->update(true);
	}
	
	/**
	 * Adiciona uma validacao ao objeto
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param string $type Tipo de validacao
	 * @param string $field Nome do campo que tera a validacao
	 * @param mixed $msg Mensagem de erro quando houver problema, nome da funcao ou array com o objeto e metodo
	 * @param int $min Minimo de caracteres ou valor minimo
	 * @param int $max Maximo de caracteres ou valor maximo
	 * @return void
	 */
	public function addValidation($type, $field, $msg, $min=null, $max=null){
		Lumine_Validator_PHPValidator::addValidation($this->obj,$field,$type,$msg,$min,$max);
	}
	
	/**
	 * Valida se as entradas nos campos estao de acordo com as regras de validacao
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param array $data Dados a serem validados
	 * @return array Lista contendo os erros encontrados
	 */
	public function validate(array $data){
		$this->obj->reset();
		$this->obj->setFrom($data);
		return $this->obj->validate();
	}
	
	/**
	 * Numero de linhas encontradas na ultima consulta
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @return int
	 */
	public function rows(){
		return $this->rows;
	}
	
	/**
	 * Set implicito
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param string $key
	 * @param mixed $val
	 * @return void
	 */
	public function __set($key, $val){
		$this->vars[$key] = $val;
	}
	
	/**
	 * Get implicito
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param $key
	 * @return mixed
	 */
	public function __get($key){
		if(!isset($this->vars[$key])){
			return null;
		}
		
		return $this->vars[$key];
	}
	
	/**
	 * Redispara eventos
	 * 
	 * @author Hugo Ferreira da Silva
	 * @link http://www.hufersil.com.br
	 * @param Lumine_SQLEvent $e
	 * @return void
	 */
	public function redispatchSQLEvent(Lumine_SQLEvent $e){
		$this->dispatchEvent($e);
	}
}


