/*******************************************************************************
*   Copyright 2015 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.core;

import static java.util.Objects.*;

import com.analog.lyric.dimple.model.factors.Factor;
import com.analog.lyric.dimple.model.variables.Variable;
import com.analog.lyric.util.misc.Internal;

/**
 * Describes an edge from a factor to a variable.
 * <p>
 * Edge state objects are maintained by each {@link FactorGraph} for the edges that connect
 * to it's directly owned {@link Factor}s.
 * <p>
 * To save memory, these objects do not contain a pointer to the owning graph and some methods
 * will require a reference to the parent graph. You can wrap this with an {@link Edge} object
 * if you want a self-contained edge reference.
 * <p>
 * @since 0.08
 * @author Christopher Barber
 */
public abstract class EdgeState
{
	/*-------
	 * State
	 */
	
	int _factorToVariableEdgeNumber = -1;
	int _variableToFactorEdgeNumber = -1;
	
	/*------------------------------
	 * FactorGraphEdgeState methods
	 */
	
	/**
	 * Describes the direction of the edge.
	 * @since 0.08
	 */
	public EdgeDirection direction(FactorGraph graph)
	{
		// TODO - perhaps we can store the direction directly in the edge state
		return getFactor(graph).getEdgeDirection(getFactorToVariableEdgeNumber());
	}
	
	/**
	 * The index of this edge within the {@link FactorGraph} that owns it's factor.
	 * <p>
	 * This index can be used to look up this object within the parent graph of the factor
	 * using the {@link FactorGraph#getSiblingEdgeState(int)} method.
	 * <p>
	 * If {@link #isLocal()} then this will be the same as {@link #variableEdgeIndex()}.
	 * <p>
	 * @since 0.08
	 * @category internal
	 */
	@Internal
	public abstract int factorEdgeIndex();
	
	/**
	 * The index of this edge within the {@link FactorGraph} that owns it's variable.
	 * <p>
	 * This index can be used to look up this object within the parent graph of its variable
	 * using the {@link FactorGraph#getSiblingEdgeState(int)} method.
	 * <p>
	 * If {@link #isLocal()} then this will be the same as {@link #factorEdgeIndex()}.
	 * <p>
	 * @since 0.08
	 * @category internal
	 */
	@Internal
	public abstract int variableEdgeIndex();

	/**
	 * Given an end node for this edge, return the corresponding edge index.
	 * 
	 * @param node Must be a variable or factor at one end of the edge.
	 * @return {@link #variableEdgeIndex()} or {@link #factorEdgeIndex()} as appropriate.
	 * @since 0.08
	 * @category internal
	 */
	@Internal
	public abstract int edgeIndex(Node node);

	/**
	 * Given the parent graph containing this edge, return its index in the graph.
	 * @since 0.08
	 * @category internal
	 */
	@Internal
	public abstract int edgeIndexInParent(FactorGraph graph);
	
	/**
	 * Return instance of {@link Factor} end of edge, given parent graph.
	 * <p>
	 * @param graph is the parent graph of either the variable or factor ends of the edge.
	 * @since 0.08
	 */
	abstract public Factor getFactor(FactorGraph graph);
	
	/**
	 * Returns sibling edge number from the perspective of {@link Factor} endpoint of the edge.
	 * <p>
	 * @return sibling edge number or else -1 if edge is not currently connected to its endpoints.
	 * @since 0.08
	 * @see #getVariableToFactorEdgeNumber()
	 */
	public final int getFactorToVariableEdgeNumber()
	{
		return _factorToVariableEdgeNumber;
	}
	
	/**
	 * Return instance of parent of factor end of edge, given parent of either edge.
	 * @since 0.08
	 * @category internal
	 */
	@Internal
	abstract public FactorGraph getFactorParent(FactorGraph graph);
	
	/**
	 * Return instance of {@link Variable} end of edge, given parent graph.
	 * <p>
	 * @param graph is the parent graph of either the variable or factor ends of the edge.
	 * @since 0.08
	 */
	abstract public Variable getVariable(FactorGraph graph);
	
