package it.mice.voila.runtime.service;

import it.mice.voila.runtime.entity.Entity;
import it.mice.voila.runtime.entity.EntityDiffAdapter;
import it.mice.voila.runtime.exception.BusinessException;
import it.mice.voila.runtime.util.UserMessages;

import java.util.Collection;
import java.util.Map;

import org.pojomatic.diff.Differences;
import org.springframework.transaction.annotation.Transactional;

/**
 * Base entity service interface.
 *
 * @param <T>
 * TODO - optimize usage of Transactional !!!!
 */
@Transactional
public interface EntityService<T extends Entity> {
	/**
	 * L'implementazione di questo metodo deve fornire l'elenco di tutti gli elementi di
	 * tipo Entity presenti nel DB in una collection.
	 * 
	 * @return una collection di Entity presenti nel DB
	 */
	Collection<T> listAll();
	
	
	Collection<T> listAll(Boolean cacheable);

	/**
	 * L'implementazione di questo metodo deve fornire l'elenco di tutti gli elementi di
	 * tipo Entity presenti nel DB corrispondendi ai criteri di ricerca specificati. Gli
	 * entity From e To, vengono utilizzati come criterio di ricerca seguendo queste
	 * regole:
	 * <ul>
	 * <li>Per gli attributi testuali viene sempre osservata la sola presenza del valore
	 * nell'entity From e, se diverso da null, verrà utilizzato come criterio
	 * "contiene (LIKE)";</li>
	 * <li>Per gli attributi numerici e temporali:
	 * <ol>
	 * <li>se valorizzato solo l'attributo From verrà utilizzato come criterio
	 * "maggiore o uguale a";</li>
	 * <li>se valorizzato solo l'attributo To verrà utilizzato come criterio
	 * "minore o uguale a";</li>
	 * <li>se valorizzati entrambi gli attributi From e To verrà utilizzato come criterio
	 * "compreso tra";</li>
	 * </ol>
	 * </ul>
	 * 
	 * @param theEntityFrom
	 *            Entity {@link Entity} utilizzato come filtro (From).
	 * @param theEntityTo
	 *            Entity {@link Entity} utilizzato come filtro (To).
	 * @return una Collection di Entity. Attenzione, la Collection avrà size 0 se non
	 *         viene trovata alcuna occorrenza.
	 */
	Collection<T> search(T theEntityFrom, T theEntityTo);

	/**
	 * L'implementazione di questo metodo estende il servizio offerto dal metodo
	 * searchEntity in quanto determina i criteri di ricerca in base ad un criteria map
	 * specificato in input. Il criteria type da utilizzare per una determinata proprietà
	 * verrà ricercato nel criteria map secondo la logica dei bean properties (es.
	 * customerId, theEntityCategory.categoryDescription, ecc...). Si vedano i tipi
	 * definiti nella classe CriteriaType per un elenco completo dei criteria definiti.
	 * 
	 * @see #searchEntity(Entity, Entity)
	 * @param theEntityFrom
	 *            Entity {@link Entity} utilizzato come filtro (From).
	 * @param theEntityTo
	 *            Entity {@link Entity} utilizzato come filtro (To).
	 * @param criteriaMap
	 *            Map di search criteria.
	 * @return una Collection di Entity. Attenzione, la Collection avrà size 0 se non
	 *         viene trovata alcuna occorrenza.
	 */
	Collection<T> searchWithCriteria(T theEntityFrom,
			T theEntityTo, Map<String, Object> criteriaMap);

	/**
	 * L'implementazione di questo metodo estende il servizio offerto dal metodo
	 * searchEntity, in quanto consente la paginazione dei dati estratti tramite
	 * l'indicazione del primo record e del numero di records da estrarre.
	 * 
	 * @see #searchEntity(Entity, Entity)
	 * @param theEntityFrom
	 *            Entity {@link Entity} utilizzato come filtro (From).
	 * @param theEntityTo
	 *            Entity {@link Entity} utilizzato come filtro (To).
	 * @param firstResult
	 *            Primo record da estrarre.
	 * @param maxResult
	 *            Numero totale di records da estrarre.
	 * @return una Collection di Entity. Attenzione, la Collection avrà size 0 se non
	 *         viene trovata alcuna occorrenza.
	 */
	Collection<T> searchWithPage(T theEntityFrom,
			T theEntityTo, int firstResult, int maxResult);

	/**
	 * L'implementazione di questo metodo aggrega i servizi offerti dai metodi
	 * searchEntityWithCriteria e searchEntityWithPage in quanto consente sia la
	 * paginazione dei dati estratti, sia la possibilità di specificare criteri di ricerca
	 * specifici.
	 * 
	 * @see #searchEntityWithCriteria(Entity, Entity, Map)
	 * @see #searchEntityWithPage(Entity, Entity, int, int)
	 * @param theEntityFrom
	 *            Entity {@link Entity} utilizzato come filtro (From).
	 * @param theEntityTo
	 *            Entity {@link Entity} utilizzato come filtro (To).
	 * @param criteriaMap
	 *            Map di search criteria.
	 * @param firstResult
	 *            Primo record da estrarre.
	 * @param maxResult
	 *            Numero totale di records da estrarre.
	 * @return una Collection di Entity. Attenzione, la Collection avrà size 0 se non
	 *         viene trovata alcuna occorrenza.
	 */
	Collection<T> searchWithPageAndCriteria(T theEntityFrom,
			T theEntityTo, Map<String, Object> criteriaMap, int firstResult,
			int maxResult);

	/**
	 * L'implementazione di questo metodo estende il servizio offerto dal metodo
	 * searchEntityWithPageAndCriteria, in quanto aggiunge anche la possibilità di
	 * fornire una clausola order by. Inoltre il metodo si presta ad utilizzi con liste
	 * paginate di dati in quanto restituisce un Map contenente due elementi:
	 * <ol>
	 * <li>"list" - contiene la collection dei soli elementi estratti dal DB in base ai
	 * criteri di ricerca specificati ed alla paginazione;</li>
	 * <li>"size" - riporta il numero totale di occorrenze che sarebbero state estratte
	 * senza l'utilizzo della paginazione.</li>
	 * </ol>
	 * 
	 * @see #searchEntityWithPageAndCriteria(Entity, Entity, Map, int, int)
	 * @param theEntityFrom
	 *            Entity {@link Entity} utilizzato come filtro (From).
	 * @param theEntityTo
	 *            Entity {@link Entity} utilizzato come filtro (To).
	 * @param criteriaMap
	 *            Map di search criteria.
	 * @param firstResult
	 *            Primo record da estrarre.
	 * @param maxResult
	 *            Numero totale di records da estrarre.
	 * @param orderBy
	 *            clausola order by.
	 * @return una HashMap che riporta la collection di elementi estratti per la pagina
	 *         corrente richiesta, più il conteggio totale dei records che rispondono ai
	 *         criteri di ricerca specificati, senza tener conto della paginazione.
	 */
	Map<String, Object> searchWithCriteriaPagingAndSort(T theEntityFrom,
			T theEntityTo, Map<String, Object> criteriaMap, int firstResult,
			int maxResult, String orderBy);

	/**
	 * L'implementazione di questo metodo deve restituire il conteggio dei records
	 * qualificati in base ai criteri di ricerca specificati.
	 * 
	 * @param theEntityFrom
	 *            Entity {@link Entity} utilizzato come filtro (From).
	 * @param theEntityTo
	 *            Entity {@link Entity} utilizzato come filtro (To).
	 * @return Numero totale di records trovati.
	 */
	int count(T theEntityFrom, T theEntityTo);

	/**
	 * L'implementazione di questo metodo estende il servizio offerto dal metodo
	 * countEntity, in quanto determina i criteri di ricerca e conteggio in base ad un
	 * criteria map specificato in input. Il criteria type da utilizzare per una
	 * determinata proprietà verrà ricercato nel criteria map secondo la logica dei bean
	 * properties (es. customerId, theEntityCategory.categoryDescription, ecc...). Si
	 * vedano i tipi definiti nella classe CriteriaType per un elenco completo dei
	 * criteria definiti.
	 * 
	 * @see #countEntity(Entity, Entity)
	 * @param theEntityFrom
	 *            Entity {@link Entity} utilizzato come filtro (From).
	 * @param theEntityTo
	 *            Entity {@link Entity} utilizzato come filtro (To).
	 * @param criteriaMap
	 *            Map di search criteria.
	 * @return Numero totale di records trovati.
	 */
	int countWithCriteria(T theEntityFrom, T theEntityTo,
			Map<String, Object> criteriaMap);

	/**
	 * L'implementazione di questo metodo deve costruire un nuovo Entity.
	 * 
	 * @return Un oggetto Entity inizializzato.
	 */
	T newEntity();
	
	/**
	 * L'implementazione di questo metodo deve leggere un entity dal db accedendo per primary Key.
	 * 
	 * @return Un oggetto Entity inizializzato.
	 */
	T findByObjectKey(Object objectKey);

	/**
	 * L'implementazione di questo metodo deve caricare e restituire i dati di dettaglio
	 * di un Entity accedendo per chiave primaria.<br>
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} valorizzato della sola chiave primaria.
	 *            <p>
	 * @return il riferimento all'oggetto fornito in input, oppure null se non trovato.
	 */
	T detail(T theEntity);

	/**
	 * L'implementazione di questo metodo deve caricare e restituire i dati di dettaglio
	 * di un Entity accedendo per chiave primaria.<br>
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} valorizzato della sola chiave primaria.
	 *            <p>
	 * @return il riferimento all'oggetto fornito in input, oppure null se non trovato.
	 */
	T detail(T theEntity, boolean useAcl);
	/**
	 * Questo metodo carica e restituisce i dati di dettaglio di un Entity accedendo per
	 * chiave primaria.<br>
	 * A differenza del metodo findByPrimaryKey, esso evita la registrazione nella hibernate cache dell'oggetto
	 * appena letto, consentendo il suo utilizzo per dei check senza per questo incorrere nella noiosissima
	 * eccezione NonUniqueObjectException di hibernate.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} valorizzato della sola chiave primaria.
	 *            <p>
	 * @return il riferimento all'oggetto fornito in input, oppure null se non trovato.
	 */
	public T detailNoCache(T theEntity);

	/**
	 * L'implementazione di questo metodo deve controllare l'esistenza di un Entity
	 * accedendo per chiave primaria.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} valorizzato della sola chiave primaria.
	 *            <p>
	 * @return <code>true</code> se l'oggetto è stato trovato;<br>
	 *         <code>false</code> se l'oggetto non è stato trovato.
	 */
	boolean exist(T theEntity);

	/**
	 * L'implementazione di questo metodo deve effettuare l'inserimento di un Entity in
	 * base dati.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} completamente valorizzato.
	 * @return <code>true</code> se l'oggetto è stato inserito correttamente;<br>
	 *         <code>false</code> se l'oggetto non è stato inserito correttamente.
	 */
	T insert(T theEntity);

	/**
	 * L'implementazione di questo metodo deve effettuare l'inserimento di un Entity in
	 * base dati.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} completamente valorizzato.
	 * @param userId
	 *            oggetto user.
	 * @return <code>true</code> se l'oggetto è stato inserito correttamente;<br>
	 *         <code>false</code> se l'oggetto non è stato inserito correttamente.
	 */
	T insert(T theEntity, String userId);

	/**
	 * L'implementazione di questo metodo deve effettuare l'inserimento di un Entity in
	 * base dati.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} completamente valorizzato.
	 * @param userId
	 *            oggetto user.
	 * @return <code>true</code> se l'oggetto è stato inserito correttamente;<br>
	 *         <code>false</code> se l'oggetto non è stato inserito correttamente.
	 */
	T insert(T theEntity, String userId, EntityDiffAdapter diffAdapter);

