/*******************************************************************
 * Questions / Fragen
 *******************************************************************
 * a) What are the asymptotic time complexities T(n) of the 
 * recursive and iterative versions of the algorithm, with n being 
 * the total number of available packing objects? State your answer 
 * in big-Oh notation.
 *
 * Was sind die Laufzeitkomplexitäten T(n) der rekursiven und der
 * iterativen Version des Algorithmus, wobei n die Anzahl der 
 * packbaren Objekte ist? Geben Sie die Komplexität in O-Notation an.
 * ----------------------------------------------------------------
 * 
 * Iterativ: solveIterative = O()
 * 
 * Recursiv: solveRecursive = O()
 * ----------------------------------------------------------------
 * b) Use TPTP to profile RucksackProblem.main(). Try different 
 * values for the variable numObjects in main() and measure the 
 * total execution time of solveRecursive(int) and solveIterative,
 * and the number of recursive calls to solveRecursive. To what 
 * problem size can you go on your machine? 
 * Enter at least 5 different, meaningful runs in the table below. 
 * 
 * Verwenden Sie TPTP um RucksackProblem.main() zu profilen. 
 * Probieren Sie verschiedene Werte für die Variable numObjects in
 * main() aus und messen Sie die Laufzeit von solveRecursive(int)
 * und solveIterative sowie die Anzahl der rekursiven Aufrufe von 
 * solveRecursive(int). Bis zu welcher Größe können Sie auf Ihrem 
 * Rechner gehen? Tragen Sie mindestens
 * 5 verschiedene, aussagekräftige Resultate in der Tabelle ein. 
 * ----------------------------------------------------------------
 * 
 * TODO: Answer / Antwort
 * 
 * numObj	| Cumul. time (iter)| Cumul. time (rec) | Recursive calls
 * ---------+-------------------+-------------------+----------------
 * 			|					|					|	
 * 			|					|					|	
 * 			|					|					|	
 * 			|					|					|	
 * 			|					|					|	
 * 			|					|					|	
 * 
 *******************************************************************/
import java.util.Deque;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Random;

/**
 * A class describing an instance of the Rucksack problem. A call to the
 * constructor initializes the problem, and a call to solve() starts a
 * recursive search.
 */
public class RucksackProblem {
	
	private PackingObject[] objects;
	
	private Rucksack init_rucksack = null;
	private Rucksack best_rucksack = null;
	
	/**
	 * The constructor initializes the class with a set of objects to pack
	 * and a rucksack of a given capacity. 
	 *  
	 * @param objects An array of PackingObjects to store optimally in the 
	 *                rucksack
	 * @param rucksack A Rucksack that has been initialized with some 
	 *                capacity
	 */
	public RucksackProblem(PackingObject[] objects, Rucksack rucksack) {

		//check objects
		for(int i=0; i< objects.length; i++)
		{
			if(objects[i].getValue() < 0 ||
			   objects[i].getWeight() < 0)
			{
				throw new IllegalArgumentException();
			}
		}
		
		//Check rucksack
		if(rucksack == null)
		{
			throw new IllegalArgumentException();
		}

		//Initialize Data
		this.objects = objects;
		this.init_rucksack = rucksack;
		this.best_rucksack = new Rucksack(0);
	}
	
	public Rucksack getOptimalRucksack() {
		return this.best_rucksack;
	}
	
	public PackingObject[] getAllObjects() {
		return objects;
	}

	/**
	 * Start the recursive algorithm to solve the Rucksack problem at
	 * the 0-th object.
	 */
	public void solveRecursive() {
		solveRecursive(0);
	}
	
	/**
	 * The recursive algorithm. Do not use loops here, implement only
	 * using recursive calls. 
	 * 
	 * For every object, tries to solve the rest of the problem both 
	 * with the object and without the object using recursive calls 
	 * to solveRecursive(i+1).
	 * 
	 * @param i the current position in the object array.
	 * 
	 * T(n) = 2T(n-1) + const
	 */
	private void solveRecursive(int i) {
		if(i >= this.objects.length)
		{
			//Anchor -> No Objects left in Array
			return;
		} else
		{
			//Solve without the actual object
			solveRecursive(i+1);
			
			try
			{
				//Put Object into Rucksack if possible
				this.init_rucksack.putObject(this.objects[i]); //throws
				
				//Is New Rucksack better then the old?
				if(init_rucksack.getTotalValue() > best_rucksack.getTotalValue())
				{
					this.best_rucksack.copyFrom(this.init_rucksack);
				}
				//Solve with object
				solveRecursive(i+1);
				
				//Remove Object again -> so all cases are covered
				this.init_rucksack.removeObject(this.objects[i]); //can not throw -> object is in list
			
			//Catch Exceptions if putObject fails 
			} catch(ObjectAlreadyPresentException e1){}
			  catch(CapacityExceededException e2){}
		}
	}
	
	/**
	 * The iterative version of the algorithm. Do not include any
	 * recursive calls, but implement it using a loop and a stack.
	 * 
	 * Loop:
	 *  	Have we processed all objects?
	 *  		Stop if there are no backtracking points left
	 *  		Else backtrack to last object that was put into rucksack 
	 *  		Remove it and continue with next object
	 * 
	 *  	Does the object still fit into the available capacity?
	 *  		Put object into rucksack
	 *  		Remember this object as a backtracking point
	 *  		Remember if the new rucksack is the best we know
     * 
	 * 		Continue loop with next object
	 */
	public void solveIterative() {
		
		// A stack to use with the algorithm
		Deque<Integer> stack = new LinkedList<Integer>();

		// The current position in the array of objects
		int i = 0;
		
		//Do till done
		while(true)
		{
			//Still objects in array?
			if(i < this.objects.length)
			{	
				try
				{
					//Put Object into Rucksack if possible
					this.init_rucksack.putObject(this.objects[i]); //throws
					
					//Put current Index to stack to allow backtracking
					stack.push(i);
					
					//Is New Rucksack better then the old?
					if(init_rucksack.getTotalValue() > best_rucksack.getTotalValue())
					{
						this.best_rucksack.copyFrom(this.init_rucksack);
					}
				
				//Catch Exceptions if putObject fails
				} catch(ObjectAlreadyPresentException e1){}
				  catch(CapacityExceededException e2){}
				    
				i++; //Move to next Object
			} else
			{
				try
				{
					//get index of backtracking element from stack
					int obj = stack.pop(); //throws when empty
				
					//remove the object
					this.init_rucksack.removeObject(this.objects[obj]);
					
					//start search with objects excluding the obj from the stack
					i = obj +1;
					
				//Catch exception when stack is empty
				} catch(NoSuchElementException e1)
				{
					//All cases covered
					return;
				}
			}
			
		}
	}

	public static void main(String[] argv) {
		int numObjects = 5;
		
		PackingObject[] objects = new PackingObject[numObjects];
		Random rand = new Random();
		for (int i=0; i<numObjects; i++) {
			objects[i] = new PackingObject(rand.nextInt(100), rand.nextInt(100));
		}

		// Invoke recursive algorithm
		Rucksack rucksack = new Rucksack(numObjects * 25);
		RucksackProblem problem = new RucksackProblem(objects, rucksack);
		problem.solveRecursive();

		// Invoke iterative algorithm
		rucksack = new Rucksack(numObjects * 25);
		problem = new RucksackProblem(objects, rucksack);
		problem.solveIterative();
	}
}
