/**
 * A07
 * Group 3
 *  Matt Corbett (cemscor) - invertSched method
 *  James King (jcking) - convertToString method
 *  Andrew Toy (atoy) - model class, parseScheds and displayScheds method
 *  Graham Worrall (gworrall) - combineScheds and displayScheds method
 */

package com.example.model;

import java.util.*;

/**
 * Simple class used to represent each time range where a user is already occupied
 * in four-digit military time form (0000-2459)
 */
class times 
{
    int start;
    int end;
}

/**
 * Simple class used to represent one entire schedule
 */
class schedule
{
    //a vector of vectors of times for each day of the week 
    //outer vector represents the day and the inner vector represents
    //the time-pairs for that day
    
    Vector<Vector<times>> days = new Vector<Vector<times>>();
}

/**
 * Schedule class including the functions to parse, combine,
 *  invert, and change to a string.
 */
public class Sched 
{
    //each user in initially represented by 5 strings, 
    //one for each weekday with all occupied times listed for that day
    //private String[][] userString;
    private String[] userString;
    private String restrictionString;  	//a string indicating general meeting time guidelines
    public String[] availableString;	//the final result
    
    private schedule[] user;		//the parsed user schedules
    private times restrictions;	//parsed restriction guidelines
    
    private schedule superSched;	//merged schedule containing all four user's schedules
    private schedule available;	//inverted "super-schedule" listing all available times
    
    /**
     * Constructor, initializes some variables
     */
    public Sched() 
    {
	userString = new String[5];
	restrictionString = new String();
	availableString = new String[5];
	user = new schedule[5];
	for(int i = 0; i < 5; i++)
	    {
		user[i] = new schedule();
	    }
	restrictions = new times();
	superSched = new schedule();
	available = new schedule();
    }
    
    /**
     * Puts each schedule into the scheds array
     *
     * @param user is an int to tell which user is being added to
     * @param day is an int to tell which day is being added to
     * @param sched is an unformatted String with times that are unavailable
     */
    public void putSched(int user, int day, String sched)
    {
	userString[day] = sched;
    }
    
    /**
     * Factors in overall scheduling restrictions
     *
     * @param r is an unformatted String with the range the output shoudl be in
     */
    public void putRestrictions(String r)
    {
	restrictionString = r;
    }
    
    /**
     * Prints out all inputted schedules for debugging purposes
     *  flag = 0 for String output, flag = 1 for Vector output
     *  flag = 2 for superSched output,
     *  flag = 3 for inverted schedule output,
     *  flag = 4 for final string output
     *
     * @param flag is an int which tells what Schedule to output
     */
    public void displaySchedules(int flag)
    {
	if(flag == 0)		//output string representation
	    {
		//for(int u = 0; u < 4; u++)	//for each user 
		//{
		//System.out.println("\nUser " + (u + 1) + ":");
			
			for(int d = 0; d < 5; d++)	//for each day
			    {
				System.out.println(userString[d]);
			    }
			//}
	    }
	
	else if (flag == 1)		//output parsed military-time representation
	    {
		for(int u = 0; u < 4; u++)	//for each user 
		{
			System.out.println("\nUser " + (u + 1) + ":");
			
			for(int d = 0; d < 5; d++)	//for each day
			    {
				for(int i = 0; i < (user[u].days.elementAt(d).size()); i++)
				    {
					System.out.print(user[u].days.elementAt(d).elementAt(i).start);
					System.out.print("-");
					System.out.print(user[u].days.elementAt(d).elementAt(i).end);
					System.out.print(" ");
				    }
				System.out.println("");
			    }
		}
	    }
	
	else if (flag == 3)		//output inverted representation
	    {
		System.out.println("\nInverted:");
		
		for(int d = 0; d < 5; d++)      //for each day
		    {
			for(int i = 0; i < (available.days.elementAt(d).size()); i++)
			    {
				System.out.print(available.days.elementAt(d).elementAt(i).start);
				System.out.print("-");
				System.out.print(available.days.elementAt(d).elementAt(i).end);
				System.out.print(" ");
			    }
			System.out.println("");
		    }
	    }
	
	if(flag == 4)		//output string representation
	    {
		System.out.println("\nAvailable:");
		for(int d = 0; d < 5; d++)	//for each day
		    System.out.println(availableString[d] + " ");
	    }
	
	else			//output contents of superSched
	    {
		System.out.println("\nSuper Schedule:");
		
		for(int d = 0; d < 5; d++)      //for each day
		    {
			for(int i = 0; i < (superSched.days.elementAt(d).size()); i++)
			    {
				System.out.print(superSched.days.elementAt(d).elementAt(i).start);
				System.out.print("-");
				System.out.print(superSched.days.elementAt(d).elementAt(i).end);
				System.out.print(" ");
			    }
			System.out.println("");
		    }
	    }
    }
    
