/*
 * ====================================================================
 * 
 * The ObjectStyle Group Software License, Version 1.0
 * 
 * Copyright (c) 2006 The ObjectStyle Group and individual authors of the
 * software. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if any, must
 * include the following acknowlegement: "This product includes software
 * developed by the ObjectStyle Group (http://objectstyle.org/)." Alternately,
 * this acknowlegement may appear in the software itself, if and wherever such
 * third-party acknowlegements normally appear.
 * 
 * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse or
 * promote products derived from this software without prior written permission.
 * For written permission, please contact andrus@objectstyle.org.
 * 
 * 5. Products derived from this software may not be called "ObjectStyle" nor
 * may "ObjectStyle" appear in their names without prior written permission of
 * the ObjectStyle Group.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * OBJECTSTYLE GROUP OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many individuals on
 * behalf of the ObjectStyle Group. For more information on the ObjectStyle
 * Group, please see <http://objectstyle.org/>.
 *  
 */
package org.objectstyle.wolips.eomodeler.core.model;

import java.text.ParseException;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.objectstyle.wolips.eomodeler.core.model.qualifier.EOAggregateQualifier;
import org.objectstyle.wolips.eomodeler.core.model.qualifier.EOAndQualifier;
import org.objectstyle.wolips.eomodeler.core.model.qualifier.EOKeyComparisonQualifier;
import org.objectstyle.wolips.eomodeler.core.model.qualifier.EOKeyValueQualifier;
import org.objectstyle.wolips.eomodeler.core.model.qualifier.EONamedQualifierVariable;
import org.objectstyle.wolips.eomodeler.core.model.qualifier.EONotQualifier;
import org.objectstyle.wolips.eomodeler.core.model.qualifier.EOOrQualifier;
import org.objectstyle.wolips.eomodeler.core.model.qualifier.EOQualifier;
import org.objectstyle.wolips.eomodeler.core.model.qualifier.EOQualifierBinding;
import org.objectstyle.wolips.eomodeler.core.model.qualifier.EOQualifierParser;
import org.objectstyle.wolips.eomodeler.core.model.qualifier.EOQualifierVariable;

public class EOQualifierFactory {
	private static class SelectorMap {
		private String _methodName;

		private String _operatorName;

		public SelectorMap(String methodName, String operatorName) {
			_methodName = methodName;
			_operatorName = operatorName;
		}

		public String getMethodName() {
			return _methodName;
		}

		public String getOperatorName() {
			return _operatorName;
		}
	}

	private static List<SelectorMap> _selectorMaps;

	static {
		_selectorMaps = new LinkedList<SelectorMap>();
		_selectorMaps.add(new SelectorMap("isEqualTo:", "="));
		_selectorMaps.add(new SelectorMap("isEqualTo", "="));
		_selectorMaps.add(new SelectorMap("isNotEqualTo:", "<>"));
		_selectorMaps.add(new SelectorMap("isNotEqualTo", "<>"));
		_selectorMaps.add(new SelectorMap("isLessThan:", "<"));
		_selectorMaps.add(new SelectorMap("isLessThan", "<"));
		_selectorMaps.add(new SelectorMap("isGreaterThan:", ">"));
		_selectorMaps.add(new SelectorMap("isGreaterThan", ">"));
		_selectorMaps.add(new SelectorMap("isLessThanOrEqualTo:", "<="));
		_selectorMaps.add(new SelectorMap("isLessThanOrEqualTo", "<="));
		_selectorMaps.add(new SelectorMap("isGreaterThanOrEqualTo:", ">="));
		_selectorMaps.add(new SelectorMap("isGreaterThanOrEqualTo", ">="));
		_selectorMaps.add(new SelectorMap("doesContain:", "contains"));
		_selectorMaps.add(new SelectorMap("doesContain", "contains"));
		_selectorMaps.add(new SelectorMap("isLike:", "like"));
		_selectorMaps.add(new SelectorMap("isLike", "like"));
		_selectorMaps.add(new SelectorMap("isCaseInsensitiveLike:", "caseInsensitiveLike"));
		_selectorMaps.add(new SelectorMap("isCaseInsensitiveLike", "caseInsensitiveLike"));

		// To correct the previous Cayenne syntax ... Just in case you had
		// either one in your fetch specs.
		_selectorMaps.add(new SelectorMap("likeIgnoreCase", "caseInsensitiveLike"));
		_selectorMaps.add(new SelectorMap("likeIgnoreCase:", "caseInsensitiveLike"));
	}

	public static EOQualifier fromString(String qualifierString) {
		try {
			EOQualifier qualifier = new EOQualifierParser().parseQualifier(qualifierString);
			return qualifier;
		} catch (ParseException e) {
			throw new RuntimeException("Failed to parse qualfier.", e);
		}
	}

