package industries.doublehelix.conditionalfilter.filtertypes.flat;

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

/**
 * A filter for a number type
 * @author klaue
 *
 */
public class NumberFilter extends FlatFilter {
	private static final long serialVersionUID = 1L;
	public enum NumberFilterMode {
		IS("is"), ISNOT("is not"), LESSTHAN("is less than"), MORETHAN("is more than"), BETWEEN("is between"), NOTBETWEEN("is not between");
		private String name;
		private NumberFilterMode(String name) {
			this.name = name;
		}
		@Override
		public String toString() {
			return name;
		}
		public static NumberFilterMode fromString(String name) {
			for (NumberFilterMode nfmode : NumberFilterMode.values()) {
				if (nfmode.toString().equals(name)) return nfmode;
			}
			return null;
		}
	}
	private NumberFilterMode filterMode = null;
	private int firstFilterNumber = 0;
	private Integer secondFilterNumber = null; // may not be set
	
	/**
	 * A filter for a number field
	 * @param filterMode the mode
	 * @param filterName the name of the filtered attribute 
	 * @param filterRawName the name of the filter for the generated SQL
	 */
	public NumberFilter(NumberFilterMode filterMode, String filterName, String filterRawName) {
		super(filterName, filterRawName);
		setFilterMode(filterMode);
	}
	
	/**
	 * A filter template for a number field
	 * @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 NumberFilter(String filterName, String filterRawName) {
		super(filterName, filterRawName);
		setFilterMode(NumberFilterMode.IS);
	}
	
	/**
	 * A filter template for a number field
	 * @param filterMode the mode  or null (disregarded) if used as a template
	 * @param filterName the name of the filtered attribute 
	 */
	public NumberFilter(String filterName) {
		super(filterName);
		setFilterMode(NumberFilterMode.IS);
	}

	/**
	 * Returns the (first) filtered number. Short-hand for getFirstNumber()
	 * If it's a between-filter, it's inclusive. if it's less/more than, it's exclusive
	 * @return the number
	 */
	public int getNumber() {
		return getFirstNumber();
	}

	/**
	 * returns the first filtered number. If it's a between-filter, it's inclusive. if it's less/more than, it's exclusive
	 * @return the number
	 */
	public int getFirstNumber() {
		return firstFilterNumber;
	}

	/**
	 * returns the second filtered number, may be null if filter is not of a "between" type. If it's a between-filter, it's inclusive
	 * @return the number or null
	 */
	public Integer getSecondNumber() {
		return secondFilterNumber;
	}

	/**
	 * sets the first filtered number. If it's a between-filter, it's inclusive. if it's less/more than, it's exclusive
	 * @param number
	 */
	public void setFirstNumber(int number) {
		this.firstFilterNumber = number;
	}

	/**
	 * sets the second filtered number. If type of this filter is none that allows multiple numbers, this will be disregarded. If it's a between-filter, it's inclusive
	 * @param number
	 */
	public void setSecondNumber(int number) {
		this.secondFilterNumber = number;
	}

	/**
	 * @return The mode of this filter
	 */
	public NumberFilterMode getFilterMode() {
		return filterMode;
	}

	/**
	 * Sets the new mode of this filter.
	 * If it was a single-number filter and is now a two numbered one, the second number will be initialized to 0
	 * If it was a two number filter and is now a single number one, the second number will be reset to null
	 * @param filterMode
	 */
	public void setFilterMode(NumberFilterMode filterMode) {
		boolean wasTwoNumberFilter = this.filterMode != null && (this.filterMode == NumberFilterMode.BETWEEN || this.filterMode == NumberFilterMode.NOTBETWEEN);
		boolean isTwoNumberedFilter = filterMode == NumberFilterMode.BETWEEN || filterMode == NumberFilterMode.NOTBETWEEN;
		
		this.filterMode = filterMode;
		if (!isTwoNumberedFilter) {
			this.secondFilterNumber = null;
		} else {
			if (!wasTwoNumberFilter) {
				this.secondFilterNumber = new Integer(0);
			}
		}
	}
	
	/**
	 * set the conditions for this filter
	 * @param filterMode
	 * @param firstNumber
	 * @param secondNumber is disregarded if mode is not (not)between. if mode is (not)between, may not be null
	 */
	public void setFilterConditions(NumberFilterMode filterMode, int firstNumber, Integer secondNumber) {
		boolean twoValuesFilter = filterMode == NumberFilterMode.BETWEEN || filterMode == NumberFilterMode.NOTBETWEEN;
		if (twoValuesFilter && secondNumber == null) {
			throw new NullPointerException("Second number cannot be null if filtermode is (not)between");
		}
		
		setFilterMode(filterMode);
		setFirstNumber(firstNumber);
		// important, after setting filter mode which may reset second number
		if (twoValuesFilter) setSecondNumber(secondNumber);
	}
	
	@Override
	public String toString() {
		String field = getRealFilterField(false);
		
		StringBuffer buf = new StringBuffer();
		buf.append(field).append(" ").append(this.filterMode.toString()).append(" ");
		buf.append(this.getFirstNumber());
		
		if (this.filterMode == NumberFilterMode.BETWEEN || this.filterMode == NumberFilterMode.NOTBETWEEN) {
			buf.append(" and ").append(this.getSecondNumber());
		}
		
		return buf.toString();
	}
	
	@Override
	public String getSqlWhere() {
		String field = getRealFilterField(true);
		
		StringBuffer buf = new StringBuffer();
		buf.append(field);
		
		switch(this.filterMode) {
			case NOTBETWEEN:
				buf.append(" not "); // fall through
			case BETWEEN:
				buf.append(" between ");
				buf.append(this.firstFilterNumber).append(" AND ").append(this.secondFilterNumber);
				break;
			case IS:
				buf.append(" = ").append(this.firstFilterNumber);
				break;
			case ISNOT:
				buf.append(" != ").append(this.firstFilterNumber);
				break;
			case LESSTHAN:
				buf.append(" < ").append(this.firstFilterNumber);
				break;
			case MORETHAN:
				buf.append(" > ").append(this.firstFilterNumber);
				break;
		}
		
		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();
		
		buf.append(field);
		switch(this.filterMode) {
			case NOTBETWEEN:
				buf.append(" not "); // fall through
			case BETWEEN:
				buf.append(" between ? AND ?");
				break;
			case IS:
				buf.append(" = ?");
				break;
			case ISNOT:
				buf.append(" != ?");
				break;
			case LESSTHAN:
				buf.append(" < ?");
				break;
			case MORETHAN:
				buf.append(" > ?");
				break;
		}
		
		return buf.toString();
	}

	@Override
	public int addPreparedStatementValues(PreparedStatement ps, int currentIndex) throws SQLException {
		ps.setInt(currentIndex, this.firstFilterNumber);
		
		switch(this.filterMode) {
			case NOTBETWEEN: // fall through
			case BETWEEN:
				ps.setInt(currentIndex+1, this.secondFilterNumber);
				return currentIndex += 2;
			default:
				return ++currentIndex;
		}
	}

	@Override
	public boolean isEmpty() {
		return this.firstFilterNumber == 0 && (this.secondFilterNumber == null || this.secondFilterNumber == 0);
	}
	
}
