package com.example.model;

import java.util.*;
import java.io.*;
import java.lang.*;

/** A Schedule object takes a set of restraint times along the busy times for a 
    group and returns the free times common to all members of the group that fall
    within the restraints.  The object contains a list of TimeInterval objects for
    each day Monday-Friday which store the free times for each day.
  @author Anthony M. Furst
  @version Schedule1.1
  @since Schedule1.1
*/
public class Schedule
{

    public boolean isConsistent() {
	return !(MList.equals(TList));
    }

    public Schedule() {
	minFreeTimeSpan = 30;
	MList = new ArrayList<TimeInterval>();
	TList = new ArrayList<TimeInterval>();
	WList = new ArrayList<TimeInterval>();
	RList = new ArrayList<TimeInterval>();
	FList = new ArrayList<TimeInterval>();	
    }

    /** Parses a string of the form "MW 9:00 AM-11:15 AM, T 12:25 PM - 4:00 PM", 
	containing the free time constraints for the schedule object.  
	Seperates the intervals of times, creates a TimeInterval object for each one, 
	and adds those objects to the appropriate lists.  
	@param times A string containing a list of free times and during which days 
		     those times occur.
	@throws FreeTimesAlreadySetException
    */
    public void setFreeTimeConstraints(String times) 
				throws FreeTimesAlreadySetException {
    	String days;
	int startTime;
	int endTime;
	TimeInterval interval;
	TimeInterval t;
  	int sIndex;
	int eIndex;

	//Repeat while there are still times to parse
	do {
	    sIndex = 0;
	    eIndex = 0;

	    //Extract the days during which each interval of time occurs.
	    eIndex = times.indexOf(' ');
	    days = times.substring(sIndex, eIndex);
	    times = times.substring(eIndex + 1);	

	    //Extract the start time for a given interval
	    eIndex = times.indexOf('-');
	    startTime = getTime(times.substring(sIndex, eIndex));
	    times = times.substring(eIndex + 1);

 	    //Extract the end time for a given interval
	    eIndex = times.indexOf(',');
	    if (eIndex >= 0) {
	    	endTime = getTime(times.substring(sIndex, eIndex));
	        times = times.substring(eIndex + 2);
  	    }
	    else {
	   	endTime = getTime(times.substring(sIndex));
	    }

	    for (int i = 0; i < days.length(); i++) {
		//Create a new TimeInterval with the correct start and end times
		interval = new TimeInterval(startTime, endTime);

		switch (days.charAt(i)) {
		    case 'M':
			addFreeTimesToList(interval, MList);
			break;
		    case 'T':
			addFreeTimesToList(interval, TList);
			break;
		    case 'W':
			addFreeTimesToList(interval, WList);
			break;
		    case 'R':
			addFreeTimesToList(interval, RList);
			break;
		    case 'F':
			addFreeTimesToList(interval, FList);
			break;
		    default:
			break;
  		}
	    }	  
   	} while (eIndex >= 0);
    }

