/*
 * This used to have a license header that it was licensed by Gatespace in the year 2000. However, this was licensed
 * to the OSGi Alliance. A member donated this as ASL 2.0 licensed matching this project's default license.
 */
package aQute.lib.filter;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;

public class Filter {
	final char			WILDCARD	= 65535;

	final static int	EQ			= 0;
	final static int	LE			= 1;
	final static int	GE			= 2;

	// Extended operators
	final static int	NEQ			= 100;
	final static int	LT			= 101;
	final static int	GT			= 102;

	final static int	APPROX		= 3;

	final String		filter;
	final boolean		extended;

	abstract class Query {
		static final String	GARBAGE		= "Trailing garbage";
		static final String	MALFORMED	= "Malformed query";
		static final String	EMPTY		= "Empty list";
		static final String	SUBEXPR		= "No subexpression";
		static final String	OPERATOR	= "Undefined operator";
		static final String	TRUNCATED	= "Truncated expression";
		static final String	EQUALITY	= "Only equality supported";

		private String		tail;

		boolean match() throws Exception {
			tail = filter;
			boolean val = doQuery();
			if (tail.length() > 0)
				error(GARBAGE);
			return val;
		}

		private boolean doQuery() throws Exception {
			if (tail.length() < 3 || !prefix("("))
				error(MALFORMED);
			boolean val;

			switch (tail.charAt(0)) {
				case '&' :
					val = doAnd();
					break;
				case '|' :
					val = doOr();
					break;
				case '!' :
					val = doNot();
					break;
				default :
					val = doSimple();
					break;
			}

			if (!prefix(")"))
				error(MALFORMED);
			return val;
		}

		private boolean doAnd() throws Exception {
			tail = skip();
			boolean val = true;
			if (!tail.startsWith("("))
				error(EMPTY);
			do {
				if (!doQuery())
					val = false;
			} while (tail.startsWith("("));
			return val;
		}

		String skip() {
			String a = tail;
			do {
				a = a.substring(1);
			} while (a.length() > 0 && Character.isWhitespace(a.charAt(0)));
			return a;
		}

		private boolean doOr() throws Exception {
			tail = skip();
			boolean val = false;
			if (!tail.startsWith("("))
				error(EMPTY);
			do {
				if (doQuery())
					val = true;
			} while (tail.startsWith("("));
			return val;
		}

		private boolean doNot() throws Exception {
			tail = skip();
			if (!tail.startsWith("("))
				error(SUBEXPR);
			return !doQuery();
		}

		boolean doSimple() throws Exception {
			int op = 0;
			Object attr = getAttr();

			if (prefix("="))
				op = EQ;
			else if (prefix("<="))
				op = LE;
			else if (prefix(">="))
				op = GE;
			else if (prefix("~="))
				op = APPROX;
			else if (extended && prefix("!="))
				op = NEQ;
			else if (extended && prefix(">"))
				op = GT;
			else if (extended && prefix("<"))
				op = LT;
			else
				error(OPERATOR);

			return compare(attr, op, getValue());
		}

		boolean prefix(String pre) {
			if (!tail.startsWith(pre))
				return false;
			tail = tail.substring(pre.length());
			return true;
		}

		Object getAttr() throws Exception {
			int len = tail.length();
			int ix = 0;
			label: for (; ix < len; ix++) {
				switch (tail.charAt(ix)) {
					case '(' :
					case ')' :
					case '<' :
					case '>' :
					case '=' :
					case '~' :
					case '*' :
					case '\\' :
						break label;
				}
			}
			String attr = tail.substring(0, ix);
			tail = tail.substring(ix);
			return getProp(attr);
		}

		abstract Object getProp(String key) throws Exception;

		private String getValue() {
			StringBuilder sb = new StringBuilder();
			int len = tail.length();
			int ix = 0;
			label: for (; ix < len; ix++) {
				char c = tail.charAt(ix);
				switch (c) {
					case '(' :
					case ')' :
						break label;
					case '*' :
						sb.append(WILDCARD);
						break;
					case '\\' :
						if (ix == len - 1)
							break label;
						sb.append(tail.charAt(++ix));
						break;
					default :
						sb.append(c);
						break;
				}
			}
			tail = tail.substring(ix);
			return sb.toString();
		}

		void error(String m) throws IllegalArgumentException {
			throw new IllegalArgumentException(m + " " + tail);
		}

