package industries.doublehelix.conditionalfilter.filtertypes.container;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;

import industries.doublehelix.conditionalfilter.filtertypes.Filter;

/**
 * Filter for AND- (all), OR- (any) or negated AND (none)-groups
 * @author klaue
 *
 */
public class ContainerFilter extends Filter {
	private static final long serialVersionUID = 1L;

	public enum ContainerType {
		ANY("any"), ALL("all"), NONE("none");
		private String name;
		private ContainerType(String name) {
			this.name = name;
		}
		@Override
		public String toString() {
			return name;
		}
		public static ContainerType fromString(String name) {
			for (ContainerType ctype : ContainerType.values()) {
				if (ctype.toString().equals(name)) return ctype;
			}
			return null;
		}
	}
	
	private ContainerType type = null;
	private ArrayList<Filter> subFilters = new ArrayList<>();
	
	/**
	 * Make a new container filter of the given filter type.
	 * @param type
	 */
	public ContainerFilter(ContainerType type) {
		super(null);
		this.type = type;
	}

	/**
	 * @return the filter type
	 */
	public ContainerType getType() {
		return type;
	}

	/**
	 * @param type the filter type
	 */
	public void setType(ContainerType type) {
		this.type = type;
	}

	/**
	 * retrieve a list of this container filters sub-filters
	 * @return the filter list
	 */
	public ArrayList<Filter> getSubFilters() {
		return subFilters;
	}

	/**
	 * Set subfilters. Will replace existing ones
	 * @param subFilters
	 */
	public void setSubFilters(ArrayList<Filter> subFilters) {
		for (Filter filter : subFilters) {
			if (filter instanceof MasterFilter) {
				throw new IllegalArgumentException("New subfilters contain master filter(s) which cannot be set as a subfilter");
			}
		}
		
		this.subFilters = subFilters;
	}
	
	/**
	 * Add a filter to this container filters sub-filter list
	 * @param filter
	 */
	public void addFilter(Filter filter) {
		if (filter instanceof MasterFilter) {
			throw new IllegalArgumentException("Cannot set master filter as sub to any other filter");
		}
		this.subFilters.add(filter);
	}
	
	/**
	 * Remove the specific filter from this containers sub-filter list
	 * @param filter
	 */
	public void removeFilter(Filter filter) {
		this.subFilters.remove(filter);
	}
	

	
	/**
	 * Replaces a filter in this container filters sub-filter list
	 * @param newFilter the new filter to use
	 * @param oldFilter the old filter to replace
	 */
	public void replaceFilter(Filter newFilter, Filter oldFilter) {
		if (newFilter instanceof MasterFilter) {
			throw new IllegalArgumentException("Cannot set master filter as sub to any other filter");
		}
		int index = -1;
		for (int i = 0; i < subFilters.size(); ++i) {
			if (subFilters.get(i) == oldFilter) {
				index = i;
			}
		}
		if (index == -1) return;
		this.subFilters.set(index, newFilter);
	}
	
	@Override
	public String toString() {
		ArrayList<String> subToString = new ArrayList<>();
		for (int i = 0; i < subFilters.size(); ++i) {
			// if subfilter is itself conditional, split by newline
			if (subFilters.get(i) instanceof ContainerFilter) {
				String s = subFilters.get(i).toString();
				subToString.addAll(Arrays.asList(s.split("\\r?\\n")));
			} else {
				subToString.add(subFilters.get(i).toString());
			}
		}
		
		StringBuffer buf = new StringBuffer();
		buf.append("Match ").append(this.type.toString()).append(" of the following:");
		for (String line : subToString) {
			buf.append("\n>").append(line);
		}
		
		return buf.toString();
	}
	
	@Override
	public String getSqlWhere() {
		String bindingString = this.type == ContainerType.ALL ? " AND " : " OR "; // none, any = or
		
		StringBuffer buf = new StringBuffer();
		if (this.type == ContainerType.NONE) buf.append("NOT ");
		buf.append("(");
		for (int i = 0; i < subFilters.size(); ++i) {
			Filter f = subFilters.get(i);
			buf.append(f.getSqlWhere());
			if (i < subFilters.size() - 1) {
				buf.append(bindingString);
			}
		}
		buf.append(")");
		
		return buf.toString();
	}

	@Override
	public String getPreparedStatementWhereString() {
		String bindingString = this.type == ContainerType.ANY ? " OR " : " AND ";
		StringBuffer buf = new StringBuffer();
		
		if (this.type == ContainerType.NONE) buf.append("NOT ");
		buf.append("(");
		for (int i = 0; i < subFilters.size(); ++i) {
			Filter f = subFilters.get(i);
			buf.append(f.getPreparedStatementWhereString());
			if (i < subFilters.size() - 1) {
				buf.append(bindingString);
			}
		}
		buf.append(")");
		
		return buf.toString();
	}

	@Override
	public int addPreparedStatementValues(PreparedStatement ps, int currentIndex) throws SQLException {
		int idx = currentIndex;
		for (int i = 0; i < subFilters.size(); ++i) {
			Filter f = subFilters.get(i);
			idx = f.addPreparedStatementValues(ps, idx);
		}
		return idx;
	}

	@Override
	public boolean isEmpty() {
		return this.subFilters.isEmpty();
	}
	
	/**
	 * Returns the total count of filters in this containerfilter and any other container filters that may be contained within.
	 * @return total number of filters
	 */
	public int getSize() {
		return getSizeOfFilterTree(this);
	}
	
	/**
	 * Returns the total count of filters in this containerfilter and any other container filters that may be contained within.
	 * @return total number of filters
	 */
	public int getNumberOfContainedFilters() {
		return getSize();
	}
	
	private int getSizeOfFilterTree(ContainerFilter parent) {
		int size = 0;
		for (Filter filter : parent.getSubFilters()) {
			++size;
			if (filter instanceof ContainerFilter) {
				size += getSizeOfFilterTree((ContainerFilter)filter);
			}
		}
		return size;
	}
}