	/**
	 * Returns sibling edge number from the perspective of {@link Variable} endpoint of the edge.
	 * <p>
	 * @return sibling edge number or else -1 if edge is not currently connected to its endpoints.
	 * @since 0.08
	 * @see #getFactorToVariableEdgeNumber()
	 */
	public final int getVariableToFactorEdgeNumber()
	{
		return _variableToFactorEdgeNumber;
	}

	/**
	 * Return instance of parent of variable end of edge, given parent of either edge.
	 * @since 0.08
	 * @category internal
	 */
	@Internal
	abstract public FactorGraph getVariableParent(FactorGraph graph);
	
	/**
	 * Given one endpoint of edge, return the other one.
	 * <p>
	 * That is given the {@link Variable} end of the edge, this returns the {@link Factor}, and
	 * vice-versa.
	 * <p>
	 * @since 0.08
	 */
	public final Node getSibling(INode node)
	{
		final FactorGraph graph = requireNonNull(node.getContainingGraph());
		
		return node.isVariable() ? getFactor(graph) : getVariable(graph);
	}
	
	/**
	 * Given one endpoint of edge, find the sibling number from that endpoint to the other.
	 * <p>
	 * @param node is either the {@link Variable} or {@link Factor} at one end of the edge.
	 * @return index that could be used to lookup this edge using {@link Node#getSiblingEdgeState(int)}
	 * on the given {@code node}.
	 * @since 0.08
	 */
	public final int getSiblingIndex(Node node)
	{
		return node.indexOfSiblingEdgeState(this);
	}
	
	/**
	 * The local id of the {@link Factor} referred to by this edge within its owning {@link FactorGraph}.
	 * @since 0.08
	 */
	public int factorLocalId()
	{
		return Ids.localIdFromParts(Ids.FACTOR_TYPE, factorIndex());
	}

	/**
	 * The index of the {@link Factor} referred to by this edge within its owning {@link FactorGraph}.
	 * @since 0.08
	 */
	abstract public int factorIndex();
	
	/**
	 * True if both the factor and variable are owned by the same graph.
	 * @since 0.08
	 */
	abstract public boolean isLocal();
	
	/**
	 * Describe type of edge with respect to given graph.
	 * @since 0.08
	 */
	abstract public Edge.Type type(FactorGraph graph);
	
	/**
	 * The index of the {@link Variable} referred to by this edge within the graph that owns this edge.
	 * <p>
	 * Note that the graph that owns the edge is the one that owns the edge's {@link Factor}, which may
	 * not be the owner of the variable. In that case, this will be the index of the corresponding
	 * boundary variable.
	 * <p>
	 * @since 0.08
	 */
	abstract public int variableIndex();
	
	/**
	 * The local id of the {@link Variable} referred to by this edge within the graph that owns this edge.
	 * <p>
	 * Note that the graph that owns the edge is the one that owns the edge's {@link Factor}, which may
	 * not be the owner of the variable. In that case, this will return a boundary variable identifier.
	 * <p>
	 * @since 0.08
	 */
	abstract public int variableLocalId();
	
	/**
	 * Set edge index to a new value with respect to given parent graph.
	 * @param newEdgeIndex must be smaller than the existing value of {@link #edgeIndexInParent(FactorGraph)}.
	 * @since 0.08
	 */
	abstract void setEdgeIndexInParent(FactorGraph graph, int newEdgeIndex);
	
	/**
	 * Set index of edge's factor to a new value.
	 * @param newIndex must be smaller than the existing value of {@link #factorIndex()}
	 * @since 0.08
	 */
	abstract void setFactorIndex(int newIndex);

	/**
	 * Set index of edge's variable to a new value.
	 * @param newIndex must be smaller than the existing value of {@link #variableIndex()}
	 * @since 0.08
	 */
	abstract void setVariableIndex(int newIndex);
}