    /**
     * Compares and combines the different schedules and returns the result,
     *  taking time restrictions into consideration
     */
    public String[] getFreeTimes()
    {
	//parseScheds();
	combineScheds();
	invertSched();
	convertToString();
	
	String[] result = new String[5];
	
	result[0] = availableString[0];
	result[1] = availableString[1];
	result[2] = availableString[2];
	result[3] = availableString[3];
	result[4] = availableString[4];
	
	return result;
    }
    
    /**
     * Parses a schedule into a form that can be used for easy comparisons
     *  (integers in military times within schedule objects)
     *  and also ensures that the format entered is valid
     */
    public void parseScheds()
    {
	Vector<times> day;	//holds all time-pairs for a given day
	StringTokenizer st;
	String token;
	times temp;
	char[] ch = new char[2]; 
	int i = 0;
	int time = 0;
	
	//for(int u = 0; u < 4; u++)	//for each user
	//{
	for(int d = 0; d < 5; d++)	//for each day
	    {
		day = new Vector<times>();
		st = new StringTokenizer(userString[d], ", ");
		while(st.hasMoreTokens())
		    {
			i = 0;
			temp = new times();
			
			//get start time
			ch[0] = ch[1] = 'a';	//initialize to non-digit
			token=st.nextToken();
			token.getChars(i, token.indexOf(':'), ch, 0);
			
			//ensure character is valid before using it for the time
			if(Character.isDigit(ch[0]))
			    {
				time = (((int)ch[0] - 48) * 10);
			    }
			if(Character.isDigit(ch[1]))
			    {
				time += ((int)ch[1] - 48);
			    }
			
			//deals with times after 1PM and before 2PM
			if(((token.indexOf(':') - i) == 1) && (ch[0] == '1'))
			    {
				time /= 10;
			    }
			
			time *= 100;
			
			if(time > 1200)
			    {
				time /= 10;
				i += 2;
			    }
			else if((time > 99) && (time < 200))
			    {
				i += 2;
			    }
			else
			    {
				i += 3;
			    }
			ch[0] = ch[1] = 0;
			
			//check for meridiem, update if PM
			token.getChars(i, i + 2, ch, 0);
			time += (((int)ch[0] - 48) * 10) + ((int)ch[1] - 48);
			
			if(((token.charAt(i + 2) == 'p') || (token.charAt(i + 2) == 'P')) && (time < 1200))
			    {
				time += 1200;
			    }
			temp.start = time;
			
			//get end time
			i = token.indexOf('-') + 1;
			ch[0] = ch[1] = 'a';     //initialize to non-digit
			token.getChars(i, token.lastIndexOf(':'), ch, 0);
			if(Character.isDigit(ch[0]))
			    {
				time = (((int)ch[0] - 48) * 10);
			    }
			if(Character.isDigit(ch[1]))
			    {
				time += ((int)ch[1] - 48);
			    }
			if(((token.lastIndexOf(':') - i) == 1) && (ch[0] == '1'))
			    {
				time /= 10;
			    }
			time *= 100;
			
			if(time > 1200)
			    {
				time /= 10;
				i += 2;
			    }
			else if((time > 99) && (time < 200))
			    {
				i += 2;
			    }
			else
			    {
				i += 3;
			    }
			ch[0] = ch[1] = 0;
			
			//check for meridiem, update if PM
			token.getChars(i, i + 2, ch, 0);
			time += (((int)ch[0] - 48) * 10) + ((int)ch[1] - 48);
			
			if(((token.charAt(i + 2) == 'p') || (token.charAt(i + 2) == 'P')) && (time < 1200))
			    {
				time += 1200;
			    }
			
			temp.end = time;
			
			day.add(temp);
			time = 0;	//reset some counters
		    }
		user[0].days.add(day);	//add day vector to days vector
	    }
    }
    
