package com.tandbergtv.metadatamanager.search;

import static com.tandbergtv.workflow.driver.search.SearchType.DATE;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import org.w3c.util.DateParser;
import org.w3c.util.InvalidDateException;

import com.tandbergtv.metadatamanager.model.Asset;
import com.tandbergtv.metadatamanager.model.AssetState;
import com.tandbergtv.metadatamanager.util.DateUtil;
import com.tandbergtv.watchpoint.search.Entity;
import com.tandbergtv.watchpoint.search.NestedQueryEntity;
import com.tandbergtv.workflow.driver.search.RangeParameter;
import com.tandbergtv.workflow.driver.search.SearchOperator;
import com.tandbergtv.workflow.driver.search.SearchType;
import com.tandbergtv.workflow.driver.search.ValueParameter;

/**
 * Class that represents the search info for a metadata field
 * 
 * @author spuranik
 * 
 */
public class MetadataValueFieldInfo extends FieldInfo {

	SearchOperator operator;
	String value;

	/**
	 * 
	 * @param ttvxpath
	 * @param operator
	 * @param value
	 */
	public MetadataValueFieldInfo(String ttvxpath, SearchOperator operator,
			String value) {
		super();
		this.ttvxpath = ttvxpath;
		this.operator = operator;
		this.value = value;
		this.isConjunction = true;
	}

	/**
	 * 
	 * @param ttvxpath
	 * @param operator
	 * @param value
	 * @param isConjunction
	 */
	public MetadataValueFieldInfo(String ttvxpath, SearchOperator operator,
			String value, boolean isConjunction) {
		super();
		this.ttvxpath = ttvxpath;
		this.operator = operator;
		this.value = value;
		this.isConjunction = isConjunction;
	}

	/**
	 * @return the value
	 */
	public String getValue() {
		return value;
	}

	/**
	 * @param value
	 *            the value to set
	 */
	public void setValue(String value) {
		this.value = value;
	}

	/**
	 * @return the operator
	 */
	public SearchOperator getOperator() {
		return operator;
	}

	/**
	 * @param operator
	 *            the operator to set
	 */
	public void setOperator(SearchOperator operator) {
		this.operator = operator;
	}

	/**
	 * {@inheritDoc}
	 * 
	 */
	@Override
	public void createEntity(String property, Entity assetEntity) {
		//for isempty, we need to use the nestedQueryEntity
		if (operator.equals(SearchOperator.ISEMPTY)) {
			Entity internalAssetEntity = new Entity("rootAsset", Asset.class,
					"internalAsset");
			
			internalAssetEntity.addParameter(new ValueParameter(AssetSearchKey.ASSET_STATE
					.toString(), SearchType.NUMERIC, AssetState.ACTIVE.ordinal()));
			
			Entity internalField = new Entity("field",
					property, "internalField");
			internalField.addParameter(new ValueParameter("ttvXPath",
					SearchType.STRING, ttvxpath, SearchOperator.ISEMPTY));

			ValueParameter assetTypeParam = addAssetTypeCriteria();
			if (assetTypeParam != null) {
				internalField.addParameter(assetTypeParam);
			}

			internalAssetEntity.addParameter(internalField);

			NestedQueryEntity n = new NestedQueryEntity("rootAsset",
					Asset.class, assetEntity.getAlias());
			n.setOperator(SearchOperator.NOTIN);

			n.addParameter(internalAssetEntity);

			assetEntity.addParameter(n, isConjunction());
		} else {
			Entity field = new Entity("field", property, "f");

			if (operator.equals(SearchOperator.ISNOTEMPTY)) {
				field.addParameter(new ValueParameter("ttvXPath",
						SearchType.STRING, ttvxpath, operator));
			} else {
				//this is a hack. since we don't know datatype for customfields, we will search in all columns
				if(ttvxpath.contains("CustomField[@name")) {
					
					handleCustomFieldValueParameter(field, value);
					
					field.addParameter(new ValueParameter("ttvXPath",
							SearchType.STRING, ttvxpath, SearchOperator.EQUAL));
					
				} else {
					String valueColumnName = "";
					SearchType searchType = SearchType.STRING;

					field.addParameter(new ValueParameter("ttvXPath",
							SearchType.STRING, ttvxpath, SearchOperator.EQUAL));

					valueColumnName = getValueColumnName();
					searchType = getSearchTypeBasedOnColumn(valueColumnName);

					if (searchType == SearchType.DATE
							&& operator.equals(SearchOperator.EQUAL)) {
						// ensure that the value is converted to 'yyyy-MM-dd' format 
						RangeParameter range = handleEqualsOnDate(field, valueColumnName);
						field.addParameter(range);
					} else {
						if(searchType == SearchType.DATE) {
							//convert the incoming search date to UTC
							value = convertDate();
						}
						field.addParameter(new ValueParameter(valueColumnName,
								searchType, value, operator));
					}
				}
			}

			ValueParameter assetTypeParam = addAssetTypeCriteria();
			if (assetTypeParam != null) {
				field.addParameter(assetTypeParam);
			}

			assetEntity.addParameter(field, isConjunction());
		}
	}

