import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

public class RucksackProblemTest {

	@Before
	public void setUp() throws Exception {
		PackingObject.reset();
	}

	////////////////////////////////////////////////
	// Tests for Rucksack object
	
	@Test 
	public void rucksackCreation() {
		int capacity = 50;
		Rucksack rucksack = new Rucksack(capacity);
		assertEquals(capacity, rucksack.getTotalCapacity());
		assertEquals(capacity, rucksack.getAvailableCapacity());
		assertEquals(0, rucksack.getTotalWeight());
		assertEquals(0, rucksack.getTotalValue());
	}

	@Test
	public void rucksackPutObjectContains() {
		int weight1 = 5;
		int value1 = 4;
		int weight2 = 15;
		int value2 = 14;
		int capacity = 40;
		PackingObject obj1 = new PackingObject(value1, weight1);
		PackingObject obj2 = new PackingObject(value2, weight2);
		Rucksack rucksack = new Rucksack(capacity);
		assertFalse(rucksack.contains(obj1));
		rucksack.putObject(obj1);
		assertTrue(rucksack.contains(obj1));
		assertEquals(weight1, rucksack.getTotalWeight());
		assertEquals(value1, rucksack.getTotalValue());
		assertEquals(capacity - weight1, rucksack.getAvailableCapacity());
		assertFalse(rucksack.contains(obj2));
		rucksack.putObject(obj2);
		assertTrue(rucksack.contains(obj2));
		assertEquals(weight1 + weight2, rucksack.getTotalWeight());
		assertEquals(value1 + value2, rucksack.getTotalValue());
		assertEquals(capacity - weight1 - weight2, rucksack.getAvailableCapacity());
		try {
			rucksack.putObject(obj2);
			assertTrue("Adding an object already present should throw a ObjectAlreadyPresentException!", false);
		} catch (ObjectAlreadyPresentException e) {
		}
	}
	
	@Test
	public void rucksackExceedCapacity() {
		int weight1 = 5;
		int value1 = 4;
		int weight2 = 15;
		int value2 = 14;
		int capacity = 15;
		PackingObject obj1 = new PackingObject(value1, weight1);
		PackingObject obj2 = new PackingObject(value2, weight2);
		Rucksack rucksack = new Rucksack(capacity);
		rucksack.putObject(obj1);
		try {
			rucksack.putObject(obj2);
			assertTrue("Adding an object above capacity should throw a CapacityExceededException!", false);
		} catch (CapacityExceededException e) {
		}
	}

	@Test
	public void rucksackRemoveObjectContains() {
		int weight1 = 5;
		int value1 = 4;
		int weight2 = 15;
		int value2 = 14;
		int capacity = 20;
		PackingObject obj1 = new PackingObject(value1, weight1);
		PackingObject obj2 = new PackingObject(value2, weight2);
		Rucksack rucksack = new Rucksack(capacity);
		rucksack.putObject(obj1);
		rucksack.putObject(obj2);
		rucksack.removeObject(obj2);
		assertEquals(value1, rucksack.getTotalValue());
		assertFalse(rucksack.contains(obj2));
		try {
			rucksack.removeObject(obj2);
			assertTrue("Removing object not present should throw ObjectNotPresentException!", false);
		} catch (ObjectNotPresentException e) {
		}
	}

	@Test
	public void rucksackCopyFrom() {
		int capacity1 = 20;
		PackingObject obj1 = new PackingObject(1,1);
		PackingObject obj2 = new PackingObject(2,2);
		Rucksack rucksack1 = new Rucksack(capacity1);
		Rucksack rucksack2 = new Rucksack(5);
		rucksack1.putObject(obj1);
		rucksack2.copyFrom(rucksack1);
		assertEquals(capacity1, rucksack2.getTotalCapacity());
		assertTrue(rucksack2.contains(obj1));
		
		rucksack1.removeObject(obj1);
		assertFalse(rucksack1.contains(obj1));
		assertTrue(rucksack2.contains(obj1));
		
		rucksack2.putObject(obj2);
		assertFalse(rucksack1.contains(obj2));
		assertTrue(rucksack2.contains(obj2));
	}

	///////////////////////////////////////////////////////////////
	// Tests for RucksackProblem

	private int calculateValue(RucksackProblem problem, Rucksack rucksack) {
		int result = 0;
		for (PackingObject object : problem.getAllObjects()) {
			if (rucksack.contains(object))
				result += object.getValue();
		}
		return result;
	}

	private int calculateWeight(RucksackProblem problem, Rucksack rucksack) {
		int result = 0;
		for (PackingObject object : problem.getAllObjects()) {
			if (rucksack.contains(object))
				result += object.getWeight();
		}
		return result;
	}
	
	private PackingObject[] createPackingObjects(int[] values, int[] weights) {
		PackingObject[] objects = new PackingObject[weights.length];
		for (int i=0; i<weights.length; i++) {
			objects[i] = new PackingObject(values[i], weights[i]);
		}
		return objects;
	}
	
	private RucksackProblem createProblemInstance(int[] values, int[] weights, int capacity) {
		PackingObject[] objects = createPackingObjects(values, weights);
		Rucksack rucksack = new Rucksack(capacity);
		return new RucksackProblem(objects, rucksack);
	}
	
	private void solve(RucksackProblem problem, boolean iterative) {
		if (iterative)
			problem.solveIterative();
		else
			problem.solveRecursive();
	}
	
	@Test
	public void constructorNegativeValues(){
		int[] values  = {7,-5,4};
		int[] weights = {5, 4,4};
		int capacity = 8;
		try {
			createProblemInstance(values, weights, capacity);
			assertTrue("IllegalArgumentException expected on negative input!", false);
		} catch (IllegalArgumentException e) {
		}
	}

	@Test
	public void constructorNegativeWeights(){
		int[] values  = {7, 5,4};
		int[] weights = {5,-4,4};
		int capacity = 8;
		try {
			createProblemInstance(values, weights, capacity);
			assertTrue("IllegalArgumentException expected on negative input!", false);
		} catch (IllegalArgumentException e) {
		}
	}

	@Test
	public void solveIterativeAllObjectsPacked() {
		solveAllObjectsPacked(true);
	}
	
	@Test
	public void solveIterativeEmptyProblem() {
		solveEmptyProblem(true);
	}
	
	@Test
	public void solveIterativeOneObjectPacked() {
		solveOneObjectPacked(true);
	}
	
	@Test
	public void solveIterativeRucksack1() {
		solveRucksack1(true);
	}
	
	@Test
	public void solveIterativeRucksack2() {
		solveRucksack2(true);
	}
	
	@Test
	public void solveRecursiveAllObjectsPacked() {
		solveAllObjectsPacked(false);
	}

	@Test
	public void solveRecursiveEmptyProblem() {
		solveEmptyProblem(false);
	}
	
	@Test
	public void solveRecursiveOneObjectPacked() {
		solveOneObjectPacked(false);
	}
	
	@Test
	public void solveRecursiveRucksack1() {
		solveRucksack1(false);
	}
	
	@Test
	public void solveRecursiveRucksack2() {
		solveRucksack2(false);
	}
	
	public void solveRucksack1(boolean iterative) {
		int[] values  = {34,11,32,100,65,33,300,55,200,37,400,32,76,50,49,19,17,28,7,4};
		int[] weights = {34,11,32, 10,65,33,30, 55, 20,37, 40,32,76, 5,49,19,17,28,7,4};
		int capacity = 105;
		RucksackProblem problem = createProblemInstance(values, weights, capacity);
		
		solve(problem, iterative);
		
		Rucksack result = problem.getOptimalRucksack();
		assertEquals(1050, result.getTotalValue());
		assertEquals(capacity, result.getTotalWeight());
		assertEquals(result.getTotalValue(), calculateValue(problem, result));
		assertEquals(result.getTotalWeight(), calculateWeight(problem, result));
	}
	
	public void solveRucksack2(boolean iterative) {
		int[] values  = {7,5,4};
		int[] weights = {5,4,4};
		int capacity = 8;
		RucksackProblem problem = createProblemInstance(values, weights, capacity);
		
		solve(problem, iterative);

		Rucksack result = problem.getOptimalRucksack();
		assertEquals(9, result.getTotalValue());
		assertEquals(8, result.getTotalWeight());
		assertEquals(result.getTotalValue(), calculateValue(problem, result));
		assertEquals(result.getTotalWeight(), calculateWeight(problem, result));
	}

	public void solveEmptyProblem(boolean iterative) {
		int[] values  = {};
		int[] weights = {};
		int capacity  = 1;
		RucksackProblem problem = createProblemInstance(values, weights, capacity);
		
		solve(problem, iterative);
		
		Rucksack result = problem.getOptimalRucksack();
	
		assertEquals(0, result.getTotalValue());
		assertEquals(0, result.getTotalWeight());
	}

	public void solveAllObjectsPacked(boolean iterative) {
		int[] values  = { 6, 5, 4, 3, 2, 1, 7, 8, 9,10};
		int[] weights = { 1,10, 2, 9, 3, 8, 4, 7, 5, 6};
		int capacity  = 1+2+3+4+5+6+7+8+9+10;
		RucksackProblem problem = createProblemInstance(values, weights, capacity);
		
		solve(problem, iterative);
		
		Rucksack result = problem.getOptimalRucksack();
	
		assertEquals(capacity, result.getTotalValue());
		assertEquals(capacity, result.getTotalWeight());
		for (PackingObject o : problem.getAllObjects()) {
			assertTrue(result.contains(o));
		}
		assertEquals(result.getTotalValue(), calculateValue(problem, result));
		assertEquals(result.getTotalWeight(), calculateWeight(problem, result));
	}

	public void solveOneObjectPacked(boolean iterative) {
		int[] values  = { 1, 1, 1, 100, 1, 1, 1, 1, 1, 1 };
		int[] weights = { 1, 1, 1, 10, 1, 1, 1, 1, 1, 1 };
		int capacity = 10;
		RucksackProblem problem = createProblemInstance(values, weights, capacity);
		
		solve(problem, iterative);
	
		Rucksack result = problem.getOptimalRucksack();
		assertEquals(100, result.getTotalValue());
		assertEquals(10, result.getTotalWeight());
		for (PackingObject object : problem.getAllObjects()) {
			if (object.getIndex() == 3)
				assertTrue(result.contains(object));
			else
				assertFalse(result.contains(object));
		}
		assertEquals(result.getTotalValue(), calculateValue(problem, result));
		assertEquals(result.getTotalWeight(), calculateWeight(problem, result));
	}

}