    //parse restriction string into a time object with the given times
    public void parseRestriction()
    {
	Vector<times> day;	//holds all time-pairs for a given day
	StringTokenizer st;
	String token;
	times temp;
	char[] ch = new char[2]; 
	int i = 0;
	int time = 0;
	
	//get start time
	ch[0] = ch[1] = 'a';	//initialize to non-digit
	token = restrictionString;
	token.getChars(i, token.indexOf(':'), ch, 0);
	
	//ensure character is valid before using it for the time
	if(Character.isDigit(ch[0]))
	    {
		time = (((int)ch[0] - 48) * 10);
	    }
	if(Character.isDigit(ch[1]))
	    {
		time += ((int)ch[1] - 48);
	    }
	
	//deals with times after 1PM and before 2PM
	if(((token.indexOf(':') - i) == 1) && (ch[0] == '1'))
	    {
		time /= 10;
	    }
	
	time *= 100;
	
	if(time > 1200)
	    {
		time /= 10;
		i += 2;
	    }
	else if((time > 99) && (time < 200))
	    {
		i += 2;
	    }
	else
	    {
		i += 3;
	    }
	ch[0] = ch[1] = 0;
	
	//check for meridiem, update if PM
	token.getChars(i, i + 2, ch, 0);
	time += (((int)ch[0] - 48) * 10) + ((int)ch[1] - 48);
	
	if(((token.charAt(i + 2) == 'p') || (token.charAt(i + 2) == 'P')) && (time < 1200))
	    {
		time += 1200;
	    }
	restrictions.start = time;
	
	//get end time
	i = token.indexOf('-') + 1;
	ch[0] = ch[1] = 'a';     //initialize to non-digit
	token.getChars(i, token.lastIndexOf(':'), ch, 0);
	if(Character.isDigit(ch[0]))
	    {
		time = (((int)ch[0] - 48) * 10);
	    }
	if(Character.isDigit(ch[1]))
	    {
		time += ((int)ch[1] - 48);
	    }
	if(((token.lastIndexOf(':') - i) == 1) && (ch[0] == '1'))
	    {
		time /= 10;
	    }
	time *= 100;
	
	if(time > 1200)
	    {
		time /= 10;
		i += 2;
	    }
	else if((time > 99) && (time < 200))
	    {
		i += 2;
	    }
	else
	    {
		i += 3;
	    }
	ch[0] = ch[1] = 0;
	
	//check for meridiem, update if PM
	token.getChars(i, i + 2, ch, 0);
	time += (((int)ch[0] - 48) * 10) + ((int)ch[1] - 48);
	
	if(((token.charAt(i + 2) == 'p') || (token.charAt(i + 2) == 'P')) && (time < 1200))
	    {
		time += 1200;
	    }
	
	restrictions.end = time;
	System.out.print(restrictions.end);
    }
    
