/*
 *
 * This file defines the objects and operations related to maintaining the user
 * and group databases of the calendar system.  See Section 2.6 of the
 * Milestone 8 requirements.
 */
import java.util.Collection;
import java.util.List;

/**
 * UserDB is the repository of registered user information.
 */
abstract class UserDB {

    /**
     * The collection of user data records.
     */
    Collection<UserRecord> data;

    /**
     * Reference to GroupDB needed for change and delete methods.
     */
    GroupDB groupDB;

    /**
     * Add the given UserRecord to the given UserDB.  The Id of the given user
     * record must not be the same as a user record already in the UserDB.
     * The Id component is required and must be eight characters or less.  The
     * email address is required.  The phone number is optional; if given, the
     * area code and number must be 3 and 7 digits respectively.
     */
    /*@
     normal_behavior
      requires
        //
        // There is no user record in the input UserDB with the same id as the
        // record to be added.
        //
        (! (\exists UserRecord ur_other ;
                data.contains(ur_other) ;
                    ur_other.id.equals(ur.id)))

            &&

        //
        // The id of the given user record is not empty and 8 characters or
        // less.
        //
        (ur.id != null) && (ur.id.length() > 0) && (ur.id.length() <= 8)

            &&

        //
        // The email address is not empty.
        //
        (ur.email != null) && (ur.email.length() > 0)

            &&

        //
        // If the phone area code and number are present, they must be 3 digits
        // and 7 digits respectively.
        //
        ((ur.phone.area != 0) ==>
            Integer.toString(ur.phone.area).length() == 3) &&
        ((ur.phone.number != 0) ==>
            Integer.toString(ur.phone.number).length() == 7);

      ensures
        //
        // A user record is in the output data if and only if it is the new
        // record to be added or it is in the input data.
        //
        (\forall UserRecord ur_other ;
            (data.contains(ur_other)) <==>
                ur_other.equals(ur) || \old(data).contains(ur_other));
      
     exceptional_behavior
       signals (SE1 e) ur.id == null;
       signals (SE1 e) ur.email == null;
    @*/
    abstract void add(UserRecord ur);

    public class SE1 extends Exception {}
    public class SE2 extends Exception {}
    /**
     * Find a user by unique id.
     */
    /*@
      ensures
        //
        // If there is a record with the given id in the input data, then the
        // output record is equal to that record, otherwise the output record
        // is null.
        //
        (\exists UserRecord ur_found ; data.contains(ur_found) ;
                ur_found.id.equals(id) && ur_found.equals(\result))
            ||
        !(\exists UserRecord ur_found ; data.contains(ur_found) ;
                ur_found.id.equals(id)) && \result == null;

     @*/
    abstract UserRecord findById(String id);

    /**
     * Find a user or users by real-world name.  If more than one is found,
     * the output list is sorted by id.
     */
    /*@
      ensures
        //
        // The output list consists of all records of the given name in the
        // input data.
        //
        (\forall UserRecord ur ;
            \result.contains(ur) ;
                data.contains(ur) && ur.name.equals(name))

          &&

        //
        // The output list is sorted lexicographically by id, according to the
        // semantics of java.lang.String.compareTo().
        //
        (\forall int i ; (i >= 0) && (i < \result.size() - 1) ;
            \result.get(i).id.compareTo(\result.get(i+1).id) < 0);
      @*/
    abstract List<UserRecord> findByName(String name);