    /** Parses a string of the form "MW 9:00 AM-11:15 AM, T 12:25 PM - 4:00 PM", 
	containing a set of busy times for the schedule object.  Seperates the 
	intervals of times, creates a TimeInterval object for each one, and adds 
	those objects to the appropriate lists.  
	@param times A string containing a list of busy times and during which days 
		     those times occur.
	@throws FreeTimesNotSetException
    */
    public void setBusyTimes(String times)
				throws FreeTimesNotSetException {
    	String days;
	int startTime;
	int endTime;
	TimeInterval interval;
	TimeInterval t;
  	int sIndex = 0;
	int eIndex = 0;

	//Asserts that times is not NULL before it gets parsed
	if (times.indexOf('\0') > 0) 
	    eIndex = 1;
 	
	//Repeat while there are still times to parse
	while (eIndex >= 0) {
	    sIndex = 0;
	    eIndex = 0;

	    //Extract the days during which each interval of time occurs.
	    eIndex = times.indexOf(' ');
	    days = times.substring(sIndex, eIndex);
	    times = times.substring(eIndex + 1);	

	    //Extract the start time for a given interval
	    eIndex = times.indexOf('-');
	    startTime = getTime(times.substring(sIndex, eIndex));
	    times = times.substring(eIndex + 1);

 	    //Extract the end time for a given interval
	    eIndex = times.indexOf(',');
	    if (eIndex >= 0) {
	    	endTime = getTime(times.substring(sIndex, eIndex));
	        times = times.substring(eIndex + 2);
  	    }
	    else {
	   	endTime = getTime(times.substring(sIndex));
	    }

	    //Add the interval to the list for every day on which that interval occurs
	    for (int i = 0; i < days.length(); i++) {
		//Create a new TimeInterval with the correct start and end times
		interval = new TimeInterval(startTime, endTime);

		switch (days.charAt(i)) {
		    case 'M':
			addBusyTimesToList(interval, MList);
			break;
		    case 'T':
			addBusyTimesToList(interval, TList);
			break;
		    case 'W':
			addBusyTimesToList(interval, WList);
			break;
		    case 'R':
			addBusyTimesToList(interval, RList);
			break;
		    case 'F':
			addBusyTimesToList(interval, FList);
			break;
		    default:
			break;
  		}
	    }	  
	}	
    }

    /** Takes a time that was parsed from the original string, and returns that 
	time as an integer in universal time, to be used in a TimeInterval object.  
	The string "2:30 PM" that was parsed from the original string would be 
	returned as 1430.
	@param time A String representing a particular time.
	@return An integer representation of that time in universal format.
    */
    public int getTime(String time) {
	int hour;
	int minutes;
	int combinedTime;
	String partOfDay;

	int sIndex = 0;
	int eIndex = 2;

	//Extract hour from the time
	hour = Integer.parseInt(time.substring(sIndex, time.indexOf(':')));
	time = time.substring(time.indexOf(':') + 1);		

	//Extract minutes from the time.
	minutes = Integer.parseInt(time.substring(sIndex, eIndex));
	time = time.substring(eIndex + 1);

	//Extract time of day, eg. AM or PM
	partOfDay = time.substring(sIndex, eIndex);

	//Convert to universal time
	if (partOfDay.compareTo("PM") == 0) 
	    hour = (hour % 12) + 12;
	else if (hour == 12)
	    hour = 0;
    
	combinedTime = (hour * 100) + minutes;
	return combinedTime;
    }

    /** Adds a free time TimeInterval to a list of intervals for a particular day.
	@param interval A TimeInterval that is to be added to a particular list.
	@param dayList A list of TimeIntervals to which interval is to be added.
	@throws FreeTimesAlreadySetException
    */
    public void addFreeTimesToList(TimeInterval interval,
					  List<TimeInterval> dayList)
					throws FreeTimesAlreadySetException {
	if (dayList.isEmpty())
	    dayList.add(interval);
	else {
	    String gripe = "ERROR: Free time constraints already set.  " +
			   "Add busy times.\n";
	    throw new FreeTimesAlreadySetException(gripe);
	}
    }	

