package caltool.model.schedule;

import caltool.model.caldb.*;
import mvp.*;

/****
 *
 * Class Schedule is the top-level model class in the
 * schedule package.  It provides methods to schedule the
 * four types of calendar item.  It also contains a
 * Categories data field, which is the sub-model for
 * editing scheduled item categories.
 *
 * @author Gene Fisher (gfisher@calpoly.edu)
 * @version 13feb13
 *
 */
public class Schedule extends Model {

    /**
     * Construct this with the given companion view and
     * the parent CalendarDB model.  The CalendarDB is
     * provided to access to its service methods that
     * store items in the current user calendar.  Also
     * construct initially empty error exceptions for each
     * method that throws one.
     */
    /*@
      ensures
        this.view == view &&
        this.calDB == calDB &&
        this.scheduleEventPrecondViolation != null &&
        !this.scheduleEventPrecondViolation.anyErrors();
           // 3 other checks omitted temporarily for simplicity
     @*/
    public Schedule(View view, CalendarDB calDB) {
        super(view);
        this.calDB = calDB;
        scheduleEventPrecondViolation = new ScheduleEventPrecondViolation();
    }

    /**
     * Return the categories component.
     */
    /*@
      requires (* no precond *) ;
      ensures \result == this.categories ;
     @*/    
    public Categories getCategories() {
        return categories;
    }

    /**
     * ScheduleEvent adds the given Event to the given
     * CalendarDB, if an event of the same start date and
     * title is not already scheduled.
     */
    /*@
     normal_behavior
      requires
        //
        // The Title field is not empty.
        //
        (event.title != null && event.title.length() >= 1)

            &&

        //
        // The startOrDueDate field is a valid date value.
        //
        (event.startOrDueDate != null) && event.startOrDueDate.isValid()

            &&

        //
        // If non-empty, the EndDate field is a valid date value.
        //
        (event.endDate != null) ==> event.endDate.isValid()

            &&

        //
        // The current workspace is not null.
        //
        (calDB.getCurrentCalendar() != null)

            &&

        //
        // No event of same title and start date is in the
        // current workspace calendar.  The
        // UserCalendar.getItem method does the work.
        //
        calDB.getCurrentCalendar().getItem(event.getKey()) == null;

      ensures
        //
        // If preconds met, a scheduled item is in the
        // output calendar if and only if it is the new
        // appt to be added or it is in the input
        // calendar.
        //
        (\forall ScheduledItem item;
            \old(calDB).getCurrentCalendar().getItem(
                   item.getKey()) != null <==>
                (item.equals(event) ||
                 \old(calDB).getCurrentCalendar().getItem(
                   item.getKey()) != null))

            &&

        //
        // Also, requiresSaving and hasChanged are both
        // true in the output calendar.
        //
        calDB.getCurrentCalendar().requiresSaving()
            &&
        calDB.getCurrentCalendar().hasChanged();

     //
     // Throw exceptions if preconds violated.
     //
     exceptional_behavior
        signals (ScheduleEventPrecondViolation e)
            validateInputs(event).anyErrors()
                ||
            alreadyScheduled(event)
                ||
            calDB.getCurrentCalendar() == null;

     @*/
    public void scheduleEvent(Event event)
            throws ScheduleEventPrecondViolation {

        /*
         * Clear out the error fields in precond violation
         * exception object.
         */
        scheduleEventPrecondViolation.clear();

        /*
         * Throw a precond violation if the validity check
         * fails on the title, start date, or end date.
         */
        if (validateInputs(event).anyErrors()) {
            throw scheduleEventPrecondViolation;
        }

        /*
         * Throw a precond violation if an event of the
         * same start date and title is already scheduled.
         */
        if (alreadyScheduled(event)) {
            scheduleEventPrecondViolation.setAlreadyScheduledError();
            throw scheduleEventPrecondViolation;
        }

        /*
         * Throw a precond violation if there is no
         * currently active calendar.  Note that this
         * condition will not be violated when interacting
         * through the view, since the 'Schedule Event'
         * menu item is disabled whenever the there is no
         * active calendar.
         */
        if (calDB.getCurrentCalendar() == null) {
            scheduleEventPrecondViolation.setNoActiveCalendarError();
            throw scheduleEventPrecondViolation;
        }

        /*
         * If preconditions are met, add the given event
         * to the currently active calendar.
         */
        calDB.getCurrentCalendar().add(event);

    }