    /**
     * Change the given old UserRecord to the given new record.  The old and
     * new records must not be the same.  The old record must already be in
     * the input db.  The new record must meet the same conditions as for the
     * input to the AddUser operation.  Typically the user runs the FindUser
     * operation prior to Change to locate an existing record to be changed.
     * 
     * If the user record id is changed, then change all occurrences of the
     * old id in the group db to the new id.
     */
    /*@
      requires
        //
        // The old and new user records are not the same.
        //
        !old_ur.equals(new_ur)

            &&

        //
        // The old record is in this.data.
        //
        data.contains(old_ur)

            &&

        //
        // There is no user record in the input UserDB with the same id as the
        // new record to be added.
        //
        (! (\exists UserRecord ur_other ;
                data.contains(ur_other) ;
                    ur_other.id.equals(new_ur.id)))

            &&

        //
        // The id of the new record is not empty and 8 characters or less.
        //
        (new_ur.id != null) && (new_ur.id.length() > 0) &&
                (new_ur.id.length() <= 8)

            &&

        //
        // The email address is not empty.
        //
        (new_ur.email != null) && (new_ur.email.length() > 0)

            &&

        //
        // If the phone area code and number are present, they must be 3 digits
        // and 7 digits respectively.
        //
        ((new_ur.phone.area != 0) ==>
            Integer.toString(new_ur.phone.area).length() == 3) &&
        ((new_ur.phone.number != 0) ==>
            Integer.toString(new_ur.phone.number).length() == 7);

      ensures
        //
        // A user record is in the output data if and only if it is the new
        // record to be added or it is in the input data, and it is not the old
        // record.
        //
        (\forall UserRecord ur_other ;
            data.contains(ur_other) <==>
                ur_other.equals(new_ur) ||
                    (\old(data).contains(ur_other) &&
                        !ur_other.equals(old_ur)))
            &&

        //
        // If new id is different than old id, then all occurrences of old id
        // in the GroupDB are replaced by new id.
        //
        !old_ur.id.equals(new_ur.id) ==> true
            // Logic left as exercise for the reader
                ;
      @*/
    abstract void change(
        UserRecord old_ur, UserRecord new_ur);

    /**
     * Delete the given user record from the given UserDB.  The given record
     * must already be in the input db.  Typically the user runs the FindUser
     * operation prior to Delete to locate an existing record to delete.
     * 
     * In addition, delete the user from all groups of which the user is a
     * member.  If the deleted user is the only leader of a one more groups,
     * output a warning indicating that those groups have become leaderless.
     */
     /*@
      requires
        //
        // The given user record is in this.data.
        //
        data.contains(ur);

      ensures
        //
        // A user record is in the output data if and only if it is not the
        // existing record to be deleted and it is in the input data.
        //
        (\forall UserRecord ur_other ;
            data.contains(ur_other) <==>
                !ur_other.equals(ur) && \old(data).contains(ur_other))

            &&

        //
        // The id of the deleted user is not in the leader or member lists of
        // any group in the output GroupDB.  (NOTE: This clause is not as
        // strong as a complete "no junk, no confusion" spec.  Why not?  Should
        // it be?)
        //
        (\forall GroupRecord gr ; groupDB.data.contains(gr) ;
            !gr.leaders.contains(ur.id) && !gr.members.contains(ur.id))

            &&

        //
        // The LeaderlessGroupsWarning list contains the ids of all groups
        // whose only leader was the user who has just been deleted.
        //
        (\forall GroupRecord gr ; groupDB.data.contains(gr) ;
            \forall String id ;
                (\result.groupNames.contains(id) <==>
                    gr.leaders.size() == 1) &&
                        (gr.leaders.get(0).equals(ur.id)));
      @*/
    abstract LeaderlessGroupsWarning delete(UserRecord ur);

}


/**
 * A UserRecord is the information stored about a registered user of the
 * Calendar Tool.  The Name component is the user`s real-world name.  The
 * Id is the unique identifier by which the user is known to the Calendar
 * Tool.  The EmailAddress is the electronic mail address used by the
 * Calendar Tool to contact the user when necessary.  The PhoneNumber is
 * for information purposes; it is not used by the Calendar Tool for
 * contacting the user.
 */
abstract class UserRecord {
    String name;
    String id;
    String email;
    PhoneNumber phone;
}

abstract class PhoneNumber {
    int area;
    int number;
}

/**
  * LeaderlessGroupsWarning is an secondary output of the UserDB.change and
  * UserDB.delete operations, indicating the names of zero or more groups that
  * have become leaderless as the result of a user having been deleted.
  */
abstract class LeaderlessGroupsWarning {
    Collection<String> groupNames;
}

/**
 * GroupDB is the repository of user group information.
 */