	public static String toString(EOQualifier qualifier) {
		String qualifierString;
		if (qualifier == null) {
			qualifierString = null;
		} else {
			qualifierString = qualifier.toString();
		}
		return qualifierString;
	}

	protected static String operatorNameForMethodNamed(String methodName) {
		for (SelectorMap selectorMap : _selectorMaps) {
			if (selectorMap.getMethodName().equalsIgnoreCase(methodName)) {
				return selectorMap.getOperatorName();
			}
		}
		return methodName;
	}

	protected static String methodNameForOperatorNamed(String operatorName) {
		for (SelectorMap selectorMap : _selectorMaps) {
			if (selectorMap.getOperatorName().equalsIgnoreCase(operatorName)) {
				return selectorMap.getMethodName();
			}
		}
		return operatorName;
	}

	public static EOQualifier createQualifierFromQualifierMap(EOModelMap qualifierMap) {
		EOQualifier qualifier = null;
		if (qualifierMap != null) {
			String className = qualifierMap.getString("class", true);
			if ("EOAndQualifier".equals(className) || "com.webobjects.eocontrol.EOAndQualifier".equals(className)) {
				qualifier = new EOAndQualifier(EOQualifierFactory.createQualifiersFromQualifierMaps(qualifierMap.getList("qualifiers")));
			} else if ("EOOrQualifier".equals(className) || "com.webobjects.eocontrol.EOOrQualifier".equals(className)) {
				qualifier = new EOOrQualifier(EOQualifierFactory.createQualifiersFromQualifierMaps(qualifierMap.getList("qualifiers")));
			} else if ("EONotQualifier".equals(className) || "com.webobjects.eocontrol.EONotQualifier".equals(className)) {
				qualifier = new EONotQualifier(EOQualifierFactory.createQualifierFromQualifierMap(new EOModelMap(qualifierMap.getMap("qualifier"))));
			} else if ("EOKeyValueQualifier".equals(className) || "com.webobjects.eocontrol.EOKeyValueQualifier".equals(className)) {
				String key = qualifierMap.getString("key", true);
				Object value = EOQualifierFactory.createValue(qualifierMap.get("value"));
				String selectorName = EOQualifierFactory.operatorNameForMethodNamed(qualifierMap.getString("selectorName", true));
				qualifier = EOQualifierFactory.createKeyValueExpression(key, selectorName, value);
			} else if ("EOKeyComparisonQualifier".equals(className) || "com.webobjects.eocontrol.EOKeyComparisonQualifier".equals(className)) {
				String leftKey = qualifierMap.getString("leftKey", true);
				String rightKey = qualifierMap.getString("rightKey", true);
				String selectorName = EOQualifierFactory.operatorNameForMethodNamed(qualifierMap.getString("selectorName", true));
				qualifier = EOQualifierFactory.createKeyComparisonExpression(leftKey, selectorName, rightKey);
			} else {
				throw new IllegalArgumentException("Unknown qualifier className '" + className + "'.");
			}
		}
		return qualifier;
	}

	private static EOQualifier createKeyValueExpression(String key, String selectorName, Object value) {
		EOQualifier qualifier = new EOKeyValueQualifier(key, selectorName, value);
		return qualifier;
	}

	private static EOQualifier createKeyComparisonExpression(String leftKey, String selectorName, String rightKey) {
		EOQualifier qualifier = new EOKeyComparisonQualifier(leftKey, selectorName, rightKey);
		return qualifier;
	}

	private static Object createValue(Object _rawValue) {
		Object value;
		if (_rawValue instanceof Map) {
			EOModelMap valueMap = new EOModelMap((Map) _rawValue);
			String valueClass = valueMap.getString("class", true);
			if ("EONull".equals(valueClass) || "com.webobjects.eocontrol.EONull".equals(valueClass)) {
				value = null;
			} else if ("EOQualifierVariable".equals(valueClass) || "com.webobjects.eocontrol.EOQualifierVariable".equals(valueClass)) {
				String variableKey = valueMap.getString("_key", true);
				// Fix up previously broken _key's
				if (variableKey.startsWith("$")) {
					variableKey = variableKey.substring(1);
				}
				value = new EONamedQualifierVariable(variableKey);
			} else if ("NSNumber".equals(valueClass)) {
				value = valueMap.get("value");
				if (value instanceof String) {
					String valueStr = (String) value;
					if (valueStr.indexOf('.') == -1) {
						value = Integer.parseInt(valueStr);
						if (!String.valueOf(value).equals(valueStr)) {
							value = Long.parseLong(valueStr);
						}
					} else {
						value = Float.parseFloat(valueStr);
					}
				}
			} else {
				throw new IllegalArgumentException("Unknown EOKeyValueQualifier value class " + valueClass);
			}
		} else {
			value = _rawValue;
		}
		return value;
	}

