package caltool.model.schedule;

import caltool.model.caldb.*;
import mvp.*;
import java.util.*;
import org.testng.*;
import org.testng.annotations.*;


/****
 *
 * Class ScheduleTest is the companion testing class for class <a href=
 * Schedule.html> Schedule </a>.  It implements the following module test plan:
 *                                                                         <ul>
 *                                                                      <p><li>
 *     Phase 1: Unit test the constructor.
 *                                                                      <p><li>
 *     Phase 2: Unit test the simple access method getCategories.
 *                                                                      <p><li>
 *     Phase 3: Unit test the constructive methods scheduleAppointment,
 *              scheduleTask, and scheduleEvent.
 *                                                                      <p><li>
 *     Phase 4: Unit test the constructive methods scheduleMeeting and
 *              confirmMeeting.
 *                                                                      <p><li>
 *     Phase 5: Unit test the changeItem and deleteItem methods.
 *                                                                      <p><li>
 *     Phase 6: Repeat phases 1 through 5.
 *                                                                      <p><li>
 *     Phase 7: Stress test by scheduling and deleting 100000 items.
 *                                                                        </ul>
 */


@Test
public class ScheduleTest extends Schedule {

    /**
     * Empty constructor, needed to placate the compiler, since parent Schedule
     * constructor takes two arguments.
     */
    protected ScheduleTest() {
        super(null, null);
    }

    /*-*
     * Individual unit testing methods for member methods
     */

    /**
     * Unit test the constructor by building one Schedule object.  No further
     * constructor testing is necessary since only one Schedule object is ever
     * constructed in the CalendarTool system.  I.e., it is a singleton class.
     *                                                                    <pre>
     *  Test
     *  Case    Input            Output             Remarks
     * ====================================================================
     *   1      null             Proper init done   Only case
     *
     */
    @Test
    protected void testSchedule() {
        schedule = new Schedule(null, new CalendarDB());  // setup & invoke
        Assert.assertTrue(                                // validate
            validateSchedulePostcond(schedule));
    }

    /**
     * Unit test getCategories by calling getCategories on a schedule with a
     * null and non-null categories field.  The Categories model is tested
     * fully in its own class test.
     *                                                                    <pre>
     *  Test
     *  Case    Input                   Output          Remarks
     * ====================================================================
     *   1      schedule.categories     null            Null case
     *            == null
     *
     *   2      schedule.categories     same non-null   Non-null case
     *            =- non-null           value
     *                                                                   </pre>
     */
    @Test(dependsOnMethods = {"testSchedule"})
    protected void testGetCategories() {
        Categories result;                              // method return value 

        /*
         * Do case 1 and validate the result.
         */
        schedule.categories = null;                     // setup

        result = schedule.getCategories();              // invoke

        Assert.assertTrue(                              // validate
            validateGetCategoriesPostcond(result));


        /*
         * Do case 2 and validate the result.
         */
        schedule.categories = new Categories();         // setup

        result = schedule.getCategories();              // invoke

        Assert.assertTrue(                              // validate
            validateGetCategoriesPostcond(result));
    }