    /**
     * ScheduleAppointment adds the given Appointment to
     * the current Calendar if an appointment of the same
     * time, duration, and title is not already scheduled.
     */
    /*@
     normal_behavior
      requires
        //
        // The Title field is not empty.
        //
        (appt.title != null && appt.title.length() >= 1)

            &&

        //
        // The startOrDueDate field is a valid date value.
        //
        ((appt.startOrDueDate != null) && appt.startOrDueDate.isValid())

            &&

        //
        // If non-empty, the EndDate field is a valid date value.
        //
        ((appt.endDate != null) || appt.endDate.isValid())

            &&

        //
        // The current workspace is not null.
        //
        (calDB.getCurrentCalendar() != null)

            &&

        //
        // No appt of same title and start date is in the current workspace
        // calendar.  The UserCalendar.getItem method does the work.
        //
        calDB.getCurrentCalendar().getItem(appt.getKey()) == null;

      ensures
        //
        // If preconds met, a scheduled item is in the output calendar if
        // and only if it is the new appt to be added or it is in the 
        // input calendar.
        //
        (\forall ScheduledItem item;
            \old(calDB).getCurrentCalendar().getItem(item.getKey()) != null <==>
                (item.equals(appt) ||
                 \old(calDB).getCurrentCalendar().getItem(item.getKey()) != null))

            &&

        //
        // Also, requiresSaving and hasChanged are both true in the output
        // calendar.
        //
        calDB.getCurrentCalendar().requiresSaving()
            &&
        calDB.getCurrentCalendar().hasChanged();

     //
     // Throw exceptions if preconds violated.
     //
     exceptional_behavior
        signals (ScheduleAppointmentPrecondViolation e)
            validateInputs(appt).anyErrors()
                ||
            alreadyScheduled(appt)
                ||
            calDB.getCurrentCalendar() == null;

     @*/
    public void scheduleAppointment(Appointment appt) {
        System.out.println("In Schedule.scheduleAppointment.");
    }

    /**
     * ScheduleMeeting adds a Meeting to the current calendar, based on the the
     * given MeetingRequest.  The work is done by the three suboperations,
     * which determine a list of possible meetings times, set
     * meeting-scheduling options, and confirm the scheduling of a specific
     * meeting selected from the possibles list.
     */
    public void scheduleMeeting(MeetingRequest meeting_req) {
        System.out.println("In Schedule.scheduleMeeting.");
    }

    /**
     * Produce the list of possible meeting times that satisfy the given
     * MeetingRequest.
     */
    public PossibleMeetingTimes listMeetingTimes(MeetingRequest request) {
        System.out.println("In schedule.listMeetingTimes.");
        return null;
    }

    /**
     * Set the meeting options in the CalendarDB to those
     * given.
     * 
     */
    public void setMeetingOptions(MeetingSchedulingOptions options) {
        System.out.println("In schedule.setMeetingOptions.");
    }

    /**
     * ConfirmMeeting takes a CalendarDB, MeetingRequest,
     * list of PossibleMeetingTimes, and a selected time
     * from the list.  It outputs a new CalendarDB with
     * the given request scheduled at the selected time.
     */
    public void confirmMeeting(MeetingRequest meeting_req,
            PossibleMeetingTimes possible_times, int selected_time) {
        System.out.println("In Schedule.confirmMeeting");
    }

    /**
     * ScheduleTask adds the given Task to the given
     * CalendarDB, if a task of the same start date,
     * title, and priority is not already scheduled.
     */
    public void scheduleTask(Task task) {
        System.out.println("In Schedule.scheduleTask.");
    }

    /**
     * Change the given old appointment to the given new
     * one in the current calendar.
     */
    public void changeAppointment(Appointment oldAppt, Appointment newAppt) {
        System.out.println("In Schedule.changeAppointment.");
    }

    /**
     * Delete the given appointment from the current
     * calendar.
     */
    public void deleteAppointment(Appointment appt) {
        System.out.println("In Schedule.deleteAppointment.");
    }


    /*-*
     * Access methods
     */

    /*-*
     * Set methods.
     */