abstract class GroupDB {

    /**
     * The collection of group data records.
     */
    Collection<GroupRecord> data;

    /**
     * Reference to GroupDB needed for change and delete methods.
     */
    UserDB userDB;

    /**
      * Add the given GroupRecord to the given GroupDB.  The name of the given
      * group must not be the same as a group already in the GroupDB.  All
      * group members must be registered users.  The leader(s) of the group
      * must be members of it.
      */
     /*@
      requires
        //
        // All group members are registered users.
        //
        (\forall String id ; gr.members.contains(id) ;
                (\exists UserRecord ur ; userDB.data.contains(ur) ;
                    ur.id.equals(id)))

            &&

        //
        // All group leaders are members of the group.
        //
        (\forall String id ; gr.leaders.contains(id) ;
            gr.members.contains(id));

      ensures
        //
        // A group record is in the output db if and only if it is the new
        // record to be added or it is in the input db.
        //
        (\forall GroupRecord gr_other ;
            data.contains(gr_other) <==>
                gr_other.equals(gr) || \old(data).contains(gr_other));
      @*/
    abstract void add(GroupRecord gr);

    /**
     * Delete the given group record from the given GroupDB.  The given record
     * must already be in the input db.  Typically the user runs the FindGroup
     * operation prior to Delete to locate an existing record to delete.
     */
     /*@
      requires
        //
        // The given GroupRecord is in the given GroupDB.
        //
        data.contains(gr);

      ensures
        //
        // A group record is in the output db if and only if it is not the
        // existing record to be deleted and it is in the input db.
        //
        (\forall GroupRecord gr_other ;
            data.contains(gr_other) <==>
                !gr_other.equals(gr) && \old(data).contains(gr_other));
      @*/
    abstract void delete(GroupRecord gr);

    /**
     * Change the given old GroupRecord to the given new record.  The old and
     * new records must not be the same.  The old record must already be in
     * the input db.  The new record must meet the same conditions as for the
     * input to the AddGroup operation.  Typically the user runs the FindGroup
     * operation prior to Change to locate an existing record to be changed.
     */
     /*@
      requires
        //
        // The old and new group records are not the same.
        //
        !old_gr.equals(new_gr)

            &&

        //
        // All group members are registered users.
        //
        (\forall String id ; new_gr.members.contains(id) ;
                (\exists UserRecord ur ; userDB.data.contains(ur) &&
                     ur.id.equals(id)))

            &&

        //
        // All group leaders are members of the group.
        //
        (\forall String id ; new_gr.leaders.contains(id) ;
            new_gr.members.contains(id));

      ensures
        //
        // A group record is in the output db if and only if it is the new
        // record to be added or it is in the input db, and it is not the old
        // record.
        //
        (\forall GroupRecord gr_other ;
            data.contains(gr_other) <==>
                gr_other.equals(new_gr) ||
                    \old(data).contains(gr_other) &&
                        !gr_other.equals(old_gr));
      @*/
    abstract void change(GroupRecord old_gr, GroupRecord new_gr);

    /**
     * Find a group by unique name.
     */
     /*@
      ensures
        //
        // If there is a record with the given name in the input db, then the
        // output record is equal to that record, otherwise the output record
        // is empty.
        //
        (\exists GroupRecord gr_found ; data.contains(gr_found) ;
                gr_found.name.equals(id) && gr_found.equals(\result))
            ||
        !(\exists GroupRecord gr_found ; data.contains(gr_found) ;
                gr_found.name.equals(id)) && \result == null;
      @*/
    abstract GroupRecord findById(String id);

}

/**
  * A GroupRecord is the information stored about a user group.  The Name
  * component is a unique group name of any length.  Leaders is a list of
  * zero or more users designated as group leader.  Members is the list of
  * group members, including the leaders.  Both lists consist of user id's.
  * Normally a group is required to have at least one leader.  The only
  * case that a group becomes leaderless is when its leader is deleted as a
  * registered user.
  */
abstract class GroupRecord {
    String name;
    List<String> leaders;
    List<String> members;
}