    /**
     * Unit test setTaskPriority by sending inputs in and out of range.
     *
     *  Test
     *  Case    Input                Output                 Remarks
     * ====================================================================
     *   1      task, priority = 0     task.priority = 0    Bottom of range
     *   2      task, priority = 5     task.priority = 5    Middle of range
     *   3      task, priority = 5     task.priority = 10   Top of range
     *   4      task, priority = -1    exception thrown     Just below bottom
     *   5      task, priority = -100  exception thrown     Well below bottom
     *   6      task, priority = 11    exception thrown     Just above top
     *   7      task, priority = 100   exception thrown     Well above top
     */
    @Test(dependsOnMethods = {"testSchedule"})
    void testSetTaskPriority ()
            throws ScheduleTaskPrecondViolation {
        Task task = new Task();

        /*
         * Check the three presumed successful test cases.
         */
        schedule.setTaskPriority(task, 0);
        Assert.assertTrue(validateSetTaskPriorityPostcond(task, 0));

        schedule.setTaskPriority(task, 5);
        Assert.assertTrue(validateSetTaskPriorityPostcond(task, 5));

        schedule.setTaskPriority(task, 10);
        Assert.assertTrue(validateSetTaskPriorityPostcond(task, 10));

        /*
         * Check the five presumed exceptional test cases.
         */
        try {
            schedule.setTaskPriority(task, -1);
            Assert.assertTrue(false);
        }
        catch (ScheduleTaskPrecondViolation e) {
            Assert.assertTrue(true);
        }

        try {
            schedule.setTaskPriority(task, -100);
            Assert.assertTrue(false);
        }
        catch (ScheduleTaskPrecondViolation e) {
            Assert.assertTrue(true);
        }

        try {
            schedule.setTaskPriority(task, 11);
            Assert.assertTrue(false);
        }
        catch (ScheduleTaskPrecondViolation e) {
            Assert.assertTrue(true);
        }

        try {
            schedule.setTaskPriority(task, 100);
            Assert.assertTrue(false);
        }
        catch (ScheduleTaskPrecondViolation e) {
            Assert.assertTrue(true);
        }
    }

    /**
     * Unit test scheduleEvent by supplying inputs that test the value range of
     * each Event data field and exercise each precondition clause.  The cases
     * are as follows:
     *                                                                    <pre>
     *  Test
     *  Case    Input                   Output          Remarks
     * ====================================================================
     *   1      {"Event 0",             Input event     Lower end of possible
     *           {"Sunday",1,           added to items  event data range
     *            "January",1},         no junk, no
     *            null,                 confusion
     *            null,
     *            null}
     *
     *   2      {10000-char string,     Input event     Upper end of possible
     *           {"Sunday",1,           added to items  event data range
     *            "January",1},         no junk,
     *           {"Saturday",31,        no confusion
     *            "December", 9999},
     *           10000-char string
     *             reversed,
     *           10000-char string}
     *
     *   3      events with start and   Input event     Events with all combos
     *  thru    end date ranging thru   added to items  of days, dates, and
     *  2564    each legal day, each    no junk, no     months
     *          legal date, and each    confusion
     *          legal month, with
     *          rotating year,
     *          category, and security,
     *          where "rotating" means
     *          that test values for
     *          years are generated in
     *          multiples of 100,
     *          numeric-valued category
     *          strings are generated
     *          in multiples of 10,
     *          and the two possible 
     *          values of security are
     *          toggled for each case
     *
     *  2565    32 cases to exercise    Schedule        Events that violate
     *  thru    possible boolean        Event           precond logic.
     *  2597    values for 5 precond    Precond
     *          clauses                 Exception
     *                                   thrown
     *                                                                   </pre>
     */
    @Test(dependsOnMethods = {"testSchedule"}) 
    protected void testScheduleEvent() {

        /*
         * Do the cases.
         */
        try {
            testScheduleEventCase1();
            testScheduleEventCase2();
            testScheduleEventCases3Thru2564();
            testScheduleEventCases2565Thru2597();
        }
        catch (ScheduleEventPrecondViolation e) {
           /* This should not happen at this level, but in case it does ... */
            System.out.println("scheduleEvent EXCEPTION:");
            for (int i = 0; i < e.numberOfErrors(); i++) {
                System.out.println(e.getErrors()[i]);
            }
        }
    }

    /**
     * Run testScheduleEvent case 1.
     */
    protected void testScheduleEventCase1()
            throws ScheduleEventPrecondViolation {

        Event e;

        /*
         * Get a shallow clone of the pre-scheduling items.
         */
        Collection<ScheduledItem> preItems =
            schedule.calDB.getCurrentCalendar().getItemsClone();

        /*
         * Call the method under test.
         */
        schedule.scheduleEvent(e = new Event(
            "Event 0",                          // title
            new Date(DayName.Sunday,            // start date
                1,
                MonthName.January,
                1),
            null,                               // end date
            new Category("Category 1"),         // category
            SimpleSecurity.Public               // security
        ));

        /*
         * Do the validation.
         */
        Assert.assertTrue(validateScheduleEventPostcond(preItems, e));
    }

