/*******************************************************************************
 * Copyright (c) 1998, 2012 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.relational;

import java.util.Collections;
import java.util.List;

import org.eclipse.persistence.tools.workbench.mappingsmodel.MWModel;
import org.eclipse.persistence.tools.workbench.mappingsmodel.MWQueryKey;
import org.eclipse.persistence.tools.workbench.mappingsmodel.ProblemConstants;
import org.eclipse.persistence.tools.workbench.mappingsmodel.db.MWColumn;
import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.MWDescriptor;
import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.MWMappingDescriptor;
import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWColumnHandle;
import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWHandle;
import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWHandle.NodeReferenceScrubber;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.relational.AggregateFieldDescription;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.relational.AggregateRuntimeFieldNameGenerator;
import org.eclipse.persistence.tools.workbench.mappingsmodel.query.MWQueryable;
import org.eclipse.persistence.tools.workbench.utility.CollectionTools;
import org.eclipse.persistence.tools.workbench.utility.filters.Filter;
import org.eclipse.persistence.tools.workbench.utility.node.Node;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping;

public final class MWUserDefinedQueryKey extends MWModel 
	implements MWQueryKey, MWQueryable, AggregateRuntimeFieldNameGenerator {

	private volatile String name;

	private MWColumnHandle columnHandle;
		public final static String COLUMN_PROPERTY = "column";


	// ********** Constructors **********

	/**
	 * Default constructor - for TopLink use only.
	 */
	private MWUserDefinedQueryKey() {
		super();
	}

	MWUserDefinedQueryKey(String name, MWRelationalClassDescriptor parent, MWColumn column) {
		this(name, parent);
		this.columnHandle.setColumn(column);
	}

	MWUserDefinedQueryKey(String name, MWRelationalClassDescriptor parent) {
		super(parent);
		initialize(name);
	}


	// ********** Initialization **********

	/**
	 * initialize persistent state
	 */
	protected void initialize(Node parent) {
		super.initialize(parent);
		this.columnHandle = new MWColumnHandle(this, this.buildColumnScrubber());
	}

	private void initialize(String qkName) {
		checkName(qkName);
		this.name = qkName;
	}

	protected void checkName(String qkName) {
		if (qkName == null || qkName == "") {
			throw new IllegalArgumentException("MWUserDefinedQueryKey name cannot be null");
		}
		getDescriptor().checkQueryKeyName(qkName);
	}


	// ********** Containment hierarchy **********

	protected void addChildrenTo(List children) {
		super.addChildrenTo(children);
		children.add(this.columnHandle);
	}

	private NodeReferenceScrubber buildColumnScrubber() {
		return new NodeReferenceScrubber() {
			public void nodeReferenceRemoved(Node node, MWHandle handle) {
				MWUserDefinedQueryKey.this.setColumn(null);
			}
			public String toString() {
				return "MWUserDefinedQueryKey.buildColumnScrubber()";
			}
		};
	}


	// ********** MWQueryable implementation **********

	public boolean allowsChildren() {
		return false;
	}

	public boolean allowsOuterJoin() {
		return allowsChildren();
	}

	public MWMappingDescriptor getParentDescriptor() {
		return getDescriptor();
	}

	public List subQueryableElements(Filter queryableFilter) {
		return Collections.EMPTY_LIST;
	}

	public MWQueryable subQueryableElementAt(int index, Filter queryableFilter) {
		return null;
	}

	public boolean isLeaf(Filter queryableFilter) {
		return true;
	}

	public String iconKey() {
		return "queryKey.userDefined";
	}

	public boolean usesAnyOf() {
		return false;
	}

	public boolean isTraversableForReadAllQueryOrderable() {
		return true;
	}

	public boolean isValidForReadAllQueryOrderable() {
		return true;
	}

	public boolean isTraversableForBatchReadAttribute() {
		return false;
	}

	public boolean isValidForBatchReadAttribute() {
		return false;
	}

	public boolean isTraversableForJoinedAttribute() {
		return false;
	}

	public boolean isValidForJoinedAttribute() {
		return false;
	}

	public boolean isValidForReportQueryAttribute() {
		return true;
	}

	public boolean isTraversableForReportQueryAttribute() {
		return true;
	}

	public boolean isTraversableForQueryExpression() {
		return true;
	}

	public boolean isValidForQueryExpression() {
		return true;
	}


	// ********** MWQueryKey implementation **********

	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		checkName(name);
		Object old = this.name;
		this.name = name;
		this.firePropertyChanged(NAME_PROPERTY, old, name);
		if (this.attributeValueHasChanged(old, name)) {
			this.getProject().nodeRenamed(this);
		}
	}

	public boolean isAutoGenerated() {
		return false;
	}

	public MWRelationalClassDescriptor getDescriptor() {
		return (MWRelationalClassDescriptor) this.getParent();
	}

	public MWColumn getColumn() {
		return this.columnHandle.getColumn();
	}

	public void setColumn(MWColumn column) {
		Object old = this.columnHandle.getColumn();
		this.columnHandle.setColumn(column);
		this.firePropertyChanged(COLUMN_PROPERTY, old, column);
	}


	//************* AggregateRuntimeFieldNameGenerator implementation ************

	public String fieldNameForRuntime() {
		return "QUERY_KEY " + getName();
	}

	public AggregateFieldDescription fullFieldDescription() {
		return new AggregateFieldDescription() {
			public String getMessageKey() {
				return "AGGREGATE_FIELD_DESCRIPTION_FOR_USER_DEFINED_QUERY_KEY";
			}

			public Object[] getMessageArguments() {
				return new Object[] {getName()};
			}
		};
	}

	public boolean fieldIsWritten() {
		return false;
	}

	public MWDescriptor owningDescriptor() {
		return (MWDescriptor) this.getParent();
	}


	//*********** Problem Handling *********

	/**
	 * Check for any problems and add them to the specified collection.
	 */
	protected void addProblemsTo(List newProblems) {
		super.addProblemsTo(newProblems);
		this.checkColumns(newProblems);
	}

	private void checkColumns(List currentProblems) {
		if (((MWRelationalDescriptor) this.getParentDescriptor()).isAggregateDescriptor()) {
			return;
		}
		if (this.getColumn() == null) {
			currentProblems.add(this.buildProblem(ProblemConstants.DESCRIPTOR_QUERY_KEY_NO_COLUMN_SPECIFIED, this.getName()));
		}
		else if ( ! CollectionTools.contains(((MWTableDescriptor) this.getParentDescriptor()).allAssociatedColumns(), this.getColumn())) {
			currentProblems.add(this.buildProblem(ProblemConstants.DESCRIPTOR_QUERY_KEY_INVALID_COLUMN, this.getName()));
		}
	}

	// ********** Display methods **********

	public String displayString() {
		return this.getName();
	}

	public void toString(StringBuffer sb) {
		sb.append("field=");
		if (getColumn() == null) {
			sb.append("null");
		}
		else {
			getColumn().toString(sb);
		}
	}


	// ********** TopLink methods *********

	public static XMLDescriptor buildDescriptor() {
		XMLDescriptor descriptor = new XMLDescriptor();

		descriptor.setJavaClass(MWUserDefinedQueryKey.class);

		descriptor.addDirectMapping("name", "name/text()");

		XMLCompositeObjectMapping columnHandleMapping = new XMLCompositeObjectMapping();
		columnHandleMapping.setAttributeName("columnHandle");
		columnHandleMapping.setGetMethodName("getColumnHandleForTopLink");
		columnHandleMapping.setSetMethodName("setColumnHandleForTopLink");
		columnHandleMapping.setReferenceClass(MWColumnHandle.class);
		columnHandleMapping.setXPath("column-handle");
		descriptor.addMapping(columnHandleMapping);

		return descriptor;
	}

	private MWColumnHandle getColumnHandleForTopLink() {
		return (this.columnHandle.getColumn() == null) ? null : this.columnHandle;
	}
	private void setColumnHandleForTopLink(MWColumnHandle handle) {
		NodeReferenceScrubber scrubber = this.buildColumnScrubber();
		this.columnHandle = ((handle == null) ? new MWColumnHandle(this, scrubber) : handle.setScrubber(scrubber));
	}
}
