/*******************************************************************************
*   Copyright 2012 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.util.misc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

import com.analog.lyric.dimple.factorfunctions.XorDelta;
import com.analog.lyric.dimple.factorfunctions.core.FactorFunction;
import com.analog.lyric.dimple.model.core.FactorGraph;
import com.analog.lyric.dimple.model.core.INameable;
import com.analog.lyric.dimple.model.factors.Factor;
import com.analog.lyric.dimple.model.variables.Discrete;
import com.analog.lyric.dimple.model.variables.Variable;
import com.analog.lyric.dimple.model.variables.VariableList;
import com.analog.lyric.dimple.schedulers.schedule.FixedSchedule;
import com.analog.lyric.dimple.schedulers.scheduleEntry.NodeScheduleEntry;
import com.analog.lyric.dimple.solvers.interfaces.IFactorGraphFactory;

public class Misc
{
	/**
	 * A do-nothing function you can use for in-code conditional breakpoints when debugging.
	 * <p>
	 * Just set a method breakpoint on this method in Eclipse and leave it there and
	 * insert this
	 */
	public static void breakpoint()
	{
	}
	
	public static Object [] nDimensionalArray2indicesAndValues(Object obj)
	{
		Object tmpObj = obj;
		ArrayList<Integer> sizes = new ArrayList<Integer>();
		int size = 1;
		while (tmpObj.getClass().isArray())
		{
			if (tmpObj instanceof double [])
			{
				int sz = ((double[])tmpObj).length;
				size*=sz;
				sizes.add(sz);
				break;
			}
			else
			{
				Object [] tmp = (Object[])tmpObj;
				sizes.add(tmp.length);
				size*=tmp.length;
				tmpObj = tmp[0];
			}
		}
		
		int [][] indices = new int[size][];
		double [] values = new double[size];
		
		int [] domainSizes = new int[sizes.size()];
		for (int i = 0; i < domainSizes.length; i++)
			domainSizes[i] = sizes.get(i);
		
		IndexCounter ic = new IndexCounter(domainSizes);
		int index = 0;
		
		for ( int [] counter : ic)
		{
			tmpObj = obj;
			int i = 0;
			while (tmpObj.getClass().isArray())
			{
				if (tmpObj instanceof double[])
					tmpObj = ((double[])tmpObj)[counter[i]];
				else
					tmpObj = ((Object[])tmpObj)[counter[i]];
				i++;
			}
			
			final double value = (Double)tmpObj;
			
			if (value != 0.0)
			{
				// Skip zero values
				values[index] = value;
				indices[index] = counter.clone();
			
				index++;
			}
		}
		
		if (index < size)
		{
			// Trim arrays if necessary
			indices = Arrays.copyOf(indices, index);
			values = Arrays.copyOf(values, index);
		}
		
		return new Object[]{indices,values};
		
	}
	
	public static long [] 			convertToIds(Collection<?> nameables)
	{
		//ArrayList<Integer> result = new ArrayList<Integer>();
		long [] ids = new long[nameables.size()];
		
		int i = 0;
		for (Object n : nameables)
		{
    		ids[i] = ((INameable)n).getGlobalId();
    		i++;
			//result.add(((INameable)n).getId());
    	}
		
		
		return ids;
	}

	
	public static FactorGraph 	makeXorGraphFromMatrix(byte[][] matrix, IFactorGraphFactory<?> solver)
    {
    	return makeGraphFromMatrix(matrix,
    							   new Double[]{0.0, 1.0},
    							   new XorDelta(),
    							   solver);
    }
	
    public static FactorGraph 	makeGraphFromMatrix(byte[][] matrix,
    												Object[] domain,
    												FactorFunction f,
    												IFactorGraphFactory<?> solver)
    {
    	
    	//Make all variables first
    	Discrete[] variables = new Discrete[matrix[0].length];
    	for(int i = 0; i < variables.length; ++i)
    	{
    		variables[i] = new Discrete(domain);
    	}

    	FactorGraph fg = new FactorGraph();
    	fg.setSolverFactory(solver);
    	fg.addVariables(variables);
	
    	//Now go function by function (row by row)
    	Object[] oneFactorsArgs = null;
    	for(int i = 0; i < matrix.length; ++i)
    	{
    		//###########################################################
    		//## Variables for this function
    		//###########################################################
    		//First build up array of the variable args to the function.
    		//count args for *this* factor (this row)
    		int numArgs = 0;
    		for(int j = 0; j < matrix[i].length; ++j)
    		{
    			if(matrix[i][j] != 0)
    			{
    				numArgs++;
    			}
    		}
    		//if num args is different than last iteration,
    		//make a new array with appropriate size
    		if(oneFactorsArgs == null ||
    		   numArgs != oneFactorsArgs.length)
    		{
        		oneFactorsArgs = new Discrete[numArgs];
    		}
    		//Actually store the variable args
    		int nextArgIdx = 0;
    		for(int j = 0; j < matrix[i].length; ++j)
    		{
    			if(matrix[i][j] != 0)
    			{
    				oneFactorsArgs[nextArgIdx] = variables[j];
    				nextArgIdx++;
    			}
    		}
    		
    		//###########################################################
    		//## The function
    		//###########################################################
    		fg.addFactor(f, oneFactorsArgs);
    	}
    	
    	return fg;
    }
	static public double[][] getBeliefs(FactorGraph fg)
	{
		VariableList vs = fg.getVariablesFlat();
		ArrayList<Variable> vbs = (ArrayList<Variable>)vs.values();
		double[][] beliefs = new double[vbs.size()][];
		for(int i = 0; i < vbs.size(); ++i)
		{
			beliefs[i] = ((Discrete)vbs.get(i)).getBelief();
		}
		return beliefs;
	}

	static public FixedSchedule nodeFloodingSchedule(FactorGraph fg)
	{
		FixedSchedule fs = new FixedSchedule(fg);
		for(Variable vb : fg.getVariablesFlat().values())
		{
			fs.add(new NodeScheduleEntry(vb));
		}
		for(Factor f : fg.getNonGraphFactorsFlat().values())
		{
			fs.add(new NodeScheduleEntry(f));
		}
		return fs;
	}
}