    /**
     * Run testScheduleEvent case 2.
     */
    protected void testScheduleEventCase2()
            throws ScheduleEventPrecondViolation {

        int i;
        String longString = "";
        String longStringReversed = "";
        Event e;

        Collection<ScheduledItem> preItems =
            schedule.calDB.getCurrentCalendar().getItemsClone();

        for (i = 0; i < 10000; i++) {
            longString += (char) i % 256;
            longStringReversed = (char) i + longStringReversed;
        }

        schedule.scheduleEvent(e = new Event(
            longString,                         // title
            new Date(DayName.Sunday,            // start date
                1,
                MonthName.January,
                1),
            new Date(DayName.Saturday,          // end date
                2,
                MonthName.December,
                9999),
            new Category(longStringReversed),   // category
            SimpleSecurity.Private              // security
        ));

        Assert.assertTrue(validateScheduleEventPostcond(preItems, e));
    }

    /**
     * Run testScheduleEvent cases 3 through 2564 by looping through various
     * combinations of days, weeks, and months.
     */
    protected void testScheduleEventCases3Thru2564()
            throws ScheduleEventPrecondViolation {

        int day, number, month, year, i, n;
        Date date;
        Event e;

        for (day = 0; day < 7; day++) {
            for (number = 1; number <= 31; number++) {
                for (month = 0; month < 12; month++) {

                    date = new Date(
                        DayName.values()[day],
                        number,
                        MonthName.values()[month],
                        year = number * 300);

                    if (date.isValid()) {

			Collection<ScheduledItem> preItems =
			    schedule.calDB.getCurrentCalendar().getItemsClone();

                        schedule.scheduleEvent(e = new Event(
                            "Event " +                              // title
                                String.valueOf(n = year + month + day),
                            date,                                   // start
                            date,                                   // end
                            new Category(String.valueOf(n*10)),     // category
                            ((number % 2) == 0) ?                   // security
                                SimpleSecurity.Public :
                                SimpleSecurity.Private
                        ));

                        Assert.assertTrue(
                            validateScheduleEventPostcond(preItems, e));
                    }
                }
            System.out.print(".");     // super crude progress "bar"
            }
        }
    }

    /**
     * Run testScheduleEvent cases 2565 through 2597 to fully exercise the
     * precond logic.
     */    
    protected void testScheduleEventCases2565Thru2597() {
        /* Coming next milestone */
    }

    /*-*
     * Postcondition validation methods.
     */

    /**
     * Evaluate the Schedule constructor postcond on the given calDB and
     * cheduleEventPrecondViolation inputs, with this.schedule as the actual
     * output.
     */
    boolean validateSchedulePostcond(Schedule schedule) {
      return
        schedule.getView() == view &&
        schedule.calDB != null &&
        schedule.scheduleEventPrecondViolation != null &&
        !schedule.scheduleEventPrecondViolation.anyErrors();
           // 3 other precond checks omitted temporarily for simplicity
    }

    /**
     * Evaluate the getCategories postcond with the given returnVal as the
     * actual output.
     */
    boolean validateGetCategoriesPostcond(Categories returnVal) {
        return schedule.categories == returnVal;
    }


    /**
     * Evaluate the setTaskPriority postcond on the given task and priority
     * value.
     */
    boolean validateSetTaskPriorityPostcond(Task task, int priority) {
        return task.priority == priority;
    }

