/*******************************************************************************
*   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.lang.reflect.Array;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;

import com.analog.lyric.collect.Supers;

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

public class ArrayDiscreteDomain<Element> extends TypedDiscreteDomain<Element>
{
	/*-------
	 * State
	 */
	
	private static final long serialVersionUID = 1L;
	
	private final Element[] _elements;
	private final HashMap<Object,Integer> _elementToIndex;
	private final boolean _hasIntCompatibleValues;
	
	/*--------------
	 * Construction
	 */
	
	@SuppressWarnings("unchecked")
	ArrayDiscreteDomain(Element firstElement, int offset, Object ... moreElements)
	{
		super(computeHashCode(firstElement, moreElements));
		
		Class<?> eltClass = Supers.nearestCommonSuperClass(firstElement, moreElements);
		Object[] elements = (Object[]) Array.newInstance(eltClass,  moreElements.length + 1 - offset);
		elements[0] = firstElement;
		for (int i = moreElements.length; --i>=offset;)
		{
			elements[i+1-offset] = Objects.requireNonNull(moreElements[i]);
		}
		_elements = (Element[])elements;
		
		boolean hasIntCompatibleValues = isIntCompatibleClass(eltClass);

		_elementToIndex = new HashMap<Object,Integer>(elements.length);
		for (int i = 0, end = elements.length; i < end; ++i)
		{
			Object element = elements[i];
			_elementToIndex.put(element, i);
			
			// HACK: In Java, although 0.0 == -0.0, this is not true when wrapped in a
			// Double or Float object!
			if (element instanceof Double)
			{
				Double d = (Double)element;
				if (d == 0.0)
				{
					_elementToIndex.put(Double.valueOf(-d), i);
				}
			}
			else if (element instanceof Float)
			{
				Float f = (Float)element;
				if (f == 0.0)
				{
					_elementToIndex.put(Float.valueOf(-f), i);
				}
			}
		}
		
		if (!hasIntCompatibleValues && Number.class.isAssignableFrom(eltClass))
		{
			Number[] numbers = (Number[])elements;
			hasIntCompatibleValues = true;
			for (Number number : numbers)
			{
				if (!isIntCompatibleValue(number))
				{
					hasIntCompatibleValues = false;
					break;
				}
			}
		}
		
		_hasIntCompatibleValues = hasIntCompatibleValues;
	}
	
	private static int computeHashCode(Object firstElement, Object[] elements)
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(elements);
		return result;
	}

	/*----------------
	 * Domain methods
	 */
	
	@Override
	public final boolean hasIntCompatibleValues()
	{
		return _hasIntCompatibleValues;
	}
	
	@Override
	public boolean inDomain(@Nullable Object value)
	{
		return _elementToIndex.containsKey(value);
	}
	
	@Override
	public boolean isNumber()
	{
		return isNumeric();
	}
	
	@Override
	public boolean isNumeric()
	{
		return Number.class.isAssignableFrom(getElementClass());
	}
	
	/*------------------------
	 * DiscreteDomain methods
	 */

	@Override
	public final Class<? extends Element> getElementClass()
	{
		@SuppressWarnings("unchecked")
		Class<? extends Element> elementClass = (Class<? extends Element>) _elements.getClass().getComponentType();
		return elementClass;
	}
	
	@NonNull // FIXME - workaround for Eclipse JDT bug (467610?)
	@Override
	public Element getElement(int i)
	{
		return _elements[i];
	}

	@Override
	public final Element[] getElements()
	{
		return _elements.clone();
	}

	// Find the list of elements corresponding to the value; return -1 if not a valid value
	@Override
	public int getIndex(@Nullable Object value)
	{
		Integer index = _elementToIndex.get(value);
		return index != null ? index : -1;
	}

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

}
