/*******************************************************************************
 * Copyright (c) 2001, 2018 IBM Corporation and others.
 * All rights reserved. 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:
 *     IBM Corporation - initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     David Carver (Intalio) - bug 300434 - Make inner classes static where possible
 *******************************************************************************/
package org.eclipse.wst.sse.core.internal.text;

import java.util.Iterator;
import java.util.NoSuchElementException;

import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;


public class TextRegionListImpl implements ITextRegionList {

	static private class NullIterator implements Iterator {
		public NullIterator() {
		}

		public boolean hasNext() {
			return false;
		}

		public Object next() {
			throw new NoSuchElementException();
		}

		public void remove() {
			throw new UnsupportedOperationException("can not remove regions via iterator"); //$NON-NLS-1$

		}

	}

	private static class RegionIterator implements Iterator {
		private ITextRegion[] fIteratorRegions;
		private int index = -1;
		private int maxindex = -1;

		public RegionIterator(ITextRegion[] regions) {
			fIteratorRegions = regions;
			maxindex = fIteratorRegions.length - 1;
		}

		public boolean hasNext() {
			return index < maxindex;
		}

		public Object next() {
			if (!(index < maxindex))
				throw new NoSuchElementException();
			return fIteratorRegions[++index];
		}

		public void remove() {
			if (index < 0) {
				// next() has never been called
				throw new IllegalStateException("can not remove regions without prior invocation of next()"); //$NON-NLS-1$
			}
			throw new UnsupportedOperationException("can not remove regions via iterator"); //$NON-NLS-1$
		}

	}

	private final static int growthConstant = 2;

	private ITextRegion[] fRegions;
	private int fRegionsCount = 0;

	public TextRegionListImpl() {
		super();
	}

	public TextRegionListImpl(ITextRegionList regionList) {
		this();
		fRegions = regionList.toArray().clone();
		fRegionsCount = fRegions.length;
	}

	public boolean add(ITextRegion region) {
		if (region == null)
			return false;
		ensureCapacity(fRegionsCount + 1);
		fRegions[fRegionsCount++] = region;
		return true;
	}

	public boolean addAll(int insertPos, ITextRegionList newRegionList) {
		// beginning of list is 0 to insertPos-1
		// remainder of list is insertPos to fRegionsCount
		// resulting total will be be fRegionsCount + newRegions.size()
		if (insertPos < 0 || insertPos > fRegionsCount) {
			throw new ArrayIndexOutOfBoundsException(insertPos);
		}

		int newRegionListSize = newRegionList.size();

		ensureCapacity(fRegionsCount + newRegionListSize);

		int numMoved = fRegionsCount - insertPos;
		if (numMoved > 0)
			System.arraycopy(fRegions, insertPos, fRegions, insertPos + newRegionListSize, numMoved);

		if (newRegionList instanceof TextRegionListImpl && ((TextRegionListImpl) newRegionList).fRegions != null) {
			System.arraycopy(((TextRegionListImpl) newRegionList).fRegions, 0, fRegions, insertPos, newRegionListSize);
		}
		else {
			for (int i = 0; i < newRegionListSize; i++) {
				fRegions[insertPos++] = newRegionList.get(i);
			}
		}
		fRegionsCount += newRegionListSize;
		return newRegionListSize != 0;
	}

	public void clear() {
		// note: size of array is not reduced!
		fRegionsCount = 0;
	}

	private void ensureCapacity(int needed) {
		if (fRegions == null) {
			// first time
			fRegions = new ITextRegion[needed];
			return;
		}
		int oldLength = fRegions.length;
		if (oldLength < needed) {
			ITextRegion[] oldAdapters = fRegions;
			ITextRegion[] newAdapters = new ITextRegion[needed + growthConstant];
			System.arraycopy(oldAdapters, 0, newAdapters, 0, fRegionsCount);
			fRegions = newAdapters;
		}
	}

	public ITextRegion get(int index) {
		// fRegionCount may not equal fRegions.length
		if (index < 0 || index >= fRegionsCount) {
			throw new ArrayIndexOutOfBoundsException(index);
		}
		return fRegions[index];
	}

	public int indexOf(ITextRegion region) {
		int result = -1;
		if (region != null) {
			if (fRegions != null) {
				for (int i = 0; i < fRegions.length; i++) {
					if (region.equals(fRegions[i])) {
						result = i;
						break;
					}
				}
			}
		}
		return result;
	}

	public boolean isEmpty() {
		return fRegionsCount == 0;
	}

	public Iterator iterator() {
		if (size() == 0) {
			return new NullIterator();
		} else {
			return new RegionIterator(toArray());
		}
	}

	public ITextRegion remove(int index) {
		// much more efficient ways to implement this, but
		// I doubt if called often
		ITextRegion oneToRemove = get(index);
		remove(oneToRemove);
		return oneToRemove;
	}

	public void remove(ITextRegion a) {
		if (fRegions == null || a == null)
			return;
		int newIndex = 0;
		ITextRegion[] newRegions = new ITextRegion[fRegionsCount];
		int oldRegionCount = fRegionsCount;
		boolean found = false;
		for (int oldIndex = 0; oldIndex < oldRegionCount; oldIndex++) {
			ITextRegion candidate = fRegions[oldIndex];
			if (a == candidate) {
				fRegionsCount--;
				found = true;
			} else
				newRegions[newIndex++] = fRegions[oldIndex];
		}
		if (found)
			fRegions = newRegions;
	}

	public void removeAll(ITextRegionList regionList) {
		// much more efficient ways to implement this, but
		// I doubt if called often
		if (regionList != null) {
			for (int i = 0; i < regionList.size(); i++) {
				this.remove(regionList.get(i));
			}
		}

	}

	public int size() {
		return fRegionsCount;
	}

	public ITextRegion[] toArray() {
		// return "clone" of internal array
		ITextRegion[] newArray = new ITextRegion[fRegionsCount];
		System.arraycopy(fRegions, 0, newArray, 0, fRegionsCount);
		return newArray;
	}
	
	public void trimToSize() {
		if (fRegions.length > fRegionsCount) {
			ITextRegion[] newRegions = new ITextRegion[fRegionsCount];
			System.arraycopy(fRegions, 0, newRegions, 0, fRegionsCount);
			fRegions = newRegions;
		}
	}

}