	private static Collection<EOQualifier> createQualifiersFromQualifierMaps(Collection<Map<Object, Object>> _qualifiers) {
		List<EOQualifier> qualifiers = new LinkedList<EOQualifier>();
		if (_qualifiers != null) {
			for (Map<Object, Object> qualifierMap : _qualifiers) {
				EOQualifier exp = EOQualifierFactory.createQualifierFromQualifierMap(new EOModelMap(qualifierMap));
				qualifiers.add(exp);
			}
		}
		return qualifiers;
	}

	private static Object createQualifierValue(Object value) {
		Object qualifierValue;
		if (value == null) {
			EOModelMap map = new EOModelMap();
			map.setString("class", "EONull", false);
			qualifierValue = map;
		} else if (value instanceof EONamedQualifierVariable) {
			EOModelMap map = new EOModelMap();
			String name = ((EONamedQualifierVariable) value).getName();
			map.setString("_key", name, true);
			map.setString("class", "EOQualifierVariable", false);
			qualifierValue = map;
		} else if (value instanceof EOQualifierVariable) {
			EOModelMap map = new EOModelMap();
			String name = ((EOQualifierVariable) value).getName();
			map.setString("_key", name, true);
			map.setString("class", "EOQualifierVariable", false);
			qualifierValue = map;
		} else if (value instanceof Number) {
			EOModelMap map = new EOModelMap();
			map.setString("class", "NSNumber", false);
			map.put("value", value);
			qualifierValue = map;
		} else if (value instanceof Boolean) {
			EOModelMap map = new EOModelMap();
			map.setString("class", "NSNumber", false);
			map.put("value", value);
			qualifierValue = map;
		} else if (value instanceof String) {
			qualifierValue = value;
		} else {
			throw new IllegalArgumentException("Unknown qualifier value type: " + value + " (type = " + value.getClass().getName() + ")");
		}
		return qualifierValue;
	}

	private static EOModelMap createQualifierMapFromKeyComparisonQualifier(EOKeyComparisonQualifier qualifier) {
		String leftKey = qualifier.getLeftKey();
		String rightKey = qualifier.getRightKey();

		EOModelMap map = new EOModelMap();
		map.setString("class", "EOKeyComparisonQualifier", false);
		map.setString("leftKey", leftKey, false);
		map.setString("rightKey", rightKey, false);
		EOQualifier.Comparison comparison = qualifier.getComparison();
		String selectorName = (comparison == null) ? null : EOQualifierFactory.methodNameForOperatorNamed(comparison.getName());
		map.setString("selectorName", selectorName, false);
		return map;
	}

	private static EOModelMap createQualifierMapFromKeyValueQualifier(EOKeyValueQualifier qualifier) {
		String key = qualifier.getKey();
		Object value = qualifier.getValue();
		EOModelMap map = new EOModelMap();
		map.setString("class", "EOKeyValueQualifier", false);
		Object processedValue = createQualifierValue(value);
		map.setString("key", key, false);
		EOQualifier.Comparison comparison = qualifier.getComparison();
		String selectorName = (comparison == null) ? null : EOQualifierFactory.methodNameForOperatorNamed(comparison.getName());
		map.setString("selectorName", selectorName, false);
		map.put("value", processedValue);
		return map;
	}

	private static List<EOModelMap> createQualifierMapsFromAggregateQualifier(EOAggregateQualifier aggregateQualifier) {
		List<EOModelMap> qualifierMaps = new LinkedList<EOModelMap>();
		for (EOQualifier qualifier : aggregateQualifier.getQualifiers()) {
			qualifierMaps.add(EOQualifierFactory.createQualifierMapFromQualifier(qualifier));
		}
		return qualifierMaps;
	}