    /**
     * Evaluate the scheduleEvet postcond on this.schedule.  For quick
     * reference, here's the postcond:
     *
     *  (\forall ScheduledItem item;
     *      calDB.getCurrentCalendar().getItems().contains(item);
     *          item.equals(event) ||
     *             \old(calDB).getCurrentCalendar().getItems().contains(item))
     *
     *       &&
     *
     *   (calDB.getCurrentCalendar().getItems().size() ==
     *      \old(calDB).getCurrentCalendar().getItems().size() + 1)
     *
     *       &&
     *
     *   calDB.getCurrentCalendar().requiresSaving()
     *
     *       &&
     *
     *  calDB.getCurrentCalendar().hasChanged();
     */
    boolean validateScheduleEventPostcond(
            Collection<ScheduledItem> preItems, Event e) {

        /*
         * Evaluate the JML \forall with a loop over the Collection returned
         * UserCalendar.getItems().  Note that this is a "short-circuit"
         * evaluation loop, where we return false at the first occurrance of a
         * postcond violation.  Note also that we are using the value of
         * preItems as the value represented absractly as \old(...) in the JML
         * logic.
         */
        for (ScheduledItem item :
                schedule.calDB.getCurrentCalendar().getItems()) {
            if (! (item.equals(e) || preItems.contains(item))) {
                return false;
            }
        }

        /*
         * The remaining clauses of the postcond are non-looping bool exprs
         * that can be evaluated in the normal way.
         */
        return
            (schedule.calDB.getCurrentCalendar().getItems().size() ==
               preItems.size() + 1)
         
                &&
         
            schedule.calDB.getCurrentCalendar().requiresSaving()
         
                &&
         
           schedule.calDB.getCurrentCalendar().hasChanged();
    }


    /*-*
     * Testing utility methods.
     */

    /**
     * Output a header message to stdout identifying the test phase.
     */
    protected void dumpPhaseHeader(int phasenum) {
        System.out.print("**** Schedule Testing Phase ");
        System.out.print(phasenum);
        System.out.println(" ****");
    }

    /**
     * Output a header message to stdout for the unit test of the given
     * testname.
     */
    protected void dumpUnitTestHeader(String testname) {
        System.out.print("** Unit Test " + testname + " **\n");
    }

    /**
     * Output a header message to stdout for a unit test case.
     */
    protected void dumpUnitTestCaseHeader(String testname, int caseNumber) {
        System.out.print("* Unit Test Case " +
            testname + String.valueOf(caseNumber)  + " **\n");
    }


    /**
     * Dump three blank lines following a test phase.
     */
    protected void dumpPhaseEndSpacing() {
        System.out.print("\n\n\n");
    }

    /**
     * Dump a couple blank lines following a unit test.
     */
    protected void dumpUnitTestEndSpacing() {
        System.out.print("\n\n");
    }

    /**
     * Dump just a string message.
     */
     protected void dumpMessage(String message) {
        System.out.println(message);
    }

    /**
     * Dump the data values of schedule to stdout.  For test validation
     * purposes, include a print of the number of elements in the dumped items.
     * Precede the dump with the given message, if the message is non-null.
     */
    protected void dump(String message) {
        int l;                  // Temp

        if (message != null) {
            System.out.print("* " + message + " *" + "\n");
        }

        System.out.print("Schedule contains\n" +
            "Categories: " + schedule.categories + "\n" +
            (l = schedule.calDB.getCurrentCalendar().numItems()) + " " +
            (l == 1 ? " item" : " items") + "\n" +
            schedule.toString() + "\n"
        );
    }

    /**
     * Schedule data object to support the tests.
     */
    protected Schedule schedule;
    /**
     * Unit test scheduleAppointment using test cases 1-2564 of
     * testScheduleEvent, with the addition of rotating values for the
     * components of an Appointment that aren't in an Event.  These rotating
     * vales are: toggling value for recurring, toggling value for Priority,
     * toggling value for remind, string value of details that starts with
     * empty and increments by string length of 1, with the addition of of
     * numerically increasing ASCII character values.
     *
     *  2565    32 cases to exercise    Schedule        Appointments that
     *  thru    possible boolean        Apppointment    violate precond logic.
     *  2629    values for 6 precond    Precond
     *          clauses                 Exception
     *                                   thrown
     * 
     */
    protected void testScheduleAppointment() {
    }

    /**
     * Unit test scheduleTask using test cases 1-2564 of
     * testScheduleAppointment, disregarding rotating values for the components
     * of an Appointment that aren't in a Task, and adding rotating values for
     * the Due Time and Priority components of a Task that are not in an
     * Appointment.  Values for Due Time rotate from 00:00 through 23:50 in
     * increments of 10 minutes (i.e., modulo 24:00).  Values for Priority
     * rotate from 0 through 10 (i.e., modulo 11).
     */
    protected void testScheduleTask() {
    }

    private void p(String s) { System.out.println(s); }

}