/*
 * ====================================================================
 * 
 * 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.util.Set;

import org.objectstyle.wolips.baseforplugins.util.ComparisonUtils;
import org.objectstyle.wolips.baseforplugins.util.StringUtils;

public abstract class AbstractEOArgument<T extends EOModelObject> extends UserInfoableEOModelObject<T> implements ISortableEOModelObject {
	public static final String ALLOWS_NULL = "allowsNull";

	public static final String NAME = "name";

	public static final String COLUMN_NAME = "columnName";

	public static final String ADAPTOR_VALUE_CONVERSION_CLASS_NAME = "adaptorValueConversionClassName";

	public static final String ADAPTOR_VALUE_CONVERSION_METHOD_NAME = "adaptorValueConversionMethodName";

	public static final String EXTERNAL_TYPE = "externalType";

	public static final String FACTORY_METHOD_ARGUMENT_TYPE = "factoryMethodArgumentType";

	public static final String PRECISION = "precision";

	public static final String SCALE = "scale";

	public static final String CLASS_NAME = "className";

	public static final String VALUE_CLASS_NAME = "valueClassName";

	public static final String VALUE_FACTORY_CLASS_NAME = "valueFactoryClassName";

	public static final String VALUE_FACTORY_METHOD_NAME = "valueFactoryMethodName";

	public static final String VALUE_TYPE = "valueType";

	public static final String DEFINITION = "definition";

	public static final String WIDTH = "width";

	public static final String DATA_TYPE = "dataType";

	public static final String SERVER_TIME_ZONE = "serverTimeZone";

	private String myName;

	private String myColumnName;

	private String myExternalType;

	private String myValueType;

	private String _className;

	protected boolean _inferredClassName;

	private String myValueClassName;
	
	protected boolean _inferredValueClassName;

	private String myValueFactoryClassName;

	private String myValueFactoryMethodName;

	private EOFactoryMethodArgumentType myFactoryMethodArgumentType;

	private String myAdaptorValueConversionClassName;

	private String myAdaptorValueConversionMethodName;

	private Integer myScale;

	private Integer myPrecision;

	private Integer myWidth;

	private Boolean myAllowsNull;

	private String myDefinition;

	private String myServerTimeZone;

	private EOModelMap myArgumentMap;

	private EODataType myDataType;

	public AbstractEOArgument() {
		myArgumentMap = new EOModelMap();
	}

	public AbstractEOArgument(String _name) {
		this();
		myName = _name;
	}

	public AbstractEOArgument(String _name, String _definition) {
		this(_name);
		myDefinition = _definition;
	}

	public void pasted() {
		// DO NOTHING
	}

	protected abstract AbstractEOArgument _createArgument(String _name);

	protected AbstractEOArgument _cloneArgument() {
		AbstractEOArgument argument = _createArgument(myName);
		_cloneIntoArgument(argument, false);
		_cloneUserInfoInto(argument);
		return argument;
	}

	public void _cloneIntoArgument(AbstractEOArgument argument, boolean updatingFlattenedAttribute) {
		if (updatingFlattenedAttribute) {
			argument.myColumnName = "";
		} else {
			argument.myColumnName = myColumnName;
		}
		argument.myExternalType = myExternalType;
		argument.myValueType = myValueType;
		argument.myValueClassName = myValueClassName;
		argument._className = _className;
		argument.myValueFactoryClassName = myValueFactoryClassName;
		argument.myValueFactoryMethodName = myValueFactoryMethodName;
		argument.myFactoryMethodArgumentType = myFactoryMethodArgumentType;
		argument.myAdaptorValueConversionClassName = myAdaptorValueConversionClassName;
		argument.myAdaptorValueConversionMethodName = myAdaptorValueConversionMethodName;
		argument.myScale = myScale;
		argument.myPrecision = myPrecision;
		argument.myServerTimeZone = myServerTimeZone;
		argument.myWidth = myWidth;
		argument.myAllowsNull = myAllowsNull;
		if (!updatingFlattenedAttribute) {
			argument.myDefinition = myDefinition;
		}
	}

	public boolean isFlattened() {
		return StringUtils.isKeyPath(_getDefinition());
	}

	public void setName(String _name) throws DuplicateNameException {
		setName(_name, true);
	}

	@SuppressWarnings("unused")
	public void setName(String _name, boolean _fireEvents) throws DuplicateNameException {
		String oldName = getName();
		myName = _name;
		if (_fireEvents) {
			firePropertyChange(AbstractEOArgument.NAME, oldName, getName());
		}
	}

	public String getName() {
		return myName;
	}

	public Boolean getAllowsNull() {
		return isAllowsNull();
	}

	public Boolean isAllowsNull() {
		return myAllowsNull;
	}

	public void setAllowsNull(Boolean _allowsNull) {
		_setAllowsNull(_allowsNull, true);
	}

	public void _setAllowsNull(Boolean _allowsNull, boolean _fireEvents) {
		setAllowsNull(_allowsNull, _fireEvents);
	}

	public void setAllowsNull(Boolean _allowsNull, boolean _fireEvents) {
		Boolean oldAllowsNull = getAllowsNull();
		myAllowsNull = _allowsNull;
		if (_fireEvents) {
			firePropertyChange(AbstractEOArgument.ALLOWS_NULL, oldAllowsNull, getAllowsNull());
		}
	}

	public String getColumnName() {
		return myColumnName;
	}

	public void setColumnName(String _columnName) {
		String oldColumnName = getColumnName();
		myColumnName = _columnName;
		firePropertyChange(AbstractEOArgument.COLUMN_NAME, oldColumnName, getColumnName());
	}

	public String getAdaptorValueConversionClassName() {
		return myAdaptorValueConversionClassName;
	}

	public void setAdaptorValueConversionClassName(String _adaptorValueConversionClassName) {
		String oldAdaptorValueConversionClassName = getAdaptorValueConversionClassName();
		myAdaptorValueConversionClassName = _adaptorValueConversionClassName;
		firePropertyChange(AbstractEOArgument.ADAPTOR_VALUE_CONVERSION_CLASS_NAME, oldAdaptorValueConversionClassName, getAdaptorValueConversionClassName());
	}

	public String getAdaptorValueConversionMethodName() {
		return myAdaptorValueConversionMethodName;
	}

	public void setAdaptorValueConversionMethodName(String _adaptorValueConversionMethodName) {
		String oldAdaptorValueConversionMethodName = getAdaptorValueConversionMethodName();
		myAdaptorValueConversionMethodName = _adaptorValueConversionMethodName;
		firePropertyChange(AbstractEOArgument.ADAPTOR_VALUE_CONVERSION_METHOD_NAME, oldAdaptorValueConversionMethodName, getAdaptorValueConversionMethodName());
	}

	public String getExternalType() {
		return myExternalType;
	}

	public void setExternalType(String _externalType) {
		String oldExternalType = getExternalType();
		myExternalType = _externalType;
		firePropertyChange(AbstractEOArgument.EXTERNAL_TYPE, oldExternalType, getExternalType());
	}

	public EOFactoryMethodArgumentType getFactoryMethodArgumentType() {
		return myFactoryMethodArgumentType;
	}

	public void setFactoryMethodArgumentType(EOFactoryMethodArgumentType _factoryMethodArgumentType) {
		EOFactoryMethodArgumentType oldFactoryMethodArgumentType = getFactoryMethodArgumentType();
		myFactoryMethodArgumentType = _factoryMethodArgumentType;
		firePropertyChange(AbstractEOArgument.FACTORY_METHOD_ARGUMENT_TYPE, oldFactoryMethodArgumentType, getFactoryMethodArgumentType());
	}

	public Integer getPrecision() {
		return myPrecision;
	}

	public void setPrecision(Integer _precision) {
		Integer oldPrecision = getPrecision();
		myPrecision = _precision;
		firePropertyChange(AbstractEOArgument.PRECISION, oldPrecision, getPrecision());
	}

	public Integer getScale() {
		return myScale;
	}

	public void setScale(Integer _scale) {
		Integer oldScale = getScale();
		myScale = _scale;
		firePropertyChange(AbstractEOArgument.SCALE, oldScale, getScale());
	}

	public String getServerTimeZone() {
		return myServerTimeZone;
	}

	public void setServerTimeZone(String _serverTimeZone) {
		String oldServerTimeZone = getServerTimeZone();
		myServerTimeZone = _serverTimeZone;
		firePropertyChange(AbstractEOArgument.SERVER_TIME_ZONE, oldServerTimeZone, getServerTimeZone());
	}

	public synchronized EODataType getDataType() {
		EODataType dataType = myDataType;
		if (dataType == null) {
			if (getValueFactoryMethodName() != null || getAdaptorValueConversionMethodName() != null) {
				dataType = EODataType.CUSTOM;
			} else {
				String className = _convertJavaClassNameToValueClassName(getClassName());
				dataType = EODataType.getDataTypeByValueClassAndType(className, getValueType());
			}
			myDataType = dataType;
		}
		return dataType;
	}

	public void setDataType(EODataType _dataType) {
		EODataType oldDataType = getDataType();
		EODataType dataType = _dataType;
		if (dataType == null) {
			dataType = EODataType.CUSTOM;
		}
		setFactoryMethodArgumentType(null);
		setAdaptorValueConversionMethodName(null);
		// MS: I think clearing external type may be a little aggressive
		// setExternalType(null);
		setPrecision(null);
		setScale(null);
		setServerTimeZone(null);
		setValueFactoryMethodName(null);
		setWidth(null);
		setValueClassName(dataType.getValueClass(), false);
		setValueType(dataType.getFirstValueType(), false);
		myDataType = dataType;
		updateDataType(oldDataType);
	}

	protected void updateDataType(EODataType _oldDataType) {
		EODataType dataType = getDataType();
		firePropertyChange(AbstractEOArgument.DATA_TYPE, _oldDataType, dataType);
	}

	public String getJavaClassName() {
		return getJavaClassName(true);
	}

	protected String _convertJavaClassNameToValueClassName(String javaClassName) {
		String className = javaClassName;
		if (className == null) {
			className = null;
		} else if (className.equals("java.lang.String")) {
			className = "NSString";
		} else if (className.equals("java.lang.Number") || className.equals("java.lang.Integer") || className.equals("java.lang.Long") || className.equals("java.lang.Short") || className.equals("java.lang.Double") || className.equals("java.lang.Float") || className.equals("java.lang.Byte")) {
			className = "NSNumber";
		} else if (className.equals("java.math.BigDecimal")) {
			className = "NSDecimalNumber";
		} else if (className.equals("com.webobjects.foundation.NSTimestamp")) {
			className = "NSCalendarDate";
		} else if (className.equals("com.webobjects.foundation.NSData")) {
			className = "NSData";
		} else if (className.equals("")) {
			className = null;
		} else {
			//String[] strs = className.split("\\.");
			//className = strs[strs.length - 1];
			className = javaClassName;
		}
		return className;
	}

	public String getJavaClassName(boolean shorten) {
		String className = getValueClassName();
		if (shorten && className != null && className.startsWith("java.lang.")) {
			className = className.substring("java.lang.".length());
		}
		if ("java.lang.Number".equals(className) || "Number".equals(className) || "NSNumber".equals(className)) {
			String valueType = getValueType();
			if (valueType == null || valueType.length() == 0) {
				className = java.lang.Integer.class.getName();
			} else if ("B".equals(valueType)) {
				className = java.math.BigDecimal.class.getName();
			} else if ("b".equals(valueType)) {
				className = java.lang.Byte.class.getName();
			} else if ("d".equals(valueType)) {
				className = java.lang.Double.class.getName();
			} else if ("f".equals(valueType)) {
				className = java.lang.Float.class.getName();
			} else if ("i".equals(valueType)) {
				className = java.lang.Integer.class.getName();
			} else if ("l".equals(valueType)) {
				className = java.lang.Long.class.getName();
			} else if ("s".equals(valueType)) {
				className = java.lang.Short.class.getName();
			} else if ("c".equals(valueType)) {
				className = java.lang.Boolean.class.getName();
			}
		} else if ("NSString".equals(className)) {
			className = java.lang.String.class.getName();
		} else if ("NSCalendarDate".equals(className)) {
			if (shorten) {
				className = "NSTimestamp";
			}
			else {
				className = "com.webobjects.foundation.NSTimestamp";
			}
		} else if ("NSDecimalNumber".equals(className)) {
			String valueType = getValueType();
			if (valueType == null || valueType.length() == 0) {
				className = java.lang.Integer.class.getName();
			} else {
				className = java.math.BigDecimal.class.getName();
			}
		}
		if (shorten && className != null && className.startsWith("java.lang.")) {
			className = className.substring("java.lang.".length());
		}
		return className;
	}
	
	public String getClassName() {
		return _className;
	}

	public void setClassName(String className) {
		String oldClassName = getClassName();
		_className = className;
		_inferredClassName = false;
		myDataType = null;
		firePropertyChange(AbstractEOArgument.CLASS_NAME, oldClassName, getClassName());
//		if (_updateDataType) {
//			updateDataType(oldDataType);
//		}
	}

	public String getValueClassName() {
		return myValueClassName;
	}

	public void setValueClassName(String _valueClassName) {
		setValueClassName(_valueClassName, true);
	}

	public synchronized void setValueClassName(String _valueClassName, boolean _updateDataType) {
		EODataType oldDataType = getDataType();
		String oldValueClassName = getValueClassName();
		myValueClassName = _valueClassName;
		_inferredValueClassName = false;
		myDataType = null;
		firePropertyChange(AbstractEOArgument.VALUE_CLASS_NAME, oldValueClassName, getValueClassName());
		setClassName(getJavaClassName(false));
		if (_updateDataType) {
			updateDataType(oldDataType);
		}
	}

	public String getValueFactoryClassName() {
		return myValueFactoryClassName;
	}

	public void setValueFactoryClassName(String _valueFactoryClassName) {
		String oldValueFactoryClassName = getValueFactoryClassName();
		myValueFactoryClassName = _valueFactoryClassName;
		firePropertyChange(AbstractEOArgument.VALUE_FACTORY_CLASS_NAME, oldValueFactoryClassName, getValueFactoryClassName());
	}

	public String getValueFactoryMethodName() {
		return myValueFactoryMethodName;
	}

	public void setValueFactoryMethodName(String _valueFactoryMethodName) {
		String oldValueFactoryMethodName = getValueFactoryMethodName();
		myValueFactoryMethodName = _valueFactoryMethodName;
		firePropertyChange(AbstractEOArgument.VALUE_FACTORY_METHOD_NAME, oldValueFactoryMethodName, getValueFactoryMethodName());
	}

	public String getValueType() {
		return myValueType;
	}

	public void setValueType(String _valueType) {
		setValueType(_valueType, false);
	}

	public synchronized void setValueType(String _valueType, boolean _updateDataType) {
		EODataType oldDataType = getDataType();
		String oldValueType = getValueType();
		myValueType = _valueType;
		myDataType = null;
		firePropertyChange(AbstractEOArgument.VALUE_TYPE, oldValueType, getValueType());
		if (_updateDataType) {
			updateDataType(oldDataType);
		}
		setClassName(getJavaClassName(false));
	}

	public Integer getWidth() {
		return myWidth;
	}

	public void setWidth(Integer _width) {
		Integer oldWidth = getWidth();
		myWidth = _width;
		firePropertyChange(AbstractEOArgument.WIDTH, oldWidth, getWidth());
	}

	public void setDefinition(String _definition) {
		String oldDefinition = myDefinition;
		myDefinition = _definition;
		updateDefinitionPath();
		firePropertyChange(AbstractEOArgument.DEFINITION, oldDefinition, getDefinition());
	}

	public String getDefinition() {
		return _getDefinition();
	}

	protected String _getDefinition() {
		return myDefinition;
	}

	protected void updateDefinitionPath() {
		// DO NOTHING
	}

	@SuppressWarnings("unused")
	public void loadFromMap(EOModelMap _argumentMap, Set<EOModelVerificationFailure> _failures) {
		myArgumentMap = _argumentMap;
		myName = _argumentMap.getString("name", true);
		if (_argumentMap.containsKey("externalName")) {
			myColumnName = _argumentMap.getString("externalName", true);
		} else {
			myColumnName = _argumentMap.getString("columnName", true);
		}
		myExternalType = _argumentMap.getString("externalType", true);
		myScale = _argumentMap.getInteger("scale");
		myPrecision = _argumentMap.getInteger("precision");
		if (_argumentMap.containsKey("maximumLength")) {
			myWidth = _argumentMap.getInteger("maximumLength");
		} else {
			myWidth = _argumentMap.getInteger("width");
		}
		myValueType = _argumentMap.getString("valueType", true);
		myValueClassName = _argumentMap.getString("valueClassName", true);
		_className = _argumentMap.getString("className", true);
		if (myValueClassName == null) {
			myValueClassName = _convertJavaClassNameToValueClassName(_className);
			_inferredValueClassName = true;
		}
		if (_className == null) {
			_className = getJavaClassName(false);
			_inferredClassName = true;
		}
		myValueFactoryClassName = _argumentMap.getString("valueFactoryClassName", true);
		myValueFactoryMethodName = _argumentMap.getString("valueFactoryMethodName", true);
		myFactoryMethodArgumentType = EOFactoryMethodArgumentType.getFactoryMethodArgumentTypeByID(_argumentMap.getString("factoryMethodArgumentType", true));
		myAdaptorValueConversionClassName = _argumentMap.getString("adaptorValueConversionClassName", true);
		myAdaptorValueConversionMethodName = _argumentMap.getString("adaptorValueConversionMethodName", true);
		myServerTimeZone = _argumentMap.getString("serverTimeZone", true);
		myAllowsNull = _argumentMap.getBoolean("allowsNull");
		myDefinition = _argumentMap.getString("definition", true);
		loadUserInfo(_argumentMap);
	}

	public EOModelMap toMap() {
		EOModelMap argumentMap = myArgumentMap.cloneModelMap();
		argumentMap.setString("name", getName(), true);
		// If columnName is prototyped, EOModeler leaves out the columnName. If,
		// however, columnName is MISSING, then it needs to write a "".
		if (isFlattened()) {
			argumentMap.remove("columnName");
		} else if (myColumnName == null && getColumnName() == null) {
			argumentMap.setString("columnName", "", false);
		} else {
			argumentMap.setString("columnName", myColumnName, false);
		}
		argumentMap.remove("externalName");
		argumentMap.setString("externalType", myExternalType, true);
		argumentMap.setInteger("scale", myScale);
		argumentMap.setInteger("precision", myPrecision);
		argumentMap.setInteger("width", myWidth);
		argumentMap.setString("serverTimeZone", myServerTimeZone, true);
		argumentMap.remove("maximumLength");
		argumentMap.setString("valueType", myValueType, true);
		boolean usesClassNameProperty = !ComparisonUtils.equals(getJavaClassName(false), getClassName());
		if (usesClassNameProperty) {
			argumentMap.setString("className", _className, true);
			String convertedValueClassName = _convertJavaClassNameToValueClassName(_className);
			// MS: If we guess the same value class name as you have now, just go ahead and leave it. If they don't
			// match, assume you typed in an explicit class name and let's toss the old value class name
			if (!ComparisonUtils.equals(convertedValueClassName, myValueClassName)) {
				argumentMap.remove("valueClassName");
			}
		} else {
			argumentMap.remove("className");
			argumentMap.setString("valueClassName", myValueClassName, true);
		}
		argumentMap.setString("valueFactoryClassName", myValueFactoryClassName, true);
		argumentMap.setString("valueFactoryMethodName", myValueFactoryMethodName, true);
		if (myFactoryMethodArgumentType != null) {
			argumentMap.setString("factoryMethodArgumentType", myFactoryMethodArgumentType.getID(), true);
		} else {
			argumentMap.remove("factoryMethodArgumentType");
		}
		argumentMap.setString("adaptorValueConversionClassName", myAdaptorValueConversionClassName, true);
		argumentMap.setString("adaptorValueConversionMethodName", myAdaptorValueConversionMethodName, true);

		if (isPrototyped()) {
			argumentMap.setBoolean("allowsNull", myAllowsNull, EOModelMap.YN);
		} else {
			argumentMap.setBoolean("allowsNull", myAllowsNull, EOModelMap.YNOptionalDefaultNo);
		}

		argumentMap.setString("definition", myDefinition, true);
		writeUserInfo(argumentMap);
		return argumentMap;
	}

	protected boolean isPrototyped() {
		return false;
	}

	public EOModelMap getArgumentMap() {
		return myArgumentMap;
	}

	public String toString() {
		return "[EOArgument: " + myName + "]";
	}
}