    /**
     * Combines independent schedules (including the "restriction" schedule)
     *  into one "super-schedule" with all of the
     *  times factored in and no over-lapping time slots
     */
    public void combineScheds()
    {
	//add user 1 to super sched
	for(int i = 0; i < user[0].days.size(); i++)
	    {
		superSched.days.add(user[0].days.elementAt(i));
	    }
	
	//add user 2 to super sched
	for(int i = 0; i < user[1].days.size(); i++)  //cycles through days
	    {
		for(int j = 0; j < user[1].days.elementAt(i).size(); j++) //cycles through times
		    {
			superSched.days.elementAt(i).add(user[1].days.elementAt(i).elementAt(j));
		    }
	    }
	
	//add user 3 to super sched
	for(int i = 0; i < user[2].days.size(); i++)  //cycles through days
	    {
		for(int j = 0; j < user[2].days.elementAt(i).size(); j++) //cycles through times
		    {
			superSched.days.elementAt(i).add(user[2].days.elementAt(i).elementAt(j));
		    }
	    }
	
	//add user 4 to super sched
	for(int i = 0; i < user[3].days.size(); i++)  //cycles through days
	    {
		for(int j = 0; j < user[3].days.elementAt(i).size(); j++) //cycles through times
		    {
			superSched.days.elementAt(i).add(user[3].days.elementAt(i).elementAt(j));
		    }
	    }
	
	//remove overlapping time slots
	int flag = 0;  //0=time not added  1=time added
	int redo = 0;
	for(int i = 0; i < superSched.days.size(); i++) //cycles through days
	    {
		redo = 0;
		while(redo == 0)
		    {
			redo = 0;
			for(int j = 0; j < superSched.days.elementAt(i).size(); j++)
			    {
				for(int k = 0; k < j && flag == 0; k++)
				    {
					if(superSched.days.elementAt(i).elementAt(j).start > superSched.days.elementAt(i).elementAt(k).start)
					    {
						if(superSched.days.elementAt(i).elementAt(j).end > superSched.days.elementAt(i).elementAt(k).end)
						    {
							if(superSched.days.elementAt(i).elementAt(j).start > superSched.days.elementAt(i).elementAt(k).end)
							    {
								//Case 5
								//adds j to final list
							    }
							else
							    {
								//Case 2
								superSched.days.elementAt(i).elementAt(k).end = superSched.days.elementAt(i).elementAt(j).end;
								superSched.days.elementAt(i).remove(j);
								j--;
								flag = 1;
								redo++;
							    }
						    }
						else
						    {
							//Case 1
							superSched.days.elementAt(i).remove(j);
							j--;
							flag = 1;
							redo++;
						    }
					    }
					else if(superSched.days.elementAt(i).elementAt(j).start <= superSched.days.elementAt(i).elementAt(k).start)
					    {
						if(superSched.days.elementAt(i).elementAt(j).end < superSched.days.elementAt(i).elementAt(k).end)
						    {
							if(superSched.days.elementAt(i).elementAt(j).end < superSched.days.elementAt(i).elementAt(k).start)
							    {
								//Case 4
								//adds j to final list
							    }
							else
							    {
								//Case 3
								superSched.days.elementAt(i).elementAt(k).start = superSched.days.elementAt(i).elementAt(j).start;
								superSched.days.elementAt(i).remove(j);
								j--;
								flag = 1;
								redo++;
							    }
						    }
						else
						    {
							//Case 6
							superSched.days.elementAt(i).remove(k);
							j--;
							flag = 1;
							redo++;
						    }
					    }
					
					else
					    {
						//should not get here
					    }
				    }
				flag = 0;
			    }
		    }
	    }
	
	//sort from earliest to latest
	int sent = 0;
	for(int i = 0; i < superSched.days.size(); i++)  //cycles through days
	    {
		sent = 0;
		for(int j = 0; j < superSched.days.elementAt(i).size(); j++)  //cycles through times
		    {
			sent = 0;
			for(int k = 0; k < j && sent == 0; k++)
			    {
				if(superSched.days.elementAt(i).elementAt(j).start < superSched.days.elementAt(i).elementAt(k).start)
				    {
					superSched.days.elementAt(i).insertElementAt(superSched.days.elementAt(i).elementAt(j), k);
					superSched.days.elementAt(i).removeElementAt(j + 1);
					sent = 1;
				    }
			    }
		    }
	    }
    }
    
