import static org.junit.Assert.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;

import org.junit.Test;


public class ISPReferenceTest {

	@Test
	public void calcA_test()
	{
		double expected;
		double actual;
		
		ISPCharge charge = new ISPCharge('A', 50.0);
		expected = 89.95;
		actual = charge.calcA();
		
		assertTrue (String.format(
				"Error in calcA (A/50). Expected: %f\tActual: %f.", 
				expected, actual),
				Math.abs(expected - actual) < .01);
		
		charge = new ISPCharge('A', 10.0);
		expected = 9.95;
		actual = charge.calcA();
		
		assertTrue (String.format(
				"Error in calcA (A/10). Expected: %f\tActual: %f.", 
				expected, actual),
				Math.abs(expected - actual) < .01);
		
		charge = new ISPCharge('A', 1.0);
		expected = 9.95;
		actual = charge.calcA();
		
		assertTrue (String.format(
				"Error in calcA (A/1). Expected: %f\tActual: %f.", 
				expected, actual),
				Math.abs(expected - actual) < .01);
	}
	@Test
	public void calcB_test()
	{
		double expected;
		double actual;
		ISPCharge charge;
		
		charge = new ISPCharge('B', 50.0);
		expected = 43.95;
		actual = charge.calcB();
		
		assertTrue (String.format(
				"Error in calcB (B/50). Expected: %f\tActual: %f.", 
				expected, actual),
				Math.abs(expected - actual) < .01);
		
		charge = new ISPCharge('B', 20.0);
		expected = 13.95;
		actual = charge.calcB();
		
		assertTrue (String.format(
				"Error in calcB (B/20). Expected: %f\tActual: %f.", 
				expected, actual),
				Math.abs(expected - actual) < .01);
	
		charge = new ISPCharge('B', 10.0);
		expected = 13.95;
		actual = charge.calcB();
		
		assertTrue (String.format(
				"Error in calcB (B/20). Expected: %f\tActual: %f.", 
				expected, actual),
				Math.abs(expected - actual) < .01);
	}
	@Test
	public void calcC_test()
	{
		double expected;
		double actual;
		ISPCharge charge;
		
		charge = new ISPCharge('C', 50.0);
		expected = 19.95;
		actual = charge.calcC();
		
		assertTrue (String.format(
				"Error in calcC (C/50). Expected: %f\tActual: %f.", 
				expected, actual), Math.abs(expected - actual) < .01);

		charge = new ISPCharge('C', 0.0);
		expected = 19.95;
		actual = charge.calcC();
		
		assertTrue (String.format(
				"Error in calcC (C/0). Expected: %f\tActual: %f.", 
				expected, actual), Math.abs(expected - actual) < .01);
		
		charge = new ISPCharge('C', 10.0);
		expected = 19.95;
		actual = charge.calcC();
		assertTrue (String.format(
				"Error in calcC (C/10). Expected: %f\tActual: %f.", 
				expected, actual), Math.abs(expected - actual) < .01);
	
	}
	
	@Test
	public void calcCost_test()
	{
		double expected;
		double actual;
		ISPCharge charge;
		
		charge = new ISPCharge('A', 50);
		expected = 89.95;
		actual = charge.calcCost();
		assertTrue (String.format(
				"Error in calcCost (A/50). Expected: %f\tActual: %f.", 
				expected, actual), Math.abs(expected - actual) < .01);

		charge = new ISPCharge('B', 20);
		expected = 13.95;
		actual = charge.calcCost();
		assertTrue (String.format(
				"Error in calcCost (B/20). Expected: %f\tActual: %f.", 
				expected, actual), Math.abs(expected - actual) < .01);

		charge = new ISPCharge('C', 150);
		expected = 19.95;
		actual = charge.calcCost();
		assertTrue (String.format(
				"Error in calcCost (C/150). Expected: %f\tActual: %f.", 
				expected, actual), Math.abs(expected - actual) < .01);
	}
	@Test
	public void calcTax_test()
	{
		ISPCharge charge;
		double expected;
		double actual;
		
		charge = new ISPCharge('A', 50);
		expected = 4.498;
		actual = charge.calcTax();
		assertTrue (String.format(
				"Error in calcTax (A/50). Expected: %f\tActual: %f.", 
				expected, actual), Math.abs(expected - actual) < .01);
		
		charge = new ISPCharge('B', 20);
		expected = .698;
		actual = charge.calcTax();
		assertTrue (String.format(
				"Error in calcTax (B/20). Expected: %f\tActual: %f.", 
				expected, actual), Math.abs(expected - actual) < .01);
		
		charge = new ISPCharge('C', 30);
		expected = 1.00;
		actual = charge.calcTax();
		assertTrue (String.format(
				"Error in calcTax (C/30). Expected: %f\tActual: %f.", 
				expected, actual), Math.abs(expected - actual) < .01);
	}
	
	@Test
	public void saveWithC_test() 
	{
		ISPCharge charge = new ISPCharge('A', 50.0);
		assertTrue ("Error in saveWithC. Expected true. (A/50)", 
				charge.saveWithC());
		
		charge = new ISPCharge('B', 50.0);
		assertTrue ("Error in saveWithC. Expected true. (B/50)", 
				charge.saveWithC());
		
		charge = new ISPCharge('C', 50.0);
		assertFalse("Error in saveWithC. Expected false. (C/50)", 
				charge.saveWithC());
		
		charge = new ISPCharge('A', 15);
		assertFalse("Error in saveWithC. Expected false. (A/15)",
				charge.saveWithC());
		
		charge = new ISPCharge('A', 12);
		assertFalse("Error in saveWithC. Expected false. (A/12)", 
				charge.saveWithC());
		
		charge = new ISPCharge('B', 26);
		assertFalse("Error in saveWithC. Expected false. (B/26)", 
				charge.saveWithC()); 
	}
	@Test
	public void saveWithB_test() 
	{
		ISPCharge charge = new ISPCharge('A', 50.0);
		assertTrue ("Error in saveWithB. Expected true. (A/50)", 
				charge.saveWithB());
		
		charge = new ISPCharge('B', 50.0);
		assertFalse ("Error in saveWithB. Expected false. (B/50)", 
				charge.saveWithB());
		
		charge = new ISPCharge('C', 50.0);
		assertFalse("Error in saveWithB. Expected false. (C/50)", 
				charge.saveWithB());
		
		charge = new ISPCharge('A', 15);
		assertTrue("Error in saveWithB. Expected false. (A/15)",
				charge.saveWithB());
		
		charge = new ISPCharge('A', 12);
		assertFalse("Error in saveWithB. Expected false. (A/12)", 
				charge.saveWithB());
		
		charge = new ISPCharge('B', 26);
		assertFalse("Error in saveWithB. Expected false. (B/26)", 
				charge.saveWithB()); 
	}
	
	@Test
	public void full_test() throws IOException
	{

	    String actual;
	    String expected;
	    
	    // first test, no savings
	    
	    expected = 
	    		"Dukes ISP Billing\n\n" +
	    		"Enter the package code (A, B, or C): " +
	    		"Enter the number of hours used: " +
	       		"\nCustomer Bill\n\n" +
	    		"Package: A\n" +
	    		"Hours Used: 15.00\n" +
	    		"\n" +
	    		"Base Charge: $9.95\n" +
	    		"Base Hours: 10.00\n" +
	    		"Additional Hours: 5.00\n" +
	    		"\n" +
	    		"Total Charge: $19.95\n" +
	    		"Tax: $1.00\n" +
	    		"\n" + 
	    		"Pay this Amount: $20.95\n" +
	    		"\n" + 
	    		"You could have saved $6.00 with package B. Call 1-888-555-1234 for more information.\n";
	    
	    PipedOutputStream pos    = new PipedOutputStream();
	    PipedInputStream  pis    = new PipedInputStream(pos);
	    PrintStream       pout = new PrintStream(pos);

	    System.setIn(pis);
	    
	    pout.println("A\n15\n");
	    pout.flush();
	    
	    ByteArrayOutputStream output = new ByteArrayOutputStream();
	    System.setOut(new PrintStream(output));

	    
	    ISPBilling.main(null);
	    actual = output.toString();
	    	    
	    // Test containment
	    actual = cleanEnds(actual);
	    assertTrue("(A/15)Check your lines. Should be " + countLines(expected) + ", is " + countLines(actual), countLines(cleanEnds(actual)) == countLines(expected));
	    assertTrue("(A/15)Check your money formatting. Could not find $9.95", actual.contains("$9.95"));
	    assertTrue("(A/15)Check your money formatting. Could not find $20.95", actual.contains("$20.95"));
	    assertTrue("(A/15)Check your money formatting. Could not find $19.95", actual.contains("$19.95"));
	    assertTrue("(A/15)Check your money formatting or Savings with B. Could not find $6.00", actual.contains("$6.00"));
	    //System.err.println(expected);
	    //System.err.println(actual);
	    //compare(expected, actual);
	    if (!cleanEnds(actual).equals(expected))
	    {
	    	fail(compareBoth("A/15", expected, cleanEnds(actual)));
	    }
	}
	    @Test
		public void full2_test() throws IOException
		{

		    String actual;
		    String expected;
		    
		    // first test, no savings
		    
		    expected = 
		    		"Dukes ISP Billing\n\n" +
		    		"Enter the package code (A, B, or C): " +
		    		"Enter the number of hours used: " +
		    		"\nCustomer Bill\n\n" +
		    		"Package: A\n" +
		    		"Hours Used: 50.00\n" +
		    		"\n" +
		    		"Base Charge: $9.95\n" +
		    		"Base Hours: 10.00\n" +
		    		"Additional Hours: 40.00\n" +
		    		"\n" +
		    		"Total Charge: $89.95\n" +
		    		"Tax: $4.50\n" +
		    		"\n" + 
		    		"Pay this Amount: $94.45\n" +
		    		"\n" + 
		    		"You could have saved $46.00 with package B. Call 1-888-555-1234 for more information.\n" +
    				"You could have saved $70.00 with package C. Call 1-888-555-1234 for more information.\n";
		    
		    PipedOutputStream pos    = new PipedOutputStream();
		    PipedInputStream  pis    = new PipedInputStream(pos);
		    PrintStream       pout = new PrintStream(pos);

		    System.setIn(pis);
		    
		    pout.println("A\n50\n");
		    pout.flush();
		    
		    ByteArrayOutputStream output = new ByteArrayOutputStream();
		    System.setOut(new PrintStream(output));

		    
		    ISPBilling.main(null);
		    actual = output.toString();
		    	    
		    // Test containment
		    actual = cleanEnds(actual);
		    assertTrue("Check your savings line. Could not find B savings (A/50)", actual.contains("package B"));
		    assertTrue("Check your savings line. Could not find C savings (A/50)", actual.contains("package C"));
		    //System.err.println(expected);
		    //System.err.println(actual);
		    //compare(expected, actual);
		    
		    if (!cleanEnds(actual).equals(expected))
		    {
		    	fail(compareBoth("A/50", expected, actual));
		    }
		    
	}
	    
	    @Test
		public void full3_test() throws IOException
		{

		    String actual;
		    String expected;
		    
		    // first test, no savings
		    
		    expected = 
		    		"Dukes ISP Billing\n\n" +
		    		"Enter the package code (A, B, or C): " +
		    		"Enter the number of hours used: " +
		    		"\nCustomer Bill\n\n" +
		    		"Package: C\n" +
		    		"Hours Used: 50.00\n" +
		    		"\n" +
		    		"Base Charge: $19.95\n" +
		    		"\n" +
		    		"Total Charge: $19.95\n" +
		    		"Tax: $1.00\n" +
		    		"\n" + 
		    		"Pay this Amount: $20.95\n";
		    
		    PipedOutputStream pos    = new PipedOutputStream();
		    PipedInputStream  pis    = new PipedInputStream(pos);
		    PrintStream       pout = new PrintStream(pos);

		    System.setIn(pis);
		    
		    pout.println("c\n50\n");
		    pout.flush();
		    
		    ByteArrayOutputStream output = new ByteArrayOutputStream();
		    System.setOut(new PrintStream(output));

		    
		    ISPBilling.main(null);
		    actual = output.toString();
		    	    
		    // Test containment
		    actual = cleanEnds(actual);
		    //assertFalse("Check your package C output. Should not have Base Hours", actual.contains("Base Hours"));
		    //assertFalse("Check your package C output. Should not have Additional Hours", actual.contains("Additional Hours"));
		    		    
		    if (!cleanEnds(actual).equals(expected))
		    {
		    	fail(compareBoth("c/50", expected, actual));
		    }
		    
	}
	    
	    @Test
		public void error_test() throws IOException
		{

		    String actual;
		    
		    // first test, no savings
		    
   
		    PipedOutputStream pos    = new PipedOutputStream();
		    PipedInputStream  pis    = new PipedInputStream(pos);
		    PrintStream       pout = new PrintStream(pos);

		    System.setIn(pis);
		    
		    pout.println("cache\nQ\n");
		    pout.flush();
		    
		    ByteArrayOutputStream output = new ByteArrayOutputStream();
		    System.setOut(new PrintStream(output));

		    
		    ISPBilling.main(null);
		    actual = output.toString();
		    	    
		    // Test containment
		    actual = cleanEnds(actual);
		    //System.err.println(actual);
		    assertTrue("Global scanner, instantiate in main", !actual.contains("entered ."));
            assertTrue("Error checking problem, package code was \"cache\"", 
		    		actual.indexOf("cache") != -1);
		    assertTrue("Error checking problem, check format of the package message",
		    		actual.indexOf("Your entry, cache is invalid. Using A") != -1);
		    assertTrue("Error checking problem, hours was \"Q\"",
		    		actual.indexOf("Q") != -1);
		    assertTrue("Error checking problem, check format of the hours message", 
		    		actual.indexOf("Your entry, Q is invalid. Using 0") != -1);
		    
	}
	public static int countLines(String actual) {
		
		int count = 0;
		
		for (int ii = 0; ii < actual.length(); ii++)
		{
			if (actual.charAt(ii) == '\n')
				count++;
		}
		return count;
	}
	public static String cleanEnds(String actual) 
	{
		return actual.replaceAll("\r", "");
	}
	public static String compareBoth(String input, String expected, String actual)
	{
		//System.err.println(expected);
		//System.err.println(actual);
		String message = null;
		int line = 1;
		int pos = 0;
		int size = expected.length();
		if (actual.length() < expected.length())
			size = actual.length();
		for (int ii = 0; ii < size && message == null; ii++)
		{
			pos++;
			
			if (expected.charAt(ii) == '\n')
			{
				line++;
				pos = 0;
			}
			if (expected.charAt(ii) != actual.charAt(ii))
			{
				message = "(" + input + ") First difference at position (line/pos) (" + line + "/" + pos + 
					"). Expected: " + expected.charAt(ii) + " Actual: " + actual.charAt(ii);
				return message;
			}
		}
      if (message == null)
         message = "Check the end of your output. It should contain a newline character";
		return message;
	}
}