		@SuppressWarnings("unchecked")
		<T> boolean compare(T obj, int op, String s) {
			if (obj == null)
				return false;
			if ((op == EQ) && (s.length() == 1) && (s.charAt(0) == WILDCARD))
				return true;
			try {
				Class<T> numClass = (Class<T>) obj.getClass();
				if (numClass == String.class) {
					return compareString((String) obj, op, s);
				} else if (numClass == Character.class) {
					return compareString(obj.toString(), op, s);
				} else if (numClass == Long.class) {
					return compareSign(op, Long.valueOf(s).compareTo((Long) obj));
				} else if (numClass == Integer.class) {
					return compareSign(op, Integer.valueOf(s).compareTo((Integer) obj));
				} else if (numClass == Short.class) {
					return compareSign(op, Short.valueOf(s).compareTo((Short) obj));
				} else if (numClass == Byte.class) {
					return compareSign(op, Byte.valueOf(s).compareTo((Byte) obj));
				} else if (numClass == Double.class) {
					return compareSign(op, Double.valueOf(s).compareTo((Double) obj));
				} else if (numClass == Float.class) {
					return compareSign(op, Float.valueOf(s).compareTo((Float) obj));
				} else if (numClass == Boolean.class) {
					if (op != EQ)
						return false;
					int a = Boolean.valueOf(s).booleanValue() ? 1 : 0;
					int b = ((Boolean) obj).booleanValue() ? 1 : 0;
					return compareSign(op, a - b);
				} else if (numClass == BigInteger.class) {
					return compareSign(op, new BigInteger(s).compareTo((BigInteger) obj));
				} else if (numClass == BigDecimal.class) {
					return compareSign(op, new BigDecimal(s).compareTo((BigDecimal) obj));
				} else if (obj instanceof Collection< ? >) {
					for (Object x : (Collection< ? >) obj)
						if (compare(x, op, s))
							return true;
				} else if (numClass.isArray()) {
					int len = Array.getLength(obj);
					for (int i = 0; i < len; i++)
						if (compare(Array.get(obj, i), op, s))
							return true;
				} else {
					Constructor<T> constructor = numClass.getConstructor(String.class);
					T source = constructor.newInstance(s);
					if (op == EQ)
						return source.equals(obj);
					Comparable<T> a = Comparable.class.cast(source);
					Comparable<T> b = Comparable.class.cast(obj);
					return compareSign(op, a.compareTo((T) b));
				}
			} catch (Exception e) {}
			return false;
		}
	}

	class DictQuery extends Query {
		private Dictionary< ? , ? > dict;

		DictQuery(Dictionary< ? , ? > dict) {
			this.dict = dict;
		}

		@Override
		Object getProp(String key) {
			return dict.get(key);
		}
	}

	class MapQuery extends Query {
		private Map< ? , ? > map;

		MapQuery(Map< ? , ? > dict) {
			this.map = dict;
		}

		@Override
		Object getProp(String key) {
			return map.get(key);
		}
	}

	class GetQuery extends Query {
		private Get get;

		GetQuery(Get get) {
			this.get = get;
		}

		@Override
		Object getProp(String key) throws Exception {
			return get.get(key);
		}
	}

	public Filter(String filter, boolean extended) throws IllegalArgumentException {
		this.filter = filter;
		this.extended = extended;
		if (filter == null || filter.length() == 0)
			throw new IllegalArgumentException("Null query");
	}

	public Filter(String filter) throws IllegalArgumentException {
		this(filter, false);
	}

	public boolean match(Dictionary< ? , ? > dict) throws Exception {
		try {
			return new DictQuery(dict).match();
		} catch (IllegalArgumentException e) {
			return false;
		}
	}

	public boolean matchMap(Map< ? , ? > dict) throws Exception {
		try {
			return new MapQuery(dict).match();
		} catch (IllegalArgumentException e) {
			return false;
		}
	}

	public boolean match(Get get) throws Exception {
		try {
			return new GetQuery(get).match();
		} catch (IllegalArgumentException e) {
			return false;
		}
	}

	public String verify() throws Exception {
		try {
			new DictQuery(new Hashtable<Object,Object>()).match();
		} catch (IllegalArgumentException e) {
			return e.getMessage();
		}
		return null;
	}

	@Override
	public String toString() {
		return filter;
	}

	@Override
	public boolean equals(Object obj) {
		return obj instanceof Filter && filter.equals(((Filter) obj).filter);
	}

	@Override
	public int hashCode() {
		return filter.hashCode();
	}

	boolean compareString(String s1, int op, String s2) {
		switch (op) {
			case EQ :
				return patSubstr(s1, s2);
			case APPROX :
				return fixupString(s2).equals(fixupString(s1));
			default :
				return compareSign(op, s2.compareTo(s1));
		}
	}

	boolean compareSign(int op, int cmp) {
		switch (op) {
			case LE :
				return cmp >= 0;
			case GE :
				return cmp <= 0;
			case EQ :
				return cmp == 0;
			case NEQ :
				return cmp != 0;
			case LT :
				return cmp > 0;
			case GT :
				return cmp < 0;
			default : /* APPROX */
				return cmp == 0;
		}
	}

	String fixupString(String s) {
		StringBuilder sb = new StringBuilder();
		int len = s.length();
		boolean isStart = true;
		boolean isWhite = false;
		for (int i = 0; i < len; i++) {
			char c = s.charAt(i);
			if (Character.isWhitespace(c)) {
				isWhite = true;
			} else {
				if (!isStart && isWhite)
					sb.append(' ');
				if (Character.isUpperCase(c))
					c = Character.toLowerCase(c);
				sb.append(c);
				isStart = false;
				isWhite = false;
			}
		}
		return sb.toString();
	}

	boolean patSubstr(String s, String pat) {
		if (s == null)
			return false;
		if (pat.length() == 0)
			return s.length() == 0;
		if (pat.charAt(0) == WILDCARD) {
			pat = pat.substring(1);
			for (;;) {
				if (patSubstr(s, pat))
					return true;
				if (s.length() == 0)
					return false;
				s = s.substring(1);
			}
		}
		if (s.length() == 0 || s.charAt(0) != pat.charAt(0))
			return false;
		return patSubstr(s.substring(1), pat.substring(1));
	}
}
