/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*******************************************************************************
 * Copyright (c) 2010 BestSolution.at and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Tom Schindl <tom.schindl@bestsolution.at> - adjustment to EObject
 ******************************************************************************/
package org.eclipse.e4.emf.internal.xpath;

import java.util.Locale;

import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathIntrospector;
import org.apache.commons.jxpath.ri.Compiler;
import org.apache.commons.jxpath.ri.QName;
import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
import org.apache.commons.jxpath.ri.compiler.NodeTest;
import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
import org.apache.commons.jxpath.ri.model.NodeIterator;
import org.apache.commons.jxpath.ri.model.NodePointer;
import org.eclipse.e4.emf.internal.xpath.helper.ValueUtils;

/**
 * Transparent pointer to a collection (array or Collection).
 *
 */
public class CollectionPointer extends NodePointer {
	private Object collection;
	private NodePointer valuePointer;

	private static final long serialVersionUID = 8620254915563256588L;

	/**
	 * Create a new CollectionPointer.
	 * @param collection value
	 * @param locale Locale
	 */
	public CollectionPointer(Object collection, Locale locale) {
		super(null, locale);
		this.collection = collection;
	}

	/**
	 * Create a new CollectionPointer.
	 * @param parent parent NodePointer
	 * @param collection value
	 */
	public CollectionPointer(NodePointer parent, Object collection) {
		super(parent);
		this.collection = collection;
	}

	@Override
	public QName getName() {
		return null;
	}

	@Override
	public Object getBaseValue() {
		return collection;
	}

	@Override
	public boolean isCollection() {
		return true;
	}

	@Override
	public int getLength() {
		return ValueUtils.getLength(getBaseValue());
	}

	@Override
	public boolean isLeaf() {
		Object value = getNode();
		return value == null || JXPathIntrospector.getBeanInfo(value.getClass()).isAtomic();
	}

	@Override
	public boolean isContainer() {
		return index != WHOLE_COLLECTION;
	}

	@Override
	public Object getImmediateNode() {
		return index == WHOLE_COLLECTION ? ValueUtils.getValue(collection)
				: ValueUtils.getValue(collection, index);
	}

	@Override
	public void setValue(Object value) {
		if (index == WHOLE_COLLECTION) {
			parent.setValue(value);
		}
		else {
			ValueUtils.setValue(collection, index, value);
		}
	}

	@Override
	public void setIndex(int index) {
		super.setIndex(index);
		valuePointer = null;
	}

	@Override
	public NodePointer getValuePointer() {
		if (valuePointer == null) {
			if (index == WHOLE_COLLECTION) {
				valuePointer = this;
			}
			else {
				Object value = getImmediateNode();
				valuePointer =
					NodePointer.newChildNodePointer(this, getName(), value);
			}
		}
		return valuePointer;
	}

	@Override
	public NodePointer createPath(JXPathContext context) {
		if (ValueUtils.getLength(getBaseValue()) <= index) {
			collection = ValueUtils.expandCollection(getNode(), index + 1);
		}
		return this;
	}

	@Override
	public NodePointer createPath(JXPathContext context, Object value) {
		NodePointer ptr = createPath(context);
		ptr.setValue(value);
		return ptr;
	}

	@Override
	public NodePointer createChild(
		JXPathContext context,
		QName name,
		int index,
		Object value) {
		NodePointer ptr = (NodePointer) clone();
		ptr.setIndex(index);
		return ptr.createPath(context, value);
	}

	@Override
	public NodePointer createChild(
		JXPathContext context,
		QName name,
		int index) {
		NodePointer ptr = (NodePointer) clone();
		ptr.setIndex(index);
		return ptr.createPath(context);
	}

	@Override
	public int hashCode() {
		return System.identityHashCode(collection) + index;
	}

	@Override
	public boolean equals(Object object) {
		if (object == this) {
			return true;
		}

		if (!(object instanceof CollectionPointer)) {
			return false;
		}

		CollectionPointer other = (CollectionPointer) object;
		return collection == other.collection && index == other.index;
	}

	@Override
	public NodeIterator childIterator(NodeTest test,
				boolean reverse, NodePointer startWith) {
		if (index == WHOLE_COLLECTION) {
			return new CollectionChildNodeIterator(
				this,
				test,
				reverse,
				startWith);
		}
		return getValuePointer().childIterator(test, reverse, startWith);
	}

	@Override
	public NodeIterator attributeIterator(QName name) {
		return index == WHOLE_COLLECTION ? new CollectionAttributeNodeIterator(this, name)
				: getValuePointer().attributeIterator(name);
	}

	@Override
	public NodeIterator namespaceIterator() {
		return index == WHOLE_COLLECTION ? null : getValuePointer().namespaceIterator();
	}

	@Override
	public NodePointer namespacePointer(String namespace) {
		return index == WHOLE_COLLECTION ? null : getValuePointer().namespacePointer(namespace);
	}

	@Override
	public boolean testNode(NodeTest test) {
		if (index == WHOLE_COLLECTION) {
			if (test == null) {
				return true;
			}
			if (test instanceof NodeNameTest) {
				return false;
			}
			return test instanceof NodeTypeTest && ((NodeTypeTest) test).getNodeType() == Compiler.NODE_TYPE_NODE;
		}
		return getValuePointer().testNode(test);
	}

	@Override
	public int compareChildNodePointers(
				NodePointer pointer1, NodePointer pointer2) {
		return pointer1.getIndex() - pointer2.getIndex();
	}

	@Override
	public String asPath() {
		StringBuilder buffer = new StringBuilder();
		NodePointer parent = getImmediateParentPointer();
		if (parent != null) {
			buffer.append(parent.asPath());
			if (index != WHOLE_COLLECTION) {
				// Address the list[1][2] case
				if (parent.getIndex() != WHOLE_COLLECTION) {
					buffer.append("/.");
				}
				buffer.append("[").append(index + 1).append(']');
			}
		}
		else {
			if (index != WHOLE_COLLECTION) {
				buffer.append("/.[").append(index + 1).append(']');
			}
			else {
				buffer.append("/");
			}
		}
		return buffer.toString();
	}
}