    /**
     * Invert the "super-schedule" to produce a schedule listing all of the available
     *  free times
     */
    public void invertSched()
    {
	times temp;
	Vector <times> temp2;
	for(int d = 0; d < 5; d++)	//for each day
	    {
		temp = new times();
		temp2 = new Vector<times>();
		temp.start = restrictions.start;
		temp.end = superSched.days.elementAt(d).elementAt(0).start;
		temp2.add(temp);
		
		for(int i = 1; i < (superSched.days.elementAt(d).size()); i++)
		    {
			temp = new times();
			temp.start = superSched.days.elementAt(d).elementAt(i - 1).end;
			temp.end = superSched.days.elementAt(d).elementAt(i).start; 
			temp2.add(temp);
		    }
		temp = new times();
		temp.start = superSched.days.elementAt(d).elementAt(superSched.days.elementAt(d).size() - 1).end;
		temp.end = restrictions.end;
		temp2.add(temp);
		available.days.add(temp2);
	    }
    }
    
    /**
     * Converts the "super-schedule" object to a single String (availableString)
     *  in standard-time human-readable format
     */
    public void convertToString()
    {
	availableString = new String[5];  // five days in a week
	int numAvail;                     // number of times in a given day
	int am = 0;                       // is the time am?
	int start, end;
	int hours, min;
	
	// put each day into the string
	for (int i = 0; i < 5; i++ )
	    {
		// how many times are in the given day
		numAvail = available.days.elementAt(i).size();
		
		//insert each time into that days string of available times
		for (int j = 0; j < numAvail; j++ )
		    {		   
			// store start and end times
			start = available.days.elementAt(i).elementAt(j).start;
			end = available.days.elementAt(i).elementAt(j).end;
			
			// first add the start time
			if ((start / 100) == 12)
			    {
				am = 0;
				hours = start / 100;
			    }
			else if ((start / 100) < 13)
			    {
				am = 1;
				hours = start / 100;
			    }
			else if ((start / 100) == 24)
			    {
				am = 1;
				hours = (start / 100) - 12;
			    }
			else
			    {
				am = 0;
				hours = (start / 100) - 12;
			    }
			min = start % 100;
			if(min < 10)
			    {
				availableString[i] = availableString[i] + hours + ":" + "0" + min;
			    }
			else
			    {
				availableString[i] = availableString[i] + hours + ":" + min;
			    }
			
			if (am == 1)
			    {
				availableString[i] = availableString[i] + "am";
			    }
			else
			    {
				availableString[i] = availableString[i] + "pm";
			    }
			
			availableString[i] = availableString[i] + "-";
			
			// now add the end time
			if ((end / 100) == 12)
			    {
				am = 0;
				hours = end / 100;
			    }
			else if ((end / 100) < 13)
			    {
				am = 1;
				hours = end / 100;
			    }
			else if ((end / 100) == 24)
			    {
				am = 1;
				hours = (end / 100) - 12;
			    }
			else
			    {
				am = 0;
				hours = (end / 100) - 12;
			    }
			
			min = end % 100;
			
			if(min < 10)
			    {
				availableString[i] = availableString[i] + hours + ":" + "0" + min;
			    }
			else
			    {
				availableString[i] = availableString[i] + hours + ":" + min;
			    }
			
			if (am == 1)
			    {
				availableString[i] = availableString[i] + "am";
			    }
			else
			    {
				availableString[i] = availableString[i] + "pm";
			    }
			
			// don't add comma to last element
			if (j < (numAvail - 1))
			    {
				availableString[i] = availableString[i] + ", ";
			    }
		    }   
		availableString[i] = availableString[i].substring(4);
	    }	   
    }
}