    /**
     * Set the priority of the given task to the given
     * priority value.  The value must be between 0 and
     * 10, inclusive.  Throw an exception if it's not in
     * this range.
     */
    /*@
     normal_behavior
      requires
        priority >= 0 && priority <= 10;
      ensures
        task.priority == priority;

     exceptional_behavior
        signals (ScheduleAppointmentPrecondViolation e)
            validateInputs(task).anyErrors();
     @*/
    public void setTaskPriority(Task task, int priority) {
            throws ScheduleTaskPrecondViolation {
        /*
         * Throw a precond violation priority is out of range.
         */
        if ((priority < 0) || (priority > 10)) {
            scheduleTaskPrecondViolation.clear();
            scheduleTaskPrecondViolation.setPriorityOutOfRangeError();
            throw scheduleTaskPrecondViolation;
        }

        /*
         * Otherwise set the priority in the task.
         */
        task.priority = priority;
    }

    /**
     * Convert this to a printable string.  Note that the
     * categories field is only converted shallow since no
     * methods of this change the contents of categories.
     * The deep string conversion is of
     * calDB.getCurrentCalendar, since it's the object to
     * which the scheduling methods effect change.
     */
    public String toString() {
        return
            "Categories: " + categories + "\n" +
            "caldDB.currentCalendar:\n" +
            calDB.getCurrentCalendar().toString();
    }

    /*-*
     * Protected methods
     */

    /**
     * Return true if there is an already scheduled event
     * of the same title on any of the same dates as the
     * given event.
     */
    protected boolean alreadyScheduled(ScheduledItem item) {

        /*
         * Implementation forthcoming.
         */
        return false;

        /*
         * The following won't fully work, since we must check all dates.
         *
        return calDB.getCurrentCalendar().getItem(
            new ItemKey(e.startDate, null, null, e.title)) == null;
         *
         */

    }

    /**
     * Validate the <a href=
     * Schedule.html#scheduleEvent(Event)> ScheduleEvent
     * </a> precondition.  Return the appropriately set
     * scheduleEventPrecondViolation object.  See the
     * definition of <a href=
     * ScheduleEventPrecondViolation.html>
     * ScheduleEventPrecondViolation </a> for further
     * details.
     */
    protected ScheduleEventPrecondViolation validateInputs(Event event) {

        if ((event.title == null) || (event.title.length() == 0)) {
            scheduleEventPrecondViolation.setEmptyTitleError();
        }

        if (! event.startOrDueDate.isValid()) {
            scheduleEventPrecondViolation.setInvalidStartDateError();
        }

        if ((event.endDate != null) && (! event.endDate.isValid())) {
            scheduleEventPrecondViolation.setInvalidEndDateError();
        }

        return scheduleEventPrecondViolation;

    }

    /**
     * Validate the <a href= Schedule.html#scheduleAppointment(Appointment)> ScheduleAppointment
     * </a> precondition.  Return the appropriately set
     * scheduleAppointmentPrecondViolation object.  See the definition of <a href=
     * ScheduleAppointmentPrecondViolation.html> ScheduleAppointmentPrecondViolation </a>
     * for further details.
     */
    protected ScheduleAppointmentPrecondViolation validateInputs(
            Appointment appt) {

        if ((appt.title == null) || (appt.title.length() == 0)) {
            scheduleAppointmentPrecondViolation.setEmptyTitleError();
        }

        if (! appt.startOrDueDate.isValid()) {
            scheduleAppointmentPrecondViolation.setInvalidStartDateError();
        }

        if ((appt.endDate != null) && (! appt.endDate.isValid())) {
            scheduleAppointmentPrecondViolation.setInvalidEndDateError();
        }

        return scheduleAppointmentPrecondViolation;

    }


    /*-*
     * Derived data fields
     */

    /** Category list in which scheduled item categories are defined */
    protected Categories categories;


    /*-*
     * Process data fields
     */

    /** Calendar database that contains the current calendar in which scheduled
     * items are stored */
    protected CalendarDB calDB;

    /** Precond violation exception objects */
    protected ScheduleAppointmentPrecondViolation
        scheduleAppointmentPrecondViolation;
    protected ScheduleMeetingPrecondViolation scheduleMeetingPrecondViolation;
    protected ScheduleTaskPrecondViolation scheduleTaskPrecondViolation;    
    protected ScheduleEventPrecondViolation scheduleEventPrecondViolation;

}