/**
 * AssetSearchServiceImpl.java
 * Created Feb 5, 2009
 * Copyright (c) Tandberg Television 2009
 */
package com.tandbergtv.metadatamanager.search;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.tandbergtv.metadatamanager.model.Asset;
import com.tandbergtv.metadatamanager.model.AssetState;
import com.tandbergtv.watchpoint.search.Entity;
import com.tandbergtv.watchpoint.search.Join;
import com.tandbergtv.watchpoint.search.QueryBuilder;
import com.tandbergtv.workflow.driver.search.SearchOperator;
import com.tandbergtv.workflow.driver.search.SearchType;
import com.tandbergtv.workflow.driver.search.SortParameter;
import com.tandbergtv.workflow.driver.search.ValueParameter;
import com.tandbergtv.workflow.util.SearchCriteria;

/**
 * Search service implementation
 * 
 * @author Sahil Verma
 */
public class AssetSearchServiceImpl implements AssetSearchService {

	private HibernateTemplate hibernateTemplate;

	private static final Logger logger = Logger
			.getLogger(AssetSearchServiceImpl.class);

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.tandbergtv.metadatamanager.search.AssetSearchService#count(com.tandbergtv
	 * .workflow.util.SearchCriteria)
	 */
	@Override
	@Transactional
	public int count(final SearchCriteria criteria) {
		Integer count = (Integer) getHibernateTemplate().execute(
				new HibernateCallback() {
					public Object doInHibernate(Session session)
							throws HibernateException, SQLException {
						Query query = session
								.createQuery(getCountQuery(criteria));

						return query.uniqueResult();
					}
				});

		return count.intValue();
	}

	/**
	 * Returns the select HQL query for the specified criteria
	 * 
	 * @param criteria
	 * @return
	 */
	public String getQuery(SearchCriteria criteria) {
		return QueryBuilder.newInstance().buildQuery(criteria);
	}

	/**
	 * Returns the count HQL query for the specified criteria
	 * 
	 * @param criteria
	 * @return
	 */
	public String getCountQuery(SearchCriteria criteria) {
		return QueryBuilder.newInstance().buildCountQuery(criteria);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @seecom.tandbergtv.metadatamanager.search.AssetSearchService#search(com.
	 * tandbergtv.workflow.util.SearchCriteria)
	 */
	@Override
	@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly=true)
	public Collection<Asset> search(SearchCriteria criteria) {
		Collection<Asset> assets = new ArrayList<Asset>();
		final int index = criteria.getStartingRecordNumber();
		final int count = criteria.getRecordsCount();
		final String s = getQuery(criteria);

		logger.debug("Executing query :" + System.getProperty("line.separator")
				+ s);

		List<?> result = (List<?>) getHibernateTemplate().executeWithNativeSession(
				new HibernateCallback() {
					public Object doInHibernate(Session session)
							throws HibernateException, SQLException {
						session.enableFilter("relationRevisionFilter").setParameter("revisionParam", Integer.MAX_VALUE);
						Query query = session.createQuery(s);
						query.setFirstResult(index);
						query.setMaxResults(count == 0 ? Integer.MAX_VALUE
								: count);

						return query.list();
					}
				});

		assets = getAssets(result);
		for (Asset a : assets) {
			if (a.getRoot() != null) {
				a.getRoot().loadCompleteTree();
			} else {
				a.loadCompleteTree();
			}
		}

		return assets;
	}

	@SuppressWarnings("unchecked")
	@Override
	@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly=true)
	public Collection<Long> searchForAssetID(SearchCriteria criteria) {
		final int index = criteria.getStartingRecordNumber();
		final int count = criteria.getRecordsCount();
		final String s = getQuery(criteria);

		logger.debug("Executing query :" + System.getProperty("line.separator")
				+ s);

		List<?> result = (List<?>) getHibernateTemplate().executeWithNativeSession(
				new HibernateCallback() {
					public Object doInHibernate(Session session)
							throws HibernateException, SQLException {
						session.enableFilter("relationRevisionFilter").setParameter("revisionParam", Integer.MAX_VALUE);
						Query query = session.createQuery(s);
						query.setFirstResult(index);
						query.setMaxResults(count == 0 ? Integer.MAX_VALUE
								: count);

						return query.list();
					}
				});

		
		return (Collection<Long>) result;
	}
	
	/**
	 * @return the hibernateTemplate
	 */
	public HibernateTemplate getHibernateTemplate() {
		return hibernateTemplate;
	}

	/**
	 * @param hibernateTemplate
	 *            the hibernateTemplate to set
	 */
	public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
		this.hibernateTemplate = hibernateTemplate;
	}

	/**
	 * Munges the results into a form that we want. This is necessary in cases
	 * we're fetching more than just the asset.
	 * 
	 * @param result
	 * @return
	 */
	private Collection<Asset> getAssets(List<?> result) {
		Collection<Asset> assets = new ArrayList<Asset>();

		for (Object object : result) {
			Asset asset = null;

			if (object instanceof Asset)
				asset = Asset.class.cast(object);
			else
				asset = (Asset) ((Object[]) object)[0];

			assets.add(asset);
		}

		return assets;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public SearchCriteria getCriteria(String rootEntityAlias,
			SearchInfo searchInfo, SortInfo sortInfo, int revision) {

		com.tandbergtv.workflow.util.SearchCriteria crit = new com.tandbergtv.workflow.util.SearchCriteria();
		Entity assetEntity = new Entity(rootEntityAlias, Asset.class, "a");

		buildActiveAssetCriteria(assetEntity);

		if (searchInfo != null) {
			FieldInfo fieldInfo = searchInfo.getFields();
			

			// add the field criteria
			fieldInfo.createEntity(searchInfo.getProperty(), assetEntity);
		}

		// now add the sort criteria
		if (sortInfo != null) {
			if (sortInfo.getProperty() == null
					|| sortInfo.getProperty().equals("")) {
				SortParameter sortParam = new SortParameter(sortInfo
						.getSortItemName(), sortInfo.getOrder());
				assetEntity.addParameter(sortParam);
			} else {
				Entity sortField = new Entity("field", sortInfo.getProperty(),
						"f");
				// resolved problems when using 'distinct' with a CLOB column  
				List<String> selectPropertyNames = new ArrayList<String>();
				selectPropertyNames.add("value");
				sortField.setSelectPropertyNames(selectPropertyNames);
				
				sortField.setJoin(Join.LEFT_OUTER);
				sortField.setFetch(true);
				sortField.addParameter(new SortParameter("value", sortInfo
						.getOrder()));
				sortField.addParameter(new ValueParameter("ttvXPath",
						SearchType.STRING, sortInfo.getSortItemName(),
						SearchOperator.EQUAL));
				assetEntity.addParameter(sortField);
			}
		}

		buildRevsionCriteria(revision, assetEntity);

		crit.addParameter(assetEntity);

		return crit;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public SearchCriteria getRootAssetCriteria(String rootEntityAlias) {

		com.tandbergtv.workflow.util.SearchCriteria crit = new com.tandbergtv.workflow.util.SearchCriteria();
		Entity assetEntity = new Entity(rootEntityAlias, Asset.class, "a");

		assetEntity.addParameter(new ValueParameter(AssetSearchKey.ASSET_ROOT
				.toString(), SearchType.NUMERIC, (Object) null));

		buildActiveAssetCriteria(assetEntity);

		buildLatestRevisionCriteria(assetEntity);

		crit.addParameter(assetEntity);

		return crit;
	}

	/**
	 * adds a parameter to the entity to search for only active assets
	 * 
	 * @param assetEntity
	 */
	private void buildActiveAssetCriteria(Entity assetEntity) {
		assetEntity.addParameter(new ValueParameter(AssetSearchKey.ASSET_STATE
				.toString(), SearchType.NUMERIC, AssetState.ACTIVE.ordinal()));
	}

	/**
	 * adds parameter to the entity to retrieve only the latest revision of the
	 * asset
	 * 
	 * @param assetEntity
	 */
	private void buildLatestRevisionCriteria(Entity assetEntity) {
		// TODO: Implement once revisioning gets implemented
	}

	/**
	 * adds parameter to the entity to retrieve asset for the given revision
	 * 
	 * @param revision
	 * @param assetEntity
	 */
	private void buildRevsionCriteria(int revision, Entity assetEntity) {
		// TODO: Implement once revisioning gets implemented
	}
}