package com.tandbergtv.workflow.driver.search;

import org.apache.log4j.Logger;

import com.tandbergtv.workflow.util.SortingOrder;

/**
 * This class contains a information for a single search criterion for
 * work order searches.
 * 
 * @author inaqvi
 */
public abstract class SearchParameterBase {

	protected static final Logger logger = Logger.getLogger(SearchParameterBase.class);

	/*This constants identifys the value property for variable instances*/
	private static final String VALUE_PROPERTY = "value";
	
	public static final String PROCESS_ALIAS = "process";

	public static final String WORKFLOW_TEMPLATE_ALIAS = "template";
	
	public static final String TOKEN_ALIAS = "token";
	
	private static final String PROTECTION_KEY_ALIAS = "key";
	
	protected String fieldName = null;
	
	protected SearchType fieldType = null;
	
	protected boolean varInstance;

	protected String alias = null;
	
	protected int objectCount;
	
	protected SortingOrder sortingOrder;
	
	public static final String DATE_FORMAT = "yyyy-MM-dd";
	public static final String DATE_FORMAT_BETTER = "yyyy-MM-dd HH:mm:ss";
	public static final String DATE_FORMAT_BETTER_HQL = "yyyy-mm-dd hh24:mi:ss";

	protected boolean ignoreCase;
	
	/**
	 * Creates a SearchParameterBase
	 */
	protected SearchParameterBase(String name) {
		this(name, SearchType.STRING);
	}
	
	/**
	 * Creates a SearchParameterBase
	 * @param fieldType
	 */
	protected SearchParameterBase(String name,SearchType fieldType) {
		this(name, fieldType, false);
	}

	/**
	 * Creates a SearchParameterBase
	 * @param fieldType
	 * @param varInstance
	 */
	protected SearchParameterBase(String name,SearchType fieldType, boolean varInstance) {
		this.fieldName = name;
		this.fieldType = fieldType;
		this.varInstance = varInstance;
	}
	
	/**
	 * Returns varInstance.
	 * @return
	 */
	public boolean isVarInstance() {
		return varInstance;
	}
	
	/**
	 * Sets the varInstance value.
	 * @param varInstance
	 */
	public void setVarInstance(boolean varInstance) {
		this.varInstance = varInstance;
	}
	
	/**
	 * Returns the field type.
	 * @return
	 */
	public SearchType getFieldType() {
		return fieldType;
	}
	
	/**
	 * Sets the field type.
	 * @param fieldType
	 */
	public void setFieldType(SearchType fieldType) {
		this.fieldType = fieldType;
	}
	
	/**
	 * Gets the alias.
	 * @return
	 */
	public String getAlias() {
		return alias;
	}

	/**
	 * Sets the alias.
	 * @param alias
	 */
	void setAlias(String alias) {
		this.alias = alias;
	}
	
	/**
	 * Sets the field name.
	 * @param fieldName
	 */
	void setFieldName(String fieldName) {
		this.fieldName = fieldName;
	}

	/**
	 * Gets the field name.
	 * @return
	 */
	public String getFieldName() {
		return fieldName;
	}
	
	/**
	 * Gets the sorting order.
	 * @return
	 */
	public SortingOrder getSortingOrder() {
		return sortingOrder;
	}

	/**
	 * Sets the sorting order.
	 * @param sortingOrder
	 */
	public void setSortingOrder(SortingOrder sortingOrder) {
		this.sortingOrder = sortingOrder;
	}
	
	/**
	 * Returns the FROM part for this search parameter
	 * 
	 * @param count
	 * @return
	 */
	public String getPartialFromClause(int count) {
		this.objectCount = count;
		String fromClause = "";
		if (isVarInstance()) {
			String varType = getVariableInstanceType();
			alias = varType.toLowerCase() + objectCount;
			if(this instanceof SortParameter)
				fromClause =  " Left Outer Join " + PROCESS_ALIAS + ".variables " + this.alias + 
				" With " + alias + ".name = '" + fieldName + "' AND " + alias + ".tokenVariableMap.id <> 0";
			else
				fromClause = varType + " " + alias;
		} else if (fieldName.equals(SearchKeyConstants.TOKEN_STATUS)) {
			fromClause = "CustomToken " + TOKEN_ALIAS + objectCount;
		} else if (fieldName.equals(SearchKeyConstants.ACCESSLEVEL)) {
			alias = PROTECTION_KEY_ALIAS + objectCount;
			fromClause = " ProtectionKey " + alias;
		}else
			alias = PROCESS_ALIAS;
		return fromClause;
	}
	
	/**
	 * Gets the VariableInstance type of a parameter based on its fieldType.
	 * 
	 * @param searchCriterion
	 */
	protected String getVariableInstanceType() {
		if (!isVarInstance()) {
			logger
					.error("Invalid search parameter, cannot return variableInstanceType for non-variableInstances.");
			return null;
		}
		return getVarInstanceTypeFromSearchType();
	}
	
	private String getVarInstanceTypeFromSearchType(){
//		 TODO: get var names from class names
		switch(fieldType){
		case STRING:
			return "StringInstance";
		case NUMERIC:
//			 TODO: Numeric can also mean DoubleInstance, we are right
//					now using only StringInstances so this is fine
//					but later on we will have to distinguish 
//					between LongInstance and DoubleInstance
			return "LongInstance";
		case DATE:
			return "DateInstance";
		}
		return null;
	}

