import static org.junit.jupiter.api.Assertions.*;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Scanner;

import org.junit.jupiter.api.Test;

/**
 * Test class for the RobustPaycheck program.
 *
 * @author Sarah Heckman
 * @author Jessica Young Schmidt
 */
public class PaychecksTest {
    /** Delta for comparing doubles */
    public static final double DELTA = 0.0001;

    /**
     * Test the constants for correct value
     */
    @Test
    public void testConstants() {
        assertEquals(1, Paychecks.LEVEL_1, "Level 1");
        assertEquals(2, Paychecks.LEVEL_2, "Level 2");
        assertEquals(3, Paychecks.LEVEL_3, "Level 3");
        assertEquals(1900, Paychecks.LEVEL_1_PAY_RATE, "Level 1 pay rate");
        assertEquals(2250, Paychecks.LEVEL_2_PAY_RATE, "Level 2 pay rate");
        assertEquals(2575, Paychecks.LEVEL_3_PAY_RATE, "Level 3 pay rate");
    }

    /**
     * Test the Paychecks.getPayRate() method.
     */
    @Test
    public void testGetPayRate() {
        // Test level 1
        assertEquals(Paychecks.LEVEL_1_PAY_RATE,
                Paychecks.getPayRate(Paychecks.LEVEL_1),
                "Paychecks.getPayRate(Paychecks.LEVEL_1)");

        // Test level 2
        assertEquals(Paychecks.LEVEL_2_PAY_RATE,
                Paychecks.getPayRate(Paychecks.LEVEL_2),
                "Paychecks.getPayRate(Paychecks.LEVEL_2)");

        // Test level 3
        assertEquals(Paychecks.LEVEL_3_PAY_RATE,
                Paychecks.getPayRate(Paychecks.LEVEL_3),
                "Paychecks.getPayRate(Paychecks.LEVEL_3)");

        // Test level 0 - invalid lower boundary
        Exception exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.getPayRate(0), "Testing level 0");
        assertEquals("Invalid Level", exception.getMessage(),
                "Testing level 0 - exception message");

