/**
 * AbstractSearchCriteriaBuilder.java
 * Created Aug 14, 2008
 * Copyright (c) TANDBERG Television 2007-2008
 */
package com.tandbergtv.watchpoint.pmm.web.search;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.apache.struts.action.ActionForm;

import com.tandbergtv.workflow.web.page.Field;
import com.tandbergtv.workflow.web.page.Page;
import com.tandbergtv.watchpoint.search.Entity;
import com.tandbergtv.workflow.driver.search.ListParameter;
import com.tandbergtv.workflow.driver.search.RangeParameter;
import com.tandbergtv.workflow.driver.search.SearchOperator;
import com.tandbergtv.workflow.driver.search.SearchParameterBase;
import com.tandbergtv.workflow.driver.search.SearchType;
import com.tandbergtv.workflow.driver.search.ValueParameter;
import com.tandbergtv.workflow.util.SearchCriteria;

/**
 * This class provides the basic mechanism for implementing conversion of a search query (an
 * HTTP GET request) into a SearchCriteria.
 * 
 * This complements the search page paint mechanism where a page containing arbitrary fields can 
 * be generated and then submitted.
 * 
 * @author Sahil Verma
 */
public abstract class AbstractSearchCriteriaBuilder {
	
	private static final Logger logger = Logger.getLogger(AbstractSearchCriteriaBuilder.class);
	
	/**
	 * Returns the search criteria
	 * 
	 * @return
	 */
	public SearchCriteria getSearchCriteria() {
		SearchCriteria criteria = getDefaultSearchCriteria();
		
		logger.debug("Request URL " + getRequest().getQueryString());
		
		addSearchCriteria(criteria);
		
		return criteria;
	}
	
	/**
	 * Builds search criteria by mapping each field in the search page to the request parameter
	 * values that have been submitted
	 * 
	 * @param criteria
	 */
	protected void addSearchCriteria(SearchCriteria criteria) {
		HttpServletRequest request = getRequest();
		Page page = getPage();
		
		for (Field field : page.getFields()) {
			String [] values = request.getParameterValues(field.getName());
			
			if (values == null || !field.hasValues(values))
				continue;
			
			logger.debug("Creating criterion for field " + field.getName());
			
			addSearchCriteria(criteria, field, values);
		}
	}
	
	/**
	 * Adds one or more search criterion to the specified criteria based on the field and associated
	 * values. This class takes care of the common case wherein only one criterion will get generated 
	 * for a field, but derived classes can choose to add more than one.
	 * 
	 * @param criteria
	 * @param field
	 * @param values
	 */
	protected void addSearchCriteria(SearchCriteria criteria, Field field, String... values) {
		Entity e = getEntity(criteria);
		SearchParameterBase parameter = getCriterion(field, field.getName(), values);
		
		e.addParameter(parameter);
	}
	
	/**
	 * Creates and returns a search criterion from the field and specified values
	 * 
	 * @param field
	 * @param name
	 * @param values
	 * @return a search parameter if values were available, null otherwise
	 */
	protected SearchParameterBase getCriterion(Field field, String name, String... values) {
		/* FIXME This should be field.getSearchParameter(name, values) instead */
		SearchType type = field.getSearchType();
		SearchOperator operator = field.getSearchOperator();
		
		if (field.getIsValueParameter())
			return new ValueParameter(name, type, false, values[0], operator);
		
		if (field.getIsListParameter()) {
			ListParameter parameter = new ListParameter(name, type, operator);
			
			for (String value : values)
				parameter.addValue(value);
			
			return parameter;
		}
		
		if (field.getIsRangeParameter()) {
			RangeParameter range = new RangeParameter(name, type);
			String from = values[0];
			String to = values[1];
			
			if (from.length() > 0)
				range.setFrom(from);
			
			if (to.length() > 0)
				range.setTo(to);
			
			return range;
		}
		
		throw new UnsupportedOperationException("Cannot create search parameter for field " + field.getName());
	}
	
	/**
	 * Returns the search page associated with this query
	 * 
	 * @return
	 */
	protected abstract Page getPage();
	
	/**
	 * Returns the default search criteria
	 * 
	 * @return
	 */
	protected abstract SearchCriteria getDefaultSearchCriteria();
	
	/**
	 * Returns the entity that is the target of the search. All criterion must get added to this
	 * entity.
	 * 
	 * @param criteria
	 * @return
	 */
	protected abstract Entity getEntity(SearchCriteria criteria);
	
	/**
	 * Returns the form that submits to the action
	 * 
	 * @return
	 */
	protected abstract ActionForm getForm();
	
	/**
	 * Returns the HTTP GET request
	 * 
	 * @return
	 */
	protected abstract HttpServletRequest getRequest();
}
