package industries.doublehelix.conditionalfilter.filtertypes.flat;

import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * A filter for a string type
 * note that this filter is ALWAYS case insensitive in it's toString() or sql statement functions
 * @author klaue
 *
 */
public class StringFilter extends FlatFilter {
	private static final long serialVersionUID = 1L;
	public enum StringFilterMode {
		IS("is"), ISNOT("is not"), CONTAINS("contains"), CONTAINSNOT("does not contain");
		private String name;
		private StringFilterMode(String name) {
			this.name = name;
		}
		@Override
		public String toString() {
			return name;
		}
		public static StringFilterMode fromString(String name) {
			for (StringFilterMode sfmode : StringFilterMode.values()) {
				if (sfmode.toString().equals(name)) return sfmode;
			}
			return null;
		}
	}
	private StringFilterMode filterMode = null;
	protected String filterString = "";
	
//	public StringFilter(StringFilterMode filterMode, String filterName) {
//		super(filterName, null);
//		this.filterMode = filterMode;
//	}
	
	/**
	 * A filter for a string field
	 * Is always case insensitive
	 * @param filterMode the mode
	 * @param filterName the name of the filtered attribute 
	 * @param filterRawName the name of the filter for the generated SQL
	 */
	public StringFilter(StringFilterMode filterMode, String filterName, String filterRawName) {
		super(filterName, filterRawName);
		this.setFilterMode(filterMode);
	}
	
	/**
	 * A filter template for a string field
	 * Is always case insensitive
	 * @param filterMode the mode or null (disregarded) if used as a template
	 * @param filterName the name of the filtered attribute 
	 * @param filterRawName the name of the filter for the generated SQL
	 */
	public StringFilter(String filterName, String filterRawName) {
		super(filterName, filterRawName);
		this.setFilterMode(StringFilterMode.IS);
	}
	
	/**
	 * A filter template for a string field
	 * Is always case insensitive
	 * @param filterMode the mode or null (disregarded) if used as a template
	 * @param filterName the name of the filtered attribute 
	 */
	public StringFilter(String filterName) {
		super(filterName);
		this.setFilterMode(StringFilterMode.IS);
	}

	/**
	 * @return the string filtered for
	 */
	public String getString() {
		return filterString;
	}

	/**
	 * sets the string to filter for
	 * @param filterString
	 */
	public void setString(String filterString) {
		this.filterString = filterString;
	}

	/**
	 * @return the mode of filtering
	 */
	public StringFilterMode getFilterMode() {
		return filterMode;
	}
	
	/**
	 * sets the mode of filtering
	 * @param filterMode
	 */
	public void setFilterMode(StringFilterMode filterMode) {
		this.filterMode = filterMode;
	}
	
	/**
	 * set the conditions for this filter
	 * @param filterMode
	 * @param string
	 */
	public void setFilterConditions(StringFilterMode filterMode, String string) {
		setFilterMode(filterMode);
		setString(string);
	}
	
	@Override
	public String toString() {
		String field = getRealFilterField(false);
		
		StringBuffer buf = new StringBuffer();
		buf.append(field).append(" ").append(this.filterMode.toString()).append(" \"").append(this.getString()).append("\"");
		
		return buf.toString();
	}
	
	@Override
	public String getSqlWhere() {
		String field = getRealFilterField(true);
		
		StringBuffer buf = new StringBuffer();
		
		// for the "NOT" to include values where the column is NULL, this is neccessary:
		if (this.filterMode == StringFilterMode.CONTAINSNOT || this.filterMode == StringFilterMode.ISNOT) {
			buf.append("(").append(field).append(" IS NULL OR ");
		}
		
		buf.append(prepareFieldForSQL(field));
		
		switch(this.filterMode) {
			case CONTAINSNOT:
				buf.append(" not "); // fall through
			case CONTAINS:
				buf.append(" like '%").append(this.filterString.toLowerCase()).append("%'");
				break;
			case IS:
				buf.append(" = '").append(this.filterString.toLowerCase()).append("'");
				break;
			case ISNOT:
				buf.append(" != '").append(this.filterString.toLowerCase()).append("'");
				break;
		}
		
		if (this.filterMode == StringFilterMode.CONTAINSNOT || this.filterMode == StringFilterMode.ISNOT) {
			buf.append(")");
		}
		
		return buf.toString();
	}

	@Override
	public String getPreparedStatementWhereString() {
		if (this.filterRawName == null && this.filterName == null) throw new IllegalArgumentException("filtername not set");
		String field = this.filterRawName == null ? this.filterName : this.filterRawName;
		
		StringBuffer buf = new StringBuffer();
		
		// for the "NOT" to include values where the column is NULL, this is neccessary:
		if (this.filterMode == StringFilterMode.CONTAINSNOT || this.filterMode == StringFilterMode.ISNOT) {
			buf.append("(").append(field).append(" IS NULL OR ");
		}
		
		buf.append(prepareFieldForSQL(field));
		
		switch(this.filterMode) {
			case CONTAINSNOT:
				buf.append(" not "); // fall through
			case CONTAINS:
				buf.append(" like ?");
				break;
			case IS:
				buf.append(" = ?");
				break;
			case ISNOT:
				buf.append(" != ?");
				break;
		}
		
		if (this.filterMode == StringFilterMode.CONTAINSNOT || this.filterMode == StringFilterMode.ISNOT) {
			buf.append(")");
		}
		
		return buf.toString();
	}

	@Override
	public int addPreparedStatementValues(PreparedStatement ps, int currentIndex) throws SQLException {
		String valToFilter = prepareFilterStringForSQL();
		ps.setString(currentIndex, valToFilter);
		return ++currentIndex;
	}
	
	protected String prepareFilterStringForSQL() {
		String valToFilter = this.filterString.toLowerCase();
		if (this.filterMode == StringFilterMode.CONTAINSNOT || this.filterMode == StringFilterMode.CONTAINS) {
			// http://stackoverflow.com/a/8248052
			valToFilter = valToFilter.replace("!", "!!")
				    .replace("%", "!%")
				    .replace("_", "!_")
				    .replace("[", "![");
			valToFilter = "%" + valToFilter + "%";
		}
		return valToFilter;
	}
	
	protected String prepareFieldForSQL(String field) {
		return "LOWER(" + field + ")";
	}

	@Override
	public boolean isEmpty() {
		return this.filterString.isEmpty();
	}
	
}
