/**
 * TokenSearchService.java
 * Created Jun 11, 2007
 * Copyright (c) Tandberg Television 2007
 */
package com.tandbergtv.workflow.driver.search;

import static com.tandbergtv.workflow.driver.search.SearchKeyConstants.CHILD_TOKEN;
import static com.tandbergtv.workflow.driver.search.SearchKeyConstants.PARENT;
import static com.tandbergtv.workflow.driver.search.SearchKeyConstants.PROCESS_DEFINITION;
import static com.tandbergtv.workflow.driver.search.SearchKeyConstants.PROCESS_INSTANCE;
import static com.tandbergtv.workflow.driver.search.SearchKeyConstants.STATUS;
import static com.tandbergtv.workflow.driver.search.SearchKeyConstants.TOKEN_STATUS;
import static com.tandbergtv.workflow.driver.search.SearchKeyConstants.WO_START_DATE;
import static com.tandbergtv.workflow.driver.search.SearchParameterBase.DATE_FORMAT;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import com.tandbergtv.workflow.core.CustomToken;
import com.tandbergtv.workflow.core.WorkflowTemplate;
import com.tandbergtv.workflow.driver.service.ITokenSearchService;
import com.tandbergtv.workflow.util.SearchCriteria;

/**
 * Default token search implementation
 * 
 * @author Sahil Verma
 */
public class TokenSearchService implements ITokenSearchService {

	private SessionFactory factory;
	
	/**
	 * The well-known name of this service
	 */
	private static final String SERVICE_NAME = "Token Search Service"; 

	/**
	 * @param factory
	 */
	public TokenSearchService(SessionFactory factory) {
		super();
		this.factory = factory;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.workflow.driver.service.ITokenSearchService#count(com.tandbergtv.workflow.util.SearchCriteria)
	 */
	public int count(SearchCriteria criteria) {
		if (criteria == null)
			throw new IllegalArgumentException("Search criteria must not be null");
		
		Session session = null;
		int count = 0;
		
		try {
			session = factory.openSession();
			
			Criteria c = getBasicCountCriteria(session);
			
			c = addCriteria(criteria, c);

			c = addProcessCriteria(criteria, c);

			count = (Integer) c.uniqueResult();
		} finally {
			if (session != null)
				session.close();
		}
		
		return count;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.workflow.driver.service.ITokenSearchService#search(com.tandbergtv.workflow.util.SearchCriteria)
	 */
	@SuppressWarnings("unchecked")
	public Collection<CustomToken> search(SearchCriteria criteria) {
		if (criteria == null)
			throw new IllegalArgumentException("Search criteria must not be null");
		
		Session session = null;
		Collection<CustomToken> tokens = new ArrayList<CustomToken>();
		
		try {
			session = factory.openSession();
			
			Criteria c = getBasicCriteria(session);
			
			c = addCriteria(criteria, c);
			
			c = addProcessCriteria(criteria, c);

			tokens = c.list();
		} finally {
			if (session != null)
				session.close();
		}
		
		return tokens;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.workflow.core.service.Service#getServiceName()
	 */
	public String getServiceName() {
		return SERVICE_NAME;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.workflow.core.service.ServiceLifecycle#start()
	 */
	public void start() {
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.workflow.core.service.ServiceLifecycle#stop()
	 */
	public void stop() {
	}
	
	/**
	 * Creates the bare minimum criteria for a query
	 * 
	 * @param session
	 * @return
	 */
	private Criteria getBasicCriteria(Session session) {
		return session.createCriteria(CustomToken.class);
	}
	
	/**
	 * Creates the bare minimum criteria for a count query 
	 * 
	 * @param session
	 * @return
	 */
	private Criteria getBasicCountCriteria(Session session) {
		return getBasicCriteria(session).setProjection(Projections.rowCount());
	}
	
	/**
	 * Adds user-supplied criteria to the Hibernate criteria that we have
	 * 
	 * @param criteria
	 * @param c
	 * @return
	 */
	private Criteria addCriteria(SearchCriteria criteria, Criteria c) {
		/* Are we searching by token status? */
		ListParameter list = criteria.getParameter(TOKEN_STATUS, ListParameter.class);

		if (list != null)
			c.add(Restrictions.in(STATUS, list.getValues()));

		/* Are we searching for child tokens or just the roots? */
		ValueParameter parameter = criteria.getParameter(CHILD_TOKEN, ValueParameter.class);

		if (parameter != null && Boolean.TRUE.equals(parameter.getValue()))
			c.add(Restrictions.isNotNull(PARENT));
		else
			c.add(Restrictions.isNull(PARENT));
		
		return c;
	}
	
	/**
	 * Adds user-supplied process filtering criteria. 
	 * Default filter - all active processes created from {@link WorkflowTemplate}
	 * 
	 * @param criteria
	 * @param c
	 * @return
	 */
	private Criteria addProcessCriteria(SearchCriteria criteria, Criteria c) {
		c = c.createCriteria(PROCESS_INSTANCE)
			.add(Restrictions.eq("active", true));
		
		RangeParameter parameter = criteria.getParameter(WO_START_DATE, RangeParameter.class);
		
		if (parameter != null) {
			String from = parameter.getFrom();
			
			try {
				if (from != null)
					c.add(Restrictions.gt(WO_START_DATE, new SimpleDateFormat(DATE_FORMAT).parse(from)));
			} catch (ParseException e) {
				/* Don't bother adding the parameter, that's all */
			}
		}
		
		ListParameter list = criteria.getParameter(STATUS, ListParameter.class);

		if (list != null)
			c.add(Restrictions.in(STATUS, list.getValues()));

		return c.createCriteria(PROCESS_DEFINITION)
			.add(Restrictions.sqlRestriction("processDefinitionTypeId=2"));
	}
}
