/*******************************************************************************
*   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.options;

import java.util.Arrays;

import org.eclipse.jdt.annotation.Nullable;

import com.analog.lyric.collect.ArrayUtil;
import com.analog.lyric.dimple.environment.DimpleEnvironment;
import com.analog.lyric.dimple.environment.IDimpleEnvironmentHolder;
import com.analog.lyric.dimple.events.EventSourceIterator;
import com.analog.lyric.dimple.events.IDimpleEventListener;
import com.analog.lyric.dimple.events.IDimpleEventSource;
import com.analog.lyric.dimple.model.core.FactorGraph;
import com.analog.lyric.options.IOptionHolder;
import com.analog.lyric.options.IOptionKey;
import com.analog.lyric.options.LocalOptionHolder;
import com.analog.lyric.options.OptionDoubleList;

/**
 * Base class for dimple objects that can hold options and generate events.
 * <p>
 * This extends {@link LocalOptionHolder} in the following ways:
 * <ul>
 * <li>It also implements the {@link IDimpleEventSource} interface, so that all Dimple objects
 * on which you can set options are also event sources.
 * <li>It overrides the {@link #getOptionDelegates()} method
 * </ul>
 * 
 * @since 0.07
 * @author Christopher Barber
 */
public abstract class DimpleOptionHolder
	extends LocalOptionHolder
	implements IDimpleEventSource, IDimpleEnvironmentHolder
{
	/*--------------
	 * Construction
	 */
	
	protected DimpleOptionHolder()
	{
	}
	
	protected DimpleOptionHolder(DimpleOptionHolder other)
	{
		super(other);
	}

	/*-----------------------
	 * IOptionHolder methods
	 */
	
	/**
	 * Iterates over option holders in the order in which options should be looked up.
	 * <p>
	 * Unlike the default implementation, which simply walks up the chain of {@linkplain #getOptionParent option
	 * parents}, this will visit both the option parent and the corresponding model object. This is described in
	 * more detail in {@link EventSourceIterator}.
	 */
	@Override
	public EventSourceIterator getOptionDelegates()
	{
		return EventSourceIterator.create(this);
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * By default, the option parent is the same as the {@linkplain #getEventParent event parent}.
	 */
	@Override
	public @Nullable IOptionHolder getOptionParent()
	{
		return getEventParent();
	}
	
	/*-----------------------------
	 * IDimpleEventSource methods
	 */
	
	@Override
	public @Nullable IDimpleEventListener getEventListener()
	{
		return getEnvironment().getEventListener();
	}
	
	/*----------------------------------
	 * IDimpleEnvironmentHolder methods
	 */

	/**
	 * {@inheritDoc}
	 * <p>
	 * Returns environment for {@linkplain #getContainingGraph() containing graph} if available, otherwise
	 * {@link DimpleEnvironment#active()}.
	 */
	@Override
	public DimpleEnvironment getEnvironment()
	{
		FactorGraph graph = getContainingGraph();
		return graph != null ? graph.getEnvironment() : DimpleEnvironment.active();
	}
	
	/*-------------------
	 * Protected methods
	 */
	
	/**
	 * Returns list of not all zero doubles from option settings.
	 * <p>
	 * <ol>
	 * <li>Look up the option value for {@code listKey} using {@link #getOption}:
	 * <ul>
	 * <li>If non-null and contains at least one non-zero value, it's values will be returned.
	 * <li>If non-null and is empty or contains all zeros, an empty array will be returned.
	 * </ul>
	 * <li>Otherwise, look up the option value for {@code singleKey} using {@link #getOptionOrDefault}:
	 * <ul>
	 * <li>If zero, an empty array will be returned.
	 * <li>If non-zero, an array of length {@code size} containing only this value will be returned.
	 * </ul>
	 * </ol>
	 * <p>
	 * This can be used in situations in which there is a global parameter, such as damping, that
	 * may be given specific values for different edges of a node.
	 * <p>
	 * @param listKey is the primary lookup key for the list.
	 * @param singleKey is a secondary lookup key to be used if {@code listKey} not set.
	 * @param size is the size of list to return when replicating value from {@code singleKey}.
	 * @param array is the array into which the result should be written. This will only be used
	 * if it is non-null and of exactly the correct length.
	 * @since 0.07
	 */
	protected double[] getReplicatedNonZeroListFromOptions(
		IOptionKey<OptionDoubleList> listKey,
		IOptionKey<Double> singleKey,
		int size,
		@Nullable double[] array)
	{
    	OptionDoubleList list = getOption(listKey);
    	if (list != null)
    	{
    		final int listSize = list.size();
    		boolean hasNonZero = false;
    		for (int i = 0; i < listSize; ++i)
    		{
    			if (list.get(i) != 0.0)
    			{
    				hasNonZero = true;
    				break;
    			}
    		}
    		if (!hasNonZero)
    		{
    			return ArrayUtil.EMPTY_DOUBLE_ARRAY;
    		}
    		if (array == null || array.length != listSize)
    		{
    			array = new double[listSize];
    		}
    		for (int i = 0; i < listSize; ++i)
    		{
    			array[i] = list.get(i);
    		}
    	}
    	else
    	{
    		double d = getOptionOrDefault(singleKey);
    		if (d == 0.0)
    		{
    			return ArrayUtil.EMPTY_DOUBLE_ARRAY;
    		}
    		if (array == null || array.length != size)
    		{
    			array = new double[size];
    		}
    		Arrays.fill(array, d);
    	}
    	
    	return array;
    }
}
