/**
 * Copyright (c) 2012-2013, Daniele Codecasa <codecasa.job@gmail.com>,
 * Models and Algorithms for Data & Text Mining (MAD) laboratory of
 * Milano-Bicocca University, and all the CTBNCToolkit contributors
 * that will follow.
 * All rights reserved.
 *
 * @author Daniele Codecasa and all the CTBNCToolkit contributors that will follow.
 * @copyright 2012-2013 Daniele Codecasa, MAD laboratory, and all the CTBNCToolkit contributors that will follow
 */
package CTBNCToolkit;

import java.util.*;

/**
 * 
 * @author Daniele Codecasa <codecasa.job@gmail.com>
 * 
 * Class that define a standard trajectory version.
 * 
 * @param <TimeType> type of the time interval (Integer = discrete time, Double = continuous time)
 */
public class CTTrajectory<TimeType extends Number> implements ITrajectory<TimeType> {

	private NodeIndexing nodeIndexing;
	private String name;
	private List<CTTransition<TimeType>> transitions;
	
	/**
	 * Empty constructor. To use the trajectory
	 * after called this constructor you have to
	 * call the function setTransisions.
	 * 
	 * @param nodeIndexing global node indexing to use
	 */
	protected CTTrajectory(NodeIndexing nodeIndexing) {

		if( nodeIndexing == null)
			throw new IllegalArgumentException("Error: null nodeIndexing argument");
		
		this.nodeIndexing = nodeIndexing;
		this.transitions = new Vector<CTTransition<TimeType>>();
	}
	
	/**
	 * Base constructor.
	 * Note: please insert only the changed nodes
	 * for each time slice.
	 * 
	 * @param nodeIndexing global node indexing to use
	 * @param times list of time value
	 * @param values list of nodes values in the related time
	 * @throws IllegalArgumentException in case of wrong arguments
	 */
	public CTTrajectory(NodeIndexing nodeIndexing, List<TimeType> times, List<String[]> values) throws IllegalArgumentException {
		
		if( nodeIndexing == null)
			throw new IllegalArgumentException("Error: null nodeIndexing argument");
		if( times == null)
			throw new IllegalArgumentException("Error: null value for the time list");
		if( values == null)
			throw new IllegalArgumentException("Error: null value for the values list");
		if( times.size() != values.size())
			throw new IllegalArgumentException("Error: lists of the constructor argument with different dimension");
		
		this.nodeIndexing = nodeIndexing;
		this.transitions = new Vector<CTTransition<TimeType>>(times.size());
		for(int i = 0; i < times.size(); ++i) {
			this.transitions.add(new CTTransition<TimeType>(this.nodeIndexing, times.get(i), values.get(i)));
		}
	}
	
	/**
	 * Set manually the transitions
	 * 
	 * @param transitions the transitions to set.
	 */
	protected void setTransisions(List<CTTransition<TimeType>> transitions) {

		this.transitions = transitions;
	}
	
	/* (non-Javadoc)
	 * @see CTBNToolkit.ITrajectory#getTransitionsNumber()
	 */
	@Override
	public int getTransitionsNumber() {

		return this.transitions.size();
	}

	/* (non-Javadoc)
	 * @see CTBNToolkit.ITrajectory#getNodeValue(int, int)
	 */
	@Override
	public String getNodeValue(int iTransition, int nodeIndex) throws IllegalArgumentException {

		if( iTransition < 0 || iTransition >=  this.transitions.size())
			throw new IllegalArgumentException("Error: transition index out of bound");
		
		String result = null;
		for(;iTransition >= 0 && result == null; --iTransition)
			result = this.transitions.get(iTransition).getNodeValue( nodeIndex);
		
		if( result == null)
			throw new IllegalArgumentException("Error: node " + this.nodeIndexing.getName( nodeIndex) + " didn't find");
		
		return result;
	}

	/* (non-Javadoc)
	 * @see CTBNToolkit.ITrajectory#getTransitionTime(int)
	 */
	@Override
	public TimeType getTransitionTime(int iTransition) throws IllegalArgumentException {
		
		if( iTransition < 0 || iTransition >=  this.transitions.size())
			throw new IllegalArgumentException("Error: transition index out of bound");
		
		return this.transitions.get( iTransition).getTime();
	}
	
