/*******************************************************************************
 * Copyright (c) 2015, 2016 Google, Inc and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Stefan Xenos (Google) - Initial implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.core.nd;

import org.eclipse.jdt.internal.core.nd.db.IndexException;

public final class NdLinkedList<T> {
	private final NdRawLinkedList rawList;
	final ITypeFactory<T> elementFactory;

	public static interface ILinkedListVisitor<T> {
		public void visit(T record, short metadataBits, int index) throws IndexException;
	}

	public NdLinkedList(Nd nd, long address, ITypeFactory<T> elementFactory, int recordsInFirstBlock,
			int recordsInSubsequentBlocks) {
		this(nd, address, elementFactory, recordsInFirstBlock, recordsInSubsequentBlocks, 0);
	}

	public NdLinkedList(Nd nd, long address, ITypeFactory<T> elementFactory, int recordsInFirstBlock,
			int recordsInSubsequentBlocks, int metadataBitsPerElement) {
		this.rawList = new NdRawLinkedList(nd, address, elementFactory.getRecordSize(), recordsInFirstBlock,
				recordsInSubsequentBlocks, metadataBitsPerElement);
		this.elementFactory = elementFactory;
	}

	/**
	 * Computes the size of this list. This is an O(n) operation.
	 *
	 * @return the size of this list
	 * @throws IndexException
	 */
	public int size() throws IndexException {
		return this.rawList.size();
	}

	public T addMember(short metadataBits) throws IndexException {
		long address = this.rawList.addMember(metadataBits);

		return this.elementFactory.create(this.rawList.getNd(), address);
	}

	public void accept(final ILinkedListVisitor<T> visitor) throws IndexException {
		final NdRawLinkedList localRawList = this.rawList;
		final ITypeFactory<T> localElementFactory = this.elementFactory;
		localRawList.accept(new NdRawLinkedList.ILinkedListVisitor() {
			@Override
			public void visit(long address, short metadataBits, int index) throws IndexException {
				visitor.visit(localElementFactory.create(localRawList.getNd(),
						address), metadataBits, index);
			}
		});
	}

	public static <T> ITypeFactory<NdLinkedList<T>> getFactoryFor(
			final ITypeFactory<T> elementFactory, final int recordsInFirstBlock, final int recordsInSubsequentBlocks) {
		return getFactoryFor(elementFactory, recordsInSubsequentBlocks, 0);
	}

	public static <T> ITypeFactory<NdLinkedList<T>> getFactoryFor(
			final ITypeFactory<T> elementFactory, final int recordsInFirstBlock, final int recordsInSubsequentBlocks,
			final int metadataBitsPerElement) {

		return new AbstractTypeFactory<NdLinkedList<T>>() {
			public NdLinkedList<T> create(Nd dom, long address) {
				return new NdLinkedList<T>(dom, address, elementFactory, recordsInFirstBlock, recordsInSubsequentBlocks, metadataBitsPerElement);
			}

			@Override
			public int getRecordSize() {
				return NdRawLinkedList.recordSize(elementFactory.getRecordSize(), recordsInFirstBlock,
						metadataBitsPerElement);
			}

			@Override
			public Class<?> getElementClass() {
				return NdLinkedList.class;
			}

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

			@Override
			public void destructFields(Nd dom, long address) {
				create(dom, address).destruct();
			}

			@Override
			public void destruct(Nd dom, long address) {
				destructFields(dom, address);
			}
		};
	}

	/**
	 *
	 */
	protected void destruct() {
		if (this.elementFactory.hasDestructor()) {
			final Nd nd = this.rawList.getNd();
			this.rawList.accept(new NdRawLinkedList.ILinkedListVisitor() {
				@Override
				public void visit(long address, short metadataBits, int index) throws IndexException {
					NdLinkedList.this.elementFactory.destruct(nd, address);
				}
			});
		}
		this.rawList.destruct();
	}
}