	/**
	 * Returns the column name to sort this column by.
	 * @return
	 */
	public String getSortingColumn(){
		if(this.sortingOrder == null)
			throw new RuntimeException("Invalid attempt at getting sorting column for not sorting paramter " +
					this.fieldName + ".");
		if(!this.isVarInstance())
			return this.fieldName;
		if(!(this instanceof SortParameter))
			return VALUE_PROPERTY;
		
		switch(fieldType){
		case STRING:
			return "stringVal";
		case NUMERIC:
			/*Not using Long Type*/
			return "doubleVal";
		case DATE:
			return "dateVal";
		}
		return null;
	}
	
	/**
	 * Returns the where part of the query for this search parameter.
	 * 
	 * @param searchCriteria
	 * @return
	 */
	public String getPartialWhereClause() {
		String whereClause = "";			
		if (this instanceof OrParameterMap){				
			return getPredicate();
		} else if (isVarInstance() && !(this instanceof SortParameter)) {
			String varType = getVariableInstanceType();
			String alias = varType.toLowerCase() + objectCount;
			/* Joining VariableInstance with ProcessInstance */
			whereClause += PROCESS_ALIAS + " =" + alias + ".processInstance";
			/* Adding VariableInstance name predicate. */
			whereClause += " AND " + alias + ".name = '" + fieldName + "'";
			/* Adding VariableInstance value predicate. */
			if(ignoreCase) {
				whereClause += " AND lower(" + alias + ".value) " + getPredicate();
			} else {
				whereClause += " AND " + alias + ".value " + getPredicate();
			}
			/*
			 * We check to see that the tokenVariableMap of the variable instance is not null to avoid task level variable
			 * instances being included in the result.
			 */
			whereClause += " AND " + alias + ".tokenVariableMap is not null";
		} else if (fieldName.equals(SearchKeyConstants.ACCESSLEVEL)) {
			whereClause += alias + ".id " + getPredicate() + 
				" AND " + alias + " in elements(" + WORKFLOW_TEMPLATE_ALIAS + ".protectionKeys) ";
		} else if (fieldName.equals(SearchKeyConstants.TOKEN_STATUS)) {
			/* Joining ProcessInstance with Token*/
			whereClause += PROCESS_ALIAS + ".id = " + TOKEN_ALIAS + objectCount + ".processInstance";
			/* Adding token status predicate */
			whereClause += " AND " + TOKEN_ALIAS + objectCount + "." + SearchKeyConstants.STATUS + getStatusPredicate();
		}else if (!(this instanceof SortParameter)){
			if (fieldName.equals(SearchKeyConstants.STATUS.toString()))
				whereClause += PROCESS_ALIAS + "." + fieldName + getStatusPredicate();
			else
				whereClause += PROCESS_ALIAS + "." + fieldName + getPredicate();
		}
		return whereClause;
	}
	
	/**
	 * Gets the predicate for a SearchParameterBase for ProcessStatus.
	 * 
	 * @param searchCriteria
	 */
	private String getStatusPredicate() {
		if (this instanceof RangeParameter) {
			throw new RuntimeException("Status cannot be searched using a RangeParameter");
		} else if (this instanceof ListParameter) {
			ListParameter listParam = new ListParameter(SearchKeyConstants.STATUS);
			for (Object obj : ((ListParameter) this).getValues()) {
				String status = obj.toString();
				listParam.setFieldType(SearchType.NUMERIC);
				if ("ACTIVE".equals(status)) {
					listParam.addValue(0);
					listParam.addValue(1);
					listParam.addValue(7);
				} else {
					listParam.addValue(getOrdinal(status));
				}
			}
			return listParam.getPredicate();
		}
		
		/* No, I won't fix this copy-paste block */
		ListParameter listParam = new ListParameter(SearchKeyConstants.STATUS);
		String status = ((ValueParameter) this).getValue().toString();
		listParam.setFieldType(SearchType.NUMERIC);
		if ("ACTIVE".equals(status)) {
			listParam.addValue(0);
			listParam.addValue(1);
			listParam.addValue(7);
		} else {
			listParam.addValue(getOrdinal(status));
		}

		return listParam.getPredicate();
	}
	
	private int getOrdinal(String status) {
		/*
		 * AARGH!! Return values must match ProcessStatus - but we don't want a direct dependency. It
		 * just doesn't make sense to couple the search API to the process model.
		 */
		if ("CREATED".equalsIgnoreCase(status))
			return 0;
		if ("RUNNING".equalsIgnoreCase(status))
			return 1;
		if ("ERROR".equalsIgnoreCase(status))
			return 2;
		if ("PAUSED".equalsIgnoreCase(status))
			return 3;
		if ("QUEUED".equalsIgnoreCase(status))
			return 4;
		if ("CANCELLED".equalsIgnoreCase(status))
			return 5;
		if ("COMPLETED".equalsIgnoreCase(status))
			return 6;
		if ("BUSY".equalsIgnoreCase(status))
			return 7;
		if ("BRANCHED".equalsIgnoreCase(status))
			return 8;
		
		throw new IllegalArgumentException(status);
	}
	
	/**
	 * Gets the predicate of a specific parameter in its proper form.
	 */
	public abstract String getPredicate();

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return fieldName.hashCode();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final SearchParameterBase other = (SearchParameterBase) obj;
		if (fieldName == null) {
			if (other.fieldName != null)
				return false;
		} else if (!fieldName.equals(other.fieldName))
			return false;
		return true;
	}

	public boolean getIgnoreCase() {
		return ignoreCase;
	}

	public void setIgnoreCase(boolean ignoreCase) {
		this.ignoreCase = ignoreCase;
	}
}
