/*******************************************************************************
*   Copyright 2012-2013 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.solvers.core;

import static java.util.Objects.*;

import org.eclipse.jdt.annotation.Nullable;

import com.analog.lyric.dimple.exceptions.DimpleException;
import com.analog.lyric.dimple.factorfunctions.core.IFactorTable;
import com.analog.lyric.dimple.model.domains.DomainList;
import com.analog.lyric.dimple.model.domains.JointDomainIndexer;
import com.analog.lyric.dimple.model.factors.Factor;
import com.analog.lyric.dimple.options.SolverOptions;
import com.analog.lyric.dimple.solvers.interfaces.ISolverFactorGraph;

public abstract class STableFactorBase extends SFactorBase
{
	/*-------
	 * State
	 */
	
	private @Nullable IFactorTable _factorTable = null;

	/*--------------
	 * Construction
	 */
	
	protected STableFactorBase(Factor factor, ISolverFactorGraph parent)
	{
		super(factor, parent);
		
		if (!factor.isDiscrete())
			throw new DimpleException("only discrete factors supported");
		
	}
	
	/*---------------------
	 * SFactorBase methods
	 */
	
    @Override
    public int [][] getPossibleBeliefIndices()
    {
            return getFactorTable().getIndicesSparseUnsafe();
    }
    
    @Override
    public void initialize()
    {
    	super.initialize();
    	if (createFactorTableOnInit())
    	{
    		getFactorTable();
    	}
    }
    
    /*--------------------------
     * STableFactorBase methods
     */
    
    /**
     * Removes current factor table instance.
     * <p>
     * After invoking this, {@link #getFactorTableIfComputed()} will return null.
     * <p>
     * The factor table can be recreated by calling {@link #getFactorTable()}.
     * <p>
     * @since 0.08
     */
    public void clearFactorTable()
    {
    	_factorTable = null;
    }
    
    /**
     * Indicate whether {@link #initialize()} should call {@link #getFactorTable()} to
     * force factor table creation (e.g. to avoid lazy initialization for multithreading
     * solvers.
     * <p>
     * The default implementation returns true if the <em>cardinality</em> of the factor is no greater than
     * {@link SolverOptions#maxAutomaticFactorTableSize}, where the <em>cardinality</em> is computed by multiplying
     * the dimensions of the independent domains of the factor, i.e. the input domains if the factor is
     * deterministic directed and otherwise all of the domains.
     */
    protected boolean createFactorTableOnInit()
    {
		final DomainList<?> domains = getFactor().getDomainList();
		final JointDomainIndexer indexer = domains.asJointDomainIndexer();
		
		if (indexer == null || !indexer.supportsJointIndexing())
		{
			return false;
		}
		
		int maxSize = getOptionOrDefault(SolverOptions.maxAutomaticFactorTableSize);
		if (maxSize <= 0)
		{
			return false;
		}
		
		final int cardinality = indexer.isDirected() && getFactor().getFactorFunction().isDeterministicDirected() ?
			indexer.getInputCardinality() : indexer.getCardinality();
		return cardinality <= getOptionOrDefault(SolverOptions.maxAutomaticFactorTableSize);
    }
    
    /**
     * Returns factor table for this factor, creating it if necessary.
     * <p>
     * Note that creation can be expensive for large joint cardinality of domains since the
     * factor function must be evaluated for all possible combinations of values!
     * 
     * @see #getFactorTableIfComputed()
     */
    public final IFactorTable getFactorTable()
	{
    	IFactorTable factorTable = _factorTable;
		if (factorTable==null)
		{
			factorTable = _factorTable = getFactor().getFactorTable();
			setTableRepresentation(factorTable);
		}
		return factorTable;
	}
    
    /**
     * Returns factor table for this factor if it has been created, else null.
     * <p>
     * @see #getFactorTable()
     * @since 0.08
     */
    public final @Nullable IFactorTable getFactorTableIfComputed()
    {
    	return _factorTable;
    }
    
    /**
     * Returns the dimension of the ith variable, assumed to be discrete
     * @since 0.08
     */
    protected int getSiblingDimension(int i)
    {
    	return requireNonNull(getSibling(i).getDomain().asDiscrete()).size();
    }
    
    /**
     * Sets representation of {@code table} for optimal performance for particular
     * solver implementation.
     * <p>
     * Called by {@link STableFactorBase#getFactorTable()} the first time it is called
     * on this object, and is also called by {@link STableFactorBase#initialize()}.
     * <p>
     * @param table
     */
    protected abstract void setTableRepresentation(IFactorTable table);
}