	/**
	 * L'implementazione di questo metodo deve effettuare l'inserimento di un Entity in
	 * base dati.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} completamente valorizzato.
	 * @param userId
	 *            oggetto user.
	 * @return <code>true</code> se l'oggetto è stato inserito correttamente;<br>
	 *         <code>false</code> se l'oggetto non è stato inserito correttamente.
	 */
	T insert(T theEntity, String userId, EntityDiffAdapter diffAdapter, boolean checkParentRelation, boolean checkDuplicate);

	/**
	 * L'implementazione di questo metodo deve effettuare l'aggiornamento di un Entity in
	 * base dati.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} completamente valorizzato.
	 * @return <code>true</code> se l'oggetto è stato aggiornato correttamente;<br>
	 *         <code>false</code> se l'oggetto non è stato aggiornato correttamente.
	 */
	T update(T theEntity);

	/**
	 * L'implementazione di questo metodo deve effettuare l'aggiornamento di un Entity in
	 * base dati.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} completamente valorizzato.
	 * @param userId
	 *            oggetto user.
	 * @return <code>true</code> se l'oggetto è stato aggiornato correttamente;<br>
	 *         <code>false</code> se l'oggetto non è stato aggiornato correttamente.
	 */
	T update(T theEntity, String userId);

	/**
	 * L'implementazione di questo metodo deve effettuare l'aggiornamento di un Entity in
	 * base dati.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} completamente valorizzato.
	 * @param userId
	 *            oggetto user.
	 * @return <code>true</code> se l'oggetto è stato aggiornato correttamente;<br>
	 *         <code>false</code> se l'oggetto non è stato aggiornato correttamente.
	 */
	T update(T theEntity, String userId, EntityDiffAdapter diffAdapter);

	/**
	 * L'implementazione di questo metodo deve effettuare l'aggiornamento di un Entity in
	 * base dati.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} completamente valorizzato.
	 * @param userId
	 *            oggetto user.
	 * @return <code>true</code> se l'oggetto è stato aggiornato correttamente;<br>
	 *         <code>false</code> se l'oggetto non è stato aggiornato correttamente.
	 */
	T update(T theEntity, String userId, EntityDiffAdapter diffAdapter, boolean checkParentRelation);
	/**
	 * L'implementazione di questo metodo deve effettuare la cancellazione di un Entity
	 * in base dati.
	 * 
	 * @param theEntity
	 *            Entity {@link Entity} valorizzato della sola chiave primaria.
	 *            <p>
	 * @return <code>true</code> se l'oggetto è stato cancellato correttamente;<br>
	 *         <code>false</code> se l'oggetto non è stato cancellato correttamente.
	 */
	boolean delete(T theEntity);
	
	void ensureExist(T theEntity, UserMessages userMessages, String entityName);
	
	/**
	 * Abstract metodo che controlla l'esistenza di tutte le entita' padre relazione con
	 * l'elemento da inserire/aggiornare in base dati.
	 * 
	 * @param entity
	 *            implementazione dell'entity di cui controllare i riferimenti a padri.
	 */
	void checkParentRelation(T entity);
	
	/**
	 * Questo metodo controlla l'esistenza in base dati di Entity fornito in input,
	 * sollevando una BusinessException in caso affermativo.
	 * 
	 * @param theEntity
	 *            Entity di cui controllarne l'esistenza.
	 * @throws BusinessException
	 *             nel caso in cui Entity esiste già in base dati.
	 */
	void checkDuplicate(T theEntity);
	
	/**
	 * Extract the first entity from the collection or return null if the list is empty.
	 */
	T firstElement(Collection<T> list);
	
	/**
	 * Extract the first entity from the collection in the given map or return null if the list is empty.
	 */
	T firstElement(Map<String, Object> map);
	
	Differences computeDifferences(T theEntity);
}