	/**
	 * This method is a piece of beauty. it jumps through so many hoops
	 * (if/else, try/catch) to try to find the datatype for the custom field
	 *
	 * REMOVE this method once we have the functionality to find the datatype
	 * for a customfield
	 * 
	 * @param field
	 * @param value
	 */
	private void handleCustomFieldValueParameter(Entity field, String value) {
		boolean isInt = true;
		boolean isFloat = true;
		boolean isDate = false;
		boolean isString = false;

		try {
			Integer.parseInt(value);
			field.addParameter(new ValueParameter("intValue", SearchType.NUMERIC, value, operator));

		} catch (NumberFormatException e) {
			isInt = false;
		}

		if (!isInt) {
			try {
				Float.parseFloat(value);
				field.addParameter(new ValueParameter("floatValue", SearchType.NUMERIC, value,
						operator));

			} catch (NumberFormatException e) {
				isFloat = false;
			}

			if (!isFloat) {
				try {
					org.w3c.util.DateParser.parse(value);
					if (operator.equals(SearchOperator.EQUAL)) {
						RangeParameter range = handleEqualsOnDate(field, "dateValue");
						field.addParameter(range);
					} else {
						field.addParameter(new ValueParameter("dateValue", SearchType.DATE, value,
								operator));
					}
				} catch (NumberFormatException e) {
					isDate = false;
				} catch (InvalidDateException e) {
					isDate = false;
				}

				if (!isDate) {
					// its a string!!
					field.addParameter(new ValueParameter("value", SearchType.STRING, value,
							operator));
					isString = true;
				}
			}
		}
		// this check is to make sure we also check the value column. this is
		// needed coz a user might define a custom field as a string in spec
		// view but then enter a date value in the data. that will be stored in
		// the value column and not in date column
		if (!isString) {
			field.addParameter(new ValueParameter("value", SearchType.STRING, value, operator),
					false);
		}
	}

	/**
	 * @param field
	 * @param valueColumnName
	 */
	private RangeParameter handleEqualsOnDate(Entity field, String valueColumnName) {
		String buffer = convertDate();
		RangeParameter range = new RangeParameter(valueColumnName, DATE, buffer);
		range.setTo(buffer);
		
		return range;
	}

	/**
	 * @return
	 */
	private String convertDate() {
		Date d = null;
		d = DateUtil.convertDate(value);
		if(d == null) {
			return "";
		}
		Calendar calendar = new GregorianCalendar();
		calendar.setTime(d);
		StringBuffer buffer = new StringBuffer();
		buffer.append(calendar.get(Calendar.YEAR));
		buffer.append("-");
		buffer.append(twoDigit(calendar.get(Calendar.MONTH) + 1));
		buffer.append("-");
		buffer.append(twoDigit(calendar.get(Calendar.DAY_OF_MONTH)));
		return buffer.toString();
	}
	
	private String twoDigit(int i) {
		if (i >= 0 && i < 10) {
			return "0" + String.valueOf(i);
		}
		return String.valueOf(i);
	}
}
