/*******************************************************************************
*   Copyright 2014 Analog Devices, Inc.
*
*   Licensed 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.
********************************************************************************/

package com.analog.lyric.dimple.model.domains;

import java.io.Serializable;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.RandomAccess;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

import com.analog.lyric.collect.WeakInterner;
import com.google.common.collect.Interner;

/**
 * An immutable ordered list of {@link Domain}.
 */
public class DomainList<D extends Domain> extends AbstractList<D> implements RandomAccess, Serializable
{
	private static final long serialVersionUID = 1L;

	final D[] _domains;

	/**
	 * Responsible for weakly caching all JointDomainIndexer instances.
	 */
	private static enum DomainListInterner implements Interner<DomainList<?>>
	{
		INSTANCE;
		
		private final WeakInterner<DomainList<?>> _interner = WeakInterner.create();

		@NonNullByDefault(false)
		@Override
		public DomainList<?> intern(DomainList<?> indexer)
		{
			return _interner.intern(indexer);
		}
	}
	
	/*--------------
	 * Construction
	 */
	
	DomainList(D[] domains)
	{
		_domains = domains;
	}
	
	/**
	 * Returns canonical interned copy of indexer.
	 * <p>
	 * Used internally for caching instances upon creation.
	 * <p>
	 * @since 0.07
	 */
	@SuppressWarnings("unchecked")
	static <DL extends DomainList<?>> DL intern(DL indexer)
	{
		return (DL) DomainListInterner.INSTANCE.intern(indexer);
	}
	
	/**
	 * Replace instance with canonical interned copy if it exists.
	 * @since 0.07
	 */
	protected Object readResolve()
	{
		return intern(this);
	}
	
	/**
	 * Creates a new domain list containing the specified domains in given order.
	 * <p>
	 * If all the domains are instances of {@link DiscreteDomain}, then this will return
	 * a {@link JointDomainIndexer}, which will be directed if {@code outputIndices} is non-null.
	 * <p>
	 * May return a previously cached value.
	 */
	@SafeVarargs
	public static <T extends Domain> DomainList<T> create(@Nullable int[] outputIndices, T ... domains)
	{
		if (allDiscrete(domains))
		{
			DiscreteDomain[] discreteDomains = Arrays.copyOf(domains, domains.length, DiscreteDomain[].class);
			@SuppressWarnings("unchecked")
			DomainList<T> domainList =
				(DomainList<T>) JointDomainIndexer.lookupOrCreate(outputIndices, discreteDomains, false);
			return domainList;
		}
	
		// TODO: do something with inputIndices for non-discrete case?
		
		return intern(new DomainList<T>(domains));
	}
	
	/**
	 * Creates a new domain list containing the specified domains in given order.
	 * <p>
	 * If all the domains are instances of {@link DiscreteDomain}, then this will return
	 * an undirected {@link JointDomainIndexer}.
	 * <p>
	 * May return a previously cached value.
	 */
	@SafeVarargs
	public static <T extends Domain> DomainList<T> create(T ... domains)
	{
		return create(null, domains);
	}
	
	/*--------------
	 * List methods
	 */
	
	@Override
	public D get(int i)
	{
		return _domains[i];
	}

	@Override
	public final int size()
	{
		return _domains.length;
	}

	/*--------------------
	 * DomainList methods
	 */
	
	/**
	 * True if every domain in list is discrete.
	 */
	public static boolean allDiscrete(Domain ... domains)
	{
		if (DiscreteDomain.class.isAssignableFrom(domains.getClass().getComponentType()))
		{
			// Array type ensures all entries must be discrete, so there is no need to check.
			return true;
		}
		
		for (Domain domain : domains)
		{
			if (!domain.isDiscrete())
			{
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Casts this object to {@link JointDomainIndexer} or else returns null.
	 */
	public @Nullable JointDomainIndexer asJointDomainIndexer()
	{
		return null;
	}
	
	/**
	 * True if domains in this and {@code that} object are the same and in the same order.
	 */
	public boolean domainsEqual(DomainList<D> that)
	{
		return Arrays.equals(_domains, that._domains);
	}
	
	/**
	 * True if all domains are discrete and therefore this is an instance of
	 * {@link JointDomainIndexer}.
	 * @see #asJointDomainIndexer()
	 */
	public boolean isDiscrete()
	{
		return false;
	}
}