	/* (non-Javadoc)
	 * @see CTBNToolkit.ITrajectory#getTransition(int)
	 */
	@Override
	public CTTransition<TimeType> getTransition(int iTransition) {

		if( iTransition < 0 || iTransition >=  this.transitions.size())
			throw new IllegalArgumentException("Error: transition index out of bound");
		
		return this.transitions.get(iTransition);
	}
	
	/**
	 * Print the trajectory.
	 */
	public String toString() {
		
		StringBuilder strBuilder = new StringBuilder();
		
		// Handing
		strBuilder.append( "t");
		for(int iNode = 0; iNode < this.nodeIndexing.getNodesNumber(); ++iNode)
			strBuilder.append( "," + this.nodeIndexing.getName(iNode));
		strBuilder.append( "\n");
		
		// Transitions
		for( int iJmp = 0; iJmp < this.getTransitionsNumber(); ++iJmp) {
			strBuilder.append( this.getTransition(iJmp).getTime());
			for(int iNode = 0; iNode < this.nodeIndexing.getNodesNumber(); ++iNode)
				strBuilder.append( "," + this.getNodeValue(iJmp, iNode));
			strBuilder.append( "\n");
		}
		
		return strBuilder.toString();
	}
	
	/* (non-Javadoc)
	 * @see CTBNToolkit.ITrajectory#setName(java.lang.String)
	 */
	@Override
	public void setName(String name) {
		
		this.name = name;
	}
	
	/* (non-Javadoc)
	 * @see CTBNToolkit.ITrajectory#getName()
	 */
	@Override
	public String getName() {
		
		return this.name;
	}

	/* (non-Javadoc)
	 * @see CTBNToolkit.ITrajectory#getNodeIndexing()
	 */
	@Override
	public NodeIndexing getNodeIndexing() {
		
		return this.nodeIndexing;
	}

	
	/**
	 * Cut the trajectory length to a 
	 * percentage of the original length.
	 * 
	 * @param trj trajectory to cut
	 * @param cutPercentage percentage of cutting in (0,1]
	 * @return new shorter trajectory (with a subset of the same instance of transitions)
	 * @throws IllegalArgumentException in case of illegal arguments
	 */
	static public CTTrajectory<Double> cutTrajectory(CTTrajectory<Double> trj, double cutPercentage) //? TODO test
			throws IllegalArgumentException {

		if( trj == null)
			throw new IllegalArgumentException("Error: null trajectory");
		if( trj.getTransitionsNumber() == 0)
			throw new IllegalArgumentException("Error: empty trajectory (name = " + trj.getName() + ")");
		if( cutPercentage <= 0 || cutPercentage > 1)
			throw new IllegalArgumentException("Error: the cutting percentage must be in (0,1]");
		
		// Searching of the last transition to keep
		double tMax = trj.getTransitionTime( trj.getTransitionsNumber() - 1) * cutPercentage;
		int iMin = 0; 
		int iMax = trj.getTransitionsNumber() - 1;
		while( iMin <= iMax) {
			int i = iMin + (iMax - iMin)/2;
			CTTransition<Double> trans = trj.getTransition(i);
			
			if( trans.getTime() < tMax)
				iMin = i + 1;
			else if( trans.getTime() > tMax)			
				iMax = i - 1;
			else {
				iMax = i;
				iMin = i+1;
			}
		}
		CTTransition<Double> lastTrans = trj.getTransition(iMax);
		
		// Transition of the new trajectory
		List<CTTransition<Double>> trjList = trj.transitions.subList(0, iMax + 1);
		if( lastTrans.getTime() != tMax) {		// define the termination point
			String[] values = new String[trj.nodeIndexing.getNodesNumber()];
			for(int i = 0; i < trj.nodeIndexing.getNodesNumber(); ++i)
				values[i] =  lastTrans.getNodeValue(i);
			
			trjList.add(new CTTransition<Double>(trj.nodeIndexing, tMax, values));
		}
		
		// New trajectory generation
		CTTrajectory<Double> newTrj = new CTTrajectory<Double>(trj.nodeIndexing);
		newTrj.setTransisions( trjList);
		newTrj.setName( trj.getName());
		
		return newTrj;
	}

}