    /** Adds a busy time TimeInterval to a list of intervals for a particular day.
	@param interval A TimeInterval that is to be added to a particular list.
	@param dayList A list of TimeIntervals to which interval to be added.
	@throws FreeTimesNotSetException
    */
    public void addBusyTimesToList(TimeInterval interval, 
					  List<TimeInterval> dayList) 
					throws FreeTimesNotSetException {
	TimeInterval currInterval;  //Current interval in dayList being examined
	int numElements;
	int sIndex;
	int eIndex;
	int counter = 0;
	
	numElements = dayList.size();
	if (numElements == 0) {          //If list is empty, just add the interval
	    String gripe = "ERROR: Cannot add busy times.  " +
			   "Must set free time constraints first.\n";
	    throw new FreeTimesNotSetException(gripe);
	}
	else 
	   do {
	       currInterval = dayList.get(counter);
	       
	       //If interval is completely before currInterval, do not add the 
	       //interval and stop looking.  This is because interval occurs 
	       //during a period which is already busy.
	       if (interval.isBefore(currInterval)) {
		   counter = numElements;
	       }
	       
	       //If interval is completely after currInterval, move on to the next
	       //interval in the list.
	       else if (interval.isAfter(currInterval)) {
		    counter++;
	       }
	       
	       //If interval is completely during currInterval, remove currInterval, 
	       //create 2 new intervals, and stop looking.
	       else if (interval.isDuring(currInterval)) {
		   dayList.add(counter + 1, new TimeInterval(
			          currInterval.getStart(), interval.getStart()));
		   dayList.add(counter + 2, new TimeInterval(
				  interval.getEnd(), currInterval.getEnd()));
		   dayList.remove(counter);
		   counter = numElements + 1;
	       }
	       
	 	//If interval begins before currInterval, but ends during it, 
		//change currInterval's start time to interval's end time.
	       else if (interval.isStartOut(currInterval)) {
		   currInterval.setStart(interval.getEnd());
		   counter = numElements;
	       }
	       
	       //If interval ends after currInterval, but starts during it,
	       //change currInterval's end time to interval's start time.
	       else if (interval.isEndOut(currInterval)) {
		   currInterval.setEnd(interval.getStart());
		   counter++;
	       }
	
	       //If currInterval occurs completely during interval, remove currInterval,
	       //since it occurs completely within a busy time period, and continue to
	       //check the rest of the intervals in the list.
	       else {
		   dayList.remove(counter);
	       }
	       
	       numElements = dayList.size();
	   } while(counter < numElements);
    }
    
    /** Creates a String representation of the free times for each day, combines 
	those strings into one all-encompassing string and returns it.
	@return A string representation of all free times common to all sets of times entered
		and which are within the restraints.
    */
    public String getFreeTimes() {
	String MfreeTimes;
	String TfreeTimes;
	String WfreeTimes;
	String RfreeTimes;
	String FfreeTimes;
	
	MfreeTimes = combine(MList, 'M');
	TfreeTimes = combine(TList, 'T');
	WfreeTimes = combine(WList, 'W');
	RfreeTimes = combine(RList, 'R');
	FfreeTimes = combine(FList, 'F');
	
	return (MfreeTimes + '\n' + TfreeTimes + '\n' + WfreeTimes + '\n' +
		RfreeTimes + '\n' + FfreeTimes);
    }

    /** Takes a list of intervals for a particular day, the character symbol for that
	day, and combines the free times in that list into a string which gets returned.
	Returned string has the form "M 6:00 AM-8:00 AM, 12:30 PM-2:00 PM".
	@param dayList A list of TimeIntervals which is to be combined into a string.
	@param day The day on which the times in dayList occur.
	@return A string representation of all free times contained in dayList.
    */
    public String combine(List<TimeInterval> dayList, char day) {
	TimeInterval interval;	
	String freeTimes;
	String startTime;
	String endTime;
	int size = dayList.size();

	freeTimes = day + " ";
	for (int index = 0; index < size; index++) {
	    interval = dayList.get(index);
	    //Only include time intervals that span 30 minutes or more. 
	    if (interval.difference() >= minFreeTimeSpan) {  
		startTime = interval.getStartTimeString();
		endTime = interval.getEndTimeString();
			
		if (index < (size - 1)) 
		    freeTimes += startTime + "-" + endTime + ", ";
		else 
		    freeTimes += startTime + "-" + endTime;
	    }
	}
	return freeTimes;
    }				
   
    /** Set the minimum time span for an interval to be considered valid free time.
	@param span String representation of the spanning time.
    */
    public void setMinFreeTimeSpan(String span) {
	minFreeTimeSpan = Integer.parseInt(span);
    }

    /** Return the minimum time span for an interval to be considered valid free time.
	@return Minimum time span.
    */
    public int getMinFreeTimeSpan() {
	return minFreeTimeSpan;
    }

    private int minFreeTimeSpan;
    private List<TimeInterval> MList;
    private List<TimeInterval> TList;
    private List<TimeInterval> WList;
    private List<TimeInterval> RList;
    private List<TimeInterval> FList;
}