        // Test level 4 - invalid upper boundary
        exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.getPayRate(4), "Testing level 4");
        assertEquals("Invalid Level", exception.getMessage(),
                "Testing level 4 - exception message");

    }

    /**
     * Test the Paychecks.calculateRegularPay() method.
     */
    @Test
    public void testCalculateRegularPay() {

        // Less than 40 hours
        // Regular Level 1 36 hours
        assertEquals(68400,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 36),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 36)");

        // Regular Level 2 36 hours
        assertEquals(81000,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 36),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 36)");

        // Regular Level 3 36 hours
        assertEquals(92700,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 36),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 36)");

        // Over 40 hours
        // Regular Level 1 46 hours
        assertEquals(76000,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 46),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 46)");

        // Regular Level 2 46 hours
        assertEquals(90000,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 46),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 46)");

        // Regular Level 3 46 hours
        assertEquals(103000,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 46),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 46)");

        // Fractional hours less than 40
        // Regular Level 1 36.5 hours
        assertEquals(69350,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 36.5),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 36.5)");

        // Regular Level 2 36.5 hours
        assertEquals(82125,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 36.5),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 36.5)");

        // Regular Level 3 36.5 hours
        assertEquals(93987,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 36.5),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 36.5)");

        // Test a payrate other than a given constant Regular
        assertEquals(34950, Paychecks.calculateRegularPay(1376, 25.4),
                "Paychecks.calculateRegularPay(1376, 25.4)");

        // Testing boundary
        // Less than 40 hours
        // Regular Level 1 39 hours
        assertEquals(74100,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 39),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 39)");

        // Regular Level 2 39 hours
        assertEquals(87750,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 39),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 39)");

        // Regular Level 3 39 hours
        assertEquals(100425,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 39),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 39)");

        // Regular Level 1 40 hours
        assertEquals(76000,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 40),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 40)");

        // Regular Level 2 40 hours
        assertEquals(90000,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 40),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 40)");

        // Regular Level 3 40 hours
        assertEquals(103000,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 40),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 40)");

        // Over 40 hours
        // Regular Level 1 41 hours
        assertEquals(76000,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 41),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_1_PAY_RATE, 41)");

        // Regular Level 2 41 hours
        assertEquals(90000,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 41),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_2_PAY_RATE, 41)");

        // Regular Level 3 41 hours
        assertEquals(103000,
                Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 41),
                "Paychecks.calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, 41)");

        // Test negative hours
        Exception exception = assertThrows(
                IllegalArgumentException.class, () -> Paychecks
                        .calculateRegularPay(Paychecks.LEVEL_3_PAY_RATE, -1),
                "Testing negative hours worked");
        assertEquals("Negative pay rate and/or hours worked",
                exception.getMessage(),
                "Testing negative hours worked - exception message");

        // Test negative pay rate
        exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.calculateRegularPay(-20, 20),
                "Testing negative pay rate");
        assertEquals("Negative pay rate and/or hours worked",
                exception.getMessage(),
                "Testing negative pay rate - exception message");
    }

    /**
     * Test the Paychecks.calculateOvertimePay() method.
     */
    @Test
    public void testCalculateOvertimePay() {

        // Less than 40 hours
        // Overtime Level 1 36 hours
        assertEquals(0,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_1_PAY_RATE, 36),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_1_PAY_RATE, 36)");

        // Overtime Level 2 36 hours
        assertEquals(0,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_2_PAY_RATE, 36),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_2_PAY_RATE, 36)");

        // Overtime Level 3 36 hours
        assertEquals(0,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_3_PAY_RATE, 36),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_3_PAY_RATE, 36)");

        // Over 40 hours
        // Overtime Level 1 46 hours
        assertEquals(17100,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_1_PAY_RATE, 46),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_1_PAY_RATE, 46)");

        // Overtime Level 2 46 hours
        assertEquals(20250,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_2_PAY_RATE, 46),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_2_PAY_RATE, 46)");

        // Overtime Level 3 46 hours
        assertEquals(23172,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_3_PAY_RATE, 46),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_3_PAY_RATE, 46)");

        // Fractional hours less than 40
        // Overtime Level 1 36.5 hours
        assertEquals(0,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_1_PAY_RATE,
                        36.5),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_1_PAY_RATE, 36.5)");

        // Overtime Level 2 36.5 hours
        assertEquals(0,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_2_PAY_RATE,
                        36.5),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_2_PAY_RATE, 36.5)");

        // Overtime Level 3 36.5 hours
        assertEquals(0,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_3_PAY_RATE,
                        36.5),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_3_PAY_RATE, 36.5)");

        // Fractional hours over 40 hours
        // Overtime Level 1 46.5 hours
        assertEquals(18525,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_1_PAY_RATE,
                        46.5),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_1_PAY_RATE, 46.5)");

        // Overtime Level 2 46.5 hours
        assertEquals(21937,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_2_PAY_RATE,
                        46.5),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_2_PAY_RATE, 46.5)");

        // Overtime Level 3 46 hours
        assertEquals(

                25103,
                Paychecks.calculateOvertimePay(Paychecks.LEVEL_3_PAY_RATE,
                        46.5),
                "Paychecks.calculateOvertimePay(Paychecks.LEVEL_3_PAY_RATE, 46.5)");

        // Test a payrate other than a given constant Regular
        assertEquals(14964, Paychecks.calculateOvertimePay(1376, 47.25),
                "Paychecks.calculateOvertimePay(1376, 47.25)");

        // Test negative hours
        Exception exception = assertThrows(
                IllegalArgumentException.class, () -> Paychecks
                        .calculateOvertimePay(Paychecks.LEVEL_3_PAY_RATE, -1),
                "Testing negative hours worked");
        assertEquals("Negative pay rate and/or hours worked",
                exception.getMessage(),
                "Testing negative hours worked - exception message");

        // Test negative pay rate
        exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.calculateOvertimePay(-20, 20),
                "Testing negative pay rate");
        assertEquals("Negative pay rate and/or hours worked",
                exception.getMessage(),
                "Testing negative pay rate - exception message");
    }

    /**
     * Test the Paychecks.calculateGrossPay() method.
     */
    @Test
    public void testCalculateGrossPay() {

        // Less than 40 hours
        // Gross Level 1 36 hours
        assertEquals(68400, Paychecks.calculateGrossPay(68400, 0),
                "Paychecks.calculateGrossPay(68400, 0)");

        // Gross Level 2 36 hours
        assertEquals(81000, Paychecks.calculateGrossPay(81000, 0),
                "Paychecks.calculateGrossPay(81000, 0)");

        // Gross Level 3 36 hours
        assertEquals(92700, Paychecks.calculateGrossPay(92700, 0),
                "Paychecks.calculateGrossPay(92700, 0)");

        // Over 40 hours
        // Gross Level 1 46 hours
        assertEquals(93100, Paychecks.calculateGrossPay(76000, 17100),
                "Paychecks.calculateGrossPay(76000, 17100)");

        // Gross Level 2 46 hours
        assertEquals(110250, Paychecks.calculateGrossPay(90000, 20250),
                "Paychecks.calculateGrossPay(90000, 20250)");

        // Gross Level 3 46 hours
        assertEquals(126172, Paychecks.calculateGrossPay(103000, 23172),
                "Paychecks.calculateGrossPay(103000, 23172)");

        // Fractional hours less than 40
        // Gross Level 1 36.5 hours
        assertEquals(69350, Paychecks.calculateGrossPay(69350, 0),
                "Paychecks.calculateGrossPay(69350, 0)");

        // Overtime Level 2 36.5 hours
        assertEquals(82125, Paychecks.calculateGrossPay(82125, 0),
                "Paychecks.calculateGrossPay(82125, 0)");

        // Gross Level 3 36.5 hours
        assertEquals(93987, Paychecks.calculateGrossPay(93987, 0),
                "Paychecks.calculateGrossPay(93987, 0)");

        // Fractional hours over 40 hours
        // Gross Level 1 46.5 hours
        assertEquals(94525, Paychecks.calculateGrossPay(76000, 18525),
                "Paychecks.calculateGrossPay(76000, 18525)");

        // Gross Level 2 46.5 hours
        assertEquals(111937, Paychecks.calculateGrossPay(90000, 21937),
                "Paychecks.calculateGrossPay(90000, 21937)");

        // Gross Level 3 46 hours
        assertEquals(128103, Paychecks.calculateGrossPay(103000, 25103),
                "Paychecks.calculateGrossPay(103000, 25103)");

        // Test a payrate other than a given constant Gross
        assertEquals(49914, Paychecks.calculateGrossPay(34950, 14964),
                "Paychecks.calculateGrossPay(34950, 14964)");

        // Test negative regular
        Exception exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.calculateGrossPay(-103000, 25103),
                "Testing negative regular pay");
        assertEquals("Invalid regular and/or overtime pay.",
                exception.getMessage(),
                "Testing negative regular pay - exception message");

        // Test negative overtime
        exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.calculateGrossPay(103000, -25103),
                "Testing negative overtime pay");
        assertEquals("Invalid regular and/or overtime pay.",
                exception.getMessage(),
                "Testing negative overtime pay - exception message");
    }

    /**
     * Test the Paychecks.calculateTotalDeductions() method.
     */
    @Test
    public void testCalculateRetirement() {

        // Zero percent retirement
        assertEquals(0, Paychecks.calculateRetirement(92700, 0),
                "Paychecks.calculateRetirement(92700, 0)");

        // One percent retirement
        assertEquals(927, Paychecks.calculateRetirement(92700, 1),
                "Paychecks.calculateRetirement(92700, 1)");

        // Two percent retirement
        assertEquals(1854, Paychecks.calculateRetirement(92700, 2),
                "Paychecks.calculateRetirement(92700, 2)");

        // Three percent retirement
        assertEquals(2781, Paychecks.calculateRetirement(92700, 3),
                "Paychecks.calculateRetirement(92700, 3)");

        // Four percent retirement
        assertEquals(3708, Paychecks.calculateRetirement(92700, 4),
                "Paychecks.calculateRetirement(92700, 4)");

        // Five percent retirement
        assertEquals(4635, Paychecks.calculateRetirement(92700, 5),
                "Paychecks.calculateRetirement(92700, 5)");

        // Six percent retirement
        assertEquals(5562, Paychecks.calculateRetirement(92700, 6),
                "Paychecks.calculateRetirement(92700, 6)");

        // Test negative gross pay
        Exception exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.calculateRetirement(-92700, 6),
                "Testing negative gross pay");
        assertEquals("Invalid gross pay and/or retirement percentage.",
                exception.getMessage(),
                "Testing negative gross pay - exception message");

        // Test invalid retirement percentage
        exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.calculateRetirement(92700, 7),
                "Testing invalid retirement percentage");
        assertEquals("Invalid gross pay and/or retirement percentage.",
                exception.getMessage(),
                "Testing invalid retirement percentage - exception message");
        exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.calculateRetirement(92700, -1),
                "Testing invalid retirement percentage");
        assertEquals("Invalid gross pay and/or retirement percentage.",
                exception.getMessage(),
                "Testing invalid retirement percentage - exception message");
    }

    /**
     * Test the Paychecks.calculateNetPay() method.
     */
    @Test
    public void testCalculateNetPay() {

        // Calculate net pay with deductions
        assertEquals(86211, Paychecks.calculateNetPay(92700, 6489),
                "Paychecks.calculateNetPay(92700, 6489)");

        // Calculate net pay without deductions
        assertEquals(92700, Paychecks.calculateNetPay(92700, 0),
                "Paychecks.calculateNetPay(92700, 0)");

        // Calculate net pay - negative
        Exception exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.calculateNetPay(92700, 93800),
                "Testing negative net pay");
        assertEquals("Net pay is less than 0.", exception.getMessage(),
                "Testing negative net pay - exception message");
    }

    /**
     * Test the Paychecks.getFile() method.
     */
    @Test
    public void testGetFile() {
        assertEquals("test-files/file1.txt", Paychecks
                .getFile(new Scanner("test-files/file1.txt"), "Input file"),
                "Get input file");

    }

    /**
     * Test the Paychecks.calculateDeductions() method.
     */
    @Test
    public void testCalculateDeductions() {
        // Test no insurance
        assertEquals(0, Paychecks.calculateDeductions(false, false, false),
                "No insurance");
        // Test all insurances
        assertEquals(4505, Paychecks.calculateDeductions(true, true, true),
                "All insurance options");
        // Test one insurance
        assertEquals(1530, Paychecks.calculateDeductions(false, true, false),
                "One insurance option");
    }

    /**
     * Test the Paychecks.checkName() method.
     */
    @Test
    public void testCheckName() {
        assertFalse(Paychecks.checkName(""), "Name with length 0");
        assertTrue(Paychecks.checkName("Jessica"),
                "Name with length greater than 0");
    }

    /**
     * Test the Paychecks.checkLevel() method.
     */
    @Test
    public void testCheckLevel() {

        assertFalse(Paychecks.checkLevel(0), "Test with boundry - invalid");
        assertFalse(Paychecks.checkLevel(4), "Test with boundry - invalid");
        assertTrue(Paychecks.checkLevel(1), "Test 1");
        assertTrue(Paychecks.checkLevel(2), "Test 2");
        assertTrue(Paychecks.checkLevel(3), "Test 3");
    }

    /**
     * Test the Paychecks.checkHoursWorked() method.
     */
    @Test
    public void testCheckHoursWorked() {
        // Test boundary value
        assertFalse(Paychecks.checkHoursWorked(-.1),
                "Test with boundry - invalid - negative");
        assertFalse(Paychecks.checkHoursWorked(0),
                "Test with boundry - invalid");
        assertTrue(Paychecks.checkHoursWorked(.001),
                "Test with regular boundry");
        assertTrue(Paychecks.checkHoursWorked(1),
                "Test with regular boundary int");

        // Test mid regular value
        assertTrue(Paychecks.checkHoursWorked(20), "Test with regular ");

        // Test max regular value
        assertTrue(Paychecks.checkHoursWorked(39.9),
                "Test with regular boundry");
        assertTrue(Paychecks.checkHoursWorked(40), "Test with regular max");

        // Test overtime values
        assertTrue(Paychecks.checkHoursWorked(41),
                "Test with overtime boundry");
        assertTrue(Paychecks.checkHoursWorked(60), "Test with overtime");
    }

    /**
     * Test the Paychecks.checkRetirementPercentage() method.
     */
    @Test
    public void testCheckRetirementPercentage() {
        // Test 0%
        assertTrue(Paychecks.checkRetirementPercentage(0), "0% Retirement");

        // Test 1%
        assertTrue(Paychecks.checkRetirementPercentage(0), "1% Retirement");

        // Test 3%
        assertTrue(Paychecks.checkRetirementPercentage(3), "3% Retirement");

        // Test 5%
        assertTrue(Paychecks.checkRetirementPercentage(0), "5% Retirement");

        // Test 6%
        assertTrue(Paychecks.checkRetirementPercentage(6), "6% Retirement");

        // Test -1% - invalid lower boundary
        assertFalse(Paychecks.checkRetirementPercentage(-1), "-1% Retirement");

        // Test 7% - invalid upper boundary
        assertFalse(Paychecks.checkRetirementPercentage(7), "7% Retirement");
    }

    /**
     * Test the Paychecks.processPaycheck() method.
     */
    @Test
    public void testProcessPaycheck() {
        String id = "Level 1: ";
        String desc = "Paychecks.processPaycheck(\"Danny D David\", 1, 20, 1000, 0)";
        String exp = "Danny D David            20.00     19.00    380.00      0.00    380.00"
                + "     10.00    370.00\n";
        String act = Paychecks.processPaycheck("Danny D David", 1, 20, 1000, 0);
        assertEquals(exp, act, id + desc);

        Exception exception = assertThrows(
                IllegalArgumentException.class, () -> Paychecks
                        .processPaycheck("Danny D David", 1, 2, 10000, 0),
                "Testing negative net pay");
        assertEquals("Net pay is less than 0.", exception.getMessage(),
                "Testing negative net pay - exception message");
    }

    /**
     * Test the Paychecks.processLine() method.
     */
    @Test
    public void testProcessLine() {
        String id = "Level 1: ";
        String desc = "Paychecks.processLine(\"Danny D David\\t1\\t20\\ttrue\\ttrue\\tfalse\")";
        String exp = "Danny D David            20.00     19.00    380.00      0.00    "
                + "380.00     39.80    340.20\n";
        String act = Paychecks
                .processLine("Danny D David\t1\t20\ttrue\ttrue\tfalse");
        assertEquals(exp, act, id + desc);

        id = "Level 2: ";
        desc = "Paychecks.processLine(\"Frank Frankenstein\\t2\\t40\\ttrue\\tfalse\\tfalse\")";
        exp = "Frank Frankenstein       40.00     22.50    900.00      0.00    900.00     "
                + "24.50    875.50\n";
        act = Paychecks
                .processLine("Frank Frankenstein\t2\t40\ttrue\tfalse\tfalse");
        assertEquals(exp, act, id + desc);

        id = "Level 3: ";
        desc = "Paychecks.processLine(\"Hilda Henderson\\t3\\t50\\tfalse\\tfalse\\tfalse\\t3\")";
        exp = "Hilda Henderson          50.00     25.75   1030.00    386.20   1416.20     "
                + "42.48   1373.72\n";
        act = Paychecks
                .processLine("Hilda Henderson\t3\t50\tfalse\tfalse\tfalse\t3");
        assertEquals(exp, act, id + desc);

        id = "Level 3 - missing retirement";
        Exception exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks
                        .processLine("Alice Anderson\t3\t10\ttrue\ttrue\ttrue"),
                id);
        assertEquals("Invalid Format", exception.getMessage(),
                id + " - exception message");

        id = "Level 3 - double retirement";
        exception = assertThrows(IllegalArgumentException.class, () -> Paychecks
                .processLine("Alice Anderson\t3\t10\ttrue\ttrue\ttrue\t3.4"),
                id);
        assertEquals("Invalid Format or Input", exception.getMessage(),
                id + " - exception message");

        id = "Invalid name";
        exception = assertThrows(IllegalArgumentException.class,
                () -> Paychecks.processLine("\t3\t50\tfalse\tfalse\tfalse\t3"),
                id);
        assertEquals("Invalid Format or Input", exception.getMessage(),
                id + " - exception message");

        id = "Invalid level";
        exception = assertThrows(IllegalArgumentException.class, () -> Paychecks
                .processLine("Hilda Henderson\t4\t50\tfalse\tfalse\tfalse\t3"),
                id);
        assertEquals("Invalid Input", exception.getMessage(),
                id + " - exception message");

        id = "Invalid hours worked";
        exception = assertThrows(IllegalArgumentException.class, () -> Paychecks
                .processLine("Hilda Henderson\t3\t0\tfalse\tfalse\tfalse\t3"),
                id);
        assertEquals("Invalid Input", exception.getMessage(),
                id + " - exception message");

        id = "Invalid retirement";
        exception = assertThrows(IllegalArgumentException.class, () -> Paychecks
                .processLine("Hilda Henderson\t3\t50\tfalse\tfalse\tfalse\t7"),
                id);
        assertEquals("Invalid Input", exception.getMessage(),
                id + " - exception message");
    }

    /**
     * Test the Paychecks.processFile() method.
     *
     * @throws FileNotFoundException if file stream cannot be constructed
     */
    @Test
    public void testProcessFile() throws FileNotFoundException {

        // TEST 1
        String message = "Testing file of multiple invalid";
        String inputFile = "test-files/input_MULTIPLE_INVALID.txt";
        String expectedFile = "test-files/UNIT-output_exp_MULTIPLE_INVALID.txt";

        String outputFile = "test-files/UNIT-output_act_MULTIPLE_INVALID.txt";

        Scanner in = new Scanner(new FileInputStream(inputFile));
        PrintWriter out = new PrintWriter(new FileOutputStream(outputFile));
        Paychecks.processFile(in, out);
        in.close();
        out.close();

        Scanner actual = new Scanner(new FileInputStream(outputFile));
        Scanner expected = new Scanner(new FileInputStream(expectedFile));
        testFileContents(expected, actual, message);
        expected.close();
        actual.close();

        // TEST 2
        message = "Testing file of level1";
        inputFile = "test-files/input_level01.txt";
        expectedFile = "test-files/UNIT-output_exp_level01.txt";
        outputFile = "test-files/UNIT-output_act_level01.txt";

        in = new Scanner(new FileInputStream(inputFile));
        out = new PrintWriter(new FileOutputStream(outputFile));
        Paychecks.processFile(in, out);
        in.close();
        out.close();

        actual = new Scanner(new FileInputStream(outputFile));
        expected = new Scanner(new FileInputStream(expectedFile));
        testFileContents(expected, actual, message);
        expected.close();
        actual.close();

        // TEST 3
        message = "Testing file of level2";
        inputFile = "test-files/input_level02.txt";
        expectedFile = "test-files/UNIT-output_exp_level02.txt";
        outputFile = "test-files/UNIT-output_act_level02.txt";

        in = new Scanner(new FileInputStream(inputFile));
        out = new PrintWriter(new FileOutputStream(outputFile));
        Paychecks.processFile(in, out);
        in.close();
        out.close();

        actual = new Scanner(new FileInputStream(outputFile));
        expected = new Scanner(new FileInputStream(expectedFile));
        testFileContents(expected, actual, message);
        expected.close();
        actual.close();

        // TEST 4
        message = "Testing file of level3";
        inputFile = "test-files/input_level03.txt";
        expectedFile = "test-files/UNIT-output_exp_level03.txt";
        outputFile = "test-files/UNIT-output_act_level03.txt";

        in = new Scanner(new FileInputStream(inputFile));
        out = new PrintWriter(new FileOutputStream(outputFile));
        Paychecks.processFile(in, out);
        in.close();
        out.close();

        actual = new Scanner(new FileInputStream(outputFile));
        expected = new Scanner(new FileInputStream(expectedFile));
        testFileContents(expected, actual, message);
        expected.close();
        actual.close();

        // TEST 5
        message = "Testing file of single employee";
        inputFile = "test-files/input_single.txt";
        expectedFile = "test-files/UNIT-output_exp_single.txt";
        outputFile = "test-files/UNIT-output_act_single.txt";

        in = new Scanner(new FileInputStream(inputFile));
        out = new PrintWriter(new FileOutputStream(outputFile));
        Paychecks.processFile(in, out);
        in.close();
        out.close();

        actual = new Scanner(new FileInputStream(outputFile));
        expected = new Scanner(new FileInputStream(expectedFile));
        testFileContents(expected, actual, message);
        expected.close();
        actual.close();
    }

    /**
     * Testing contents of scanner
     *
     * @param expected expected scanner
     * @param actual actual scanner
     * @param message message for test
     */
    public void testFileContents(Scanner expected, Scanner actual,
            String message) {
        int line = 0;
        while (expected.hasNextLine()) {
            line++;
            if (actual.hasNextLine()) {
                assertEquals(expected.nextLine(), actual.nextLine(),
                        message + ": Testing line " + line);
            } else {
                fail(message + ": Too few lines: line " + line);
            }
        }
        if (actual.hasNextLine()) {
            fail(message + ": Too many lines");
        }
    }
}