	public static EOModelMap createQualifierMapFromQualifier(EOQualifier qualifier) {
		EOModelMap map;
		if (qualifier instanceof EOKeyValueQualifier) {
			map = EOQualifierFactory.createQualifierMapFromKeyValueQualifier((EOKeyValueQualifier) qualifier);
		} else if (qualifier instanceof EOKeyComparisonQualifier) {
			map = EOQualifierFactory.createQualifierMapFromKeyComparisonQualifier((EOKeyComparisonQualifier) qualifier);
		} else if (qualifier instanceof EOAndQualifier) {
			map = new EOModelMap();
			map.setString("class", "EOAndQualifier", false);
			map.setList("qualifiers", createQualifierMapsFromAggregateQualifier((EOAndQualifier) qualifier), true);
		} else if (qualifier instanceof EOOrQualifier) {
			map = new EOModelMap();
			map.setString("class", "EOOrQualifier", false);
			map.setList("qualifiers", createQualifierMapsFromAggregateQualifier((EOOrQualifier) qualifier), true);
		} else if (qualifier instanceof EONotQualifier) {
			map = new EOModelMap();
			map.setString("class", "EONotQualifier", false);
			map.setMap("qualifier", createQualifierMapFromQualifier(((EONotQualifier) qualifier).getQualifier()), true);
		} else {
			throw new IllegalArgumentException("Unknown qualifier " + qualifier + ".");
		}
		return map;
	}

	public static Set<String> getQualifierKeysFromQualifierString(String qualifierString) {
		EOQualifier qualifier = EOQualifierFactory.fromString(qualifierString);
		return EOQualifierFactory.getQualifierKeysFromQualifier(qualifier);
	}

	public static Set<String> getQualifierKeysFromQualifier(EOQualifier expression) {
		Set<String> keys = new HashSet<String>();
		try {
			EOQualifierFactory.fillInQualifierKeysFromQualifier(expression, keys);
		} catch (Throwable t) {
			t.printStackTrace();
		}
		return keys;
	}

	public static void fillInQualifierKeysFromQualifier(EOQualifier qualifier, Set<String> keys) {
		if (qualifier instanceof EOKeyValueQualifier) {
			String key = ((EOKeyValueQualifier) qualifier).getKey();
			keys.add(key);
		} else if (qualifier instanceof EOKeyComparisonQualifier) {
			String leftKey = ((EOKeyComparisonQualifier) qualifier).getLeftKey();
			keys.add(leftKey);
			String rightKey = ((EOKeyComparisonQualifier) qualifier).getRightKey();
			keys.add(rightKey);
		} else if (qualifier instanceof EOAggregateQualifier) {
			for (EOQualifier childQualifier : ((EOAggregateQualifier) qualifier).getQualifiers()) {
				EOQualifierFactory.fillInQualifierKeysFromQualifier(childQualifier, keys);
			}
		} else if (qualifier instanceof EONotQualifier) {
			EOQualifierFactory.fillInQualifierKeysFromQualifier(((EONotQualifier) qualifier).getQualifier(), keys);
		} else {
			throw new IllegalArgumentException("Unknown expression " + qualifier + ".");
		}
	}

	public static List<EOQualifierBinding> getQualifierBindingsFromQualifierString(EOEntity entity, String qualifierString) {
		EOQualifier qualifier = EOQualifierFactory.fromString(qualifierString);
		return EOQualifierFactory.getQualifierBindingsFromQualifier(entity, qualifier);
	}

	public static List<EOQualifierBinding> getQualifierBindingsFromQualifier(EOEntity entity, EOQualifier qualifier) {
		List<EOQualifierBinding> bindings = new LinkedList<EOQualifierBinding>();
		try {
			if (qualifier != null) {
				EOQualifierFactory.fillInQualifierBindingsFromQualifier(entity, qualifier, bindings);
			}
		} catch (Throwable t) {
			t.printStackTrace();
		}
		return bindings;
	}

	public static void fillInQualifierBindingsFromQualifier(EOEntity entity, EOQualifier qualifier, List<EOQualifierBinding> bindings) {
		if (qualifier instanceof EOKeyValueQualifier) {
			String key = ((EOKeyValueQualifier) qualifier).getKey();
			Object value = ((EOKeyValueQualifier) qualifier).getValue();
			if (value instanceof EONamedQualifierVariable) {
				String bindingName = ((EONamedQualifierVariable) value).getName();
				EOQualifierBinding binding = new EOQualifierBinding(entity, bindingName, key);
				bindings.add(binding);
			}
		} else if (qualifier instanceof EOKeyComparisonQualifier) {
			// DO NOTHING
		} else if (qualifier instanceof EOAggregateQualifier) {
			for (EOQualifier aggregatedQualifier : ((EOAggregateQualifier) qualifier).getQualifiers()) {
				EOQualifierFactory.fillInQualifierBindingsFromQualifier(entity, aggregatedQualifier, bindings);
			}
		} else if (qualifier instanceof EONotQualifier) {
			EOQualifierFactory.fillInQualifierBindingsFromQualifier(entity, ((EONotQualifier) qualifier).getQualifier(), bindings);
		} else {
			throw new IllegalArgumentException("Unknown qualifier '" + qualifier + "'.");
		}
	}

}