/**
 * A10
 * Group D
 */

package com.example.model;

import java.util.*;
import java.lang.*;
import java.sql.Connection;
import java.sql.Date;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.servlet.*;

/**
 * Schedule class including the functions to parse, combine,
 *  invert, and change to a string.
 */
public class Sched 
{
    
    /**
     * Simple class used to represent each time range where a user is already occupied
     * in four-digit military time form (0000-2459)
     */
    private class times 
    {
	int start;
	int end;
    }
    
    /**
     * Simple class used to represent one entire schedule
     */
    private 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>>();
    }

    //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 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[4][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();
	
	String url = "jdbc:mysql://jaguar.cis.udel.edu:8099/worrall";
	String username = "worrall";
	String password = "h524bl";
	
	try
	    {
		Class.forName("org.gjt.mm.mysql.Driver").newInstance();
		conn = DriverManager.getConnection(url, username, password);
		stmt = conn.createStatement();
	    }
	catch(Exception e)
	    {
	    }
    }
    
    /**
     * 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[user][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[u][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[u][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[u].days.add(day);	//add day vector to days vector
		    }
	    }
	
	//parse restriction string into a time object with the given times
	{
	    i = 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);
	    }	   
    }
    
    //-------------------------------------------------------------------------------------
    
    /**
     *  Takes a username and password and returns true if there is anentry
     *  in the database with a matching username and password, and false
     *  if there isn't.
     *
     *  @param username is a string that contains the username to login
     *  @param password is a string that contains the password to login
     *  @return is a boolean value which is true if the user logs in successfully and falso otherwise
     */
    public boolean login(String username, String password)
	throws Exception
    {
	Exception err = null;
        boolean match = false;
	
	try {
	    // Create the select statement.
	    final String selectStmt = "SELECT password FROM sched WHERE " +
		"username = '" + username + "'";
	    
	    // Execute the query.
	    ResultSet result = stmt.executeQuery(selectStmt);
	    
	    // Test if the password in the database matches the one
	    // entered.
	    while(result.next())
		{
		    if( password.equals(result.getString("password")) )
			match = true;
		}
	}
	catch(SQLException e) {
	    e.printStackTrace();
	    err = e;
	}
	
	if(err != null)
	    throw err;
	else    
	    return(match);
    }	
    
    /**  Adds a user to the database.
     *  Returns true is the user was added and false if the specified
     *  username is already in the database.
     *
     *  @param username is a string that contains a username
     *  @param password is a string that contains a password
     *  @param email is a string which contains an email address
     *  @return boolean value which is true if account is created succesfully and false otherwise
     */
    public boolean createAccount(String username, String password, 
				 String email)
	throws Exception
    {
	Exception err = null;
        boolean success = false;
	
	try {
	    // Create a select statement to test if the user is already
	    // in the database.
	    final String selectStmt = "SELECT * FROM sched WHERE username = '"
		+ username + "'";
	    
	    // Execute the query.
	    ResultSet result = stmt.executeQuery(selectStmt);
	    
	    // If the user isn't in the database, execute an INSERT
	    // statement to add him.
	    if(!result.next())
		{
		    stmt.executeUpdate("INSERT INTO " +
				       "sched(username, password, email) " +
				       "VALUES ('" + username + "', '" +
				       password + "', '" + email + "')");
		    success = true;
		}
	}
	catch(SQLException e) {
	    e.printStackTrace();
	    err = e;
	}
	
	if(err != null)
	    throw err;
	else    
	    return(success);
    }
    
    /**
     *  Takes a username and returns an array of Strings containing the
     *  user's schedules for Monday through Friday.
     *  The array contains null values if the username isn't found in the
     *  database.
     *
     *  @param username is a username which is being looked up
     *  @return is an array of strings which hold the unavailable times for each day
     */
    public String[] lookupSchedule(String username)
	throws Exception
    {
	Exception err = null;
	
        // Initialize the day array to null;
	String days[] = {null, null, null, null, null};

	try {
	    // Create the select statment.
	    final String selectStmt = "SELECT * FROM sched WHERE " +
		"username = '" + username + "'";
	    
	    // Execute the query.
	    ResultSet result = stmt.executeQuery(selectStmt);
	    
	    // If the user's entry was found, place his schedules into the
	    // array.
	    if(result.next())
		{
		    days[0] = result.getString("monday");
		    days[1] = result.getString("tuesday");
		    days[2] = result.getString("wednesday");
		    days[3] = result.getString("thursday");
		    days[4] = result.getString("friday");
		}
	}
	catch(SQLException e) {
	    e.printStackTrace();
	    err = e;
	}
	
	if(err != null)
	    throw err;
	else    
	    return(days);
    }	
    
    /**
     *  Takes a username and schedules for Monday through Friday and
     *  enters them into the database.
     *
     *  @param username is a string which has the username which is being added to
     *  @param monday is a string of unavailable times for monday
     *  @param tuesday is a string of unavailable times for tuesday
     *  @param wednesday is a string of unavailable times for wednesday
     *  @param thursday is a string of unavailable times for thursday
     *  @param friday is a string of unavailable times for friday
     */
    public void addSchedule(String username, String monday, 
			    String tuesday, String wednesday, 
			    String thursday, String friday)
	throws Exception
    {
	Exception err = null;
	
	try {
	    stmt.executeUpdate("UPDATE sched " +
			       "SET monday = '" + monday +
			       "', tuesday = '" + tuesday +
			       "', wednesday = '" + wednesday +
			       "', thursday = '" + thursday +
			       "', friday = '" + friday +
			       "' WHERE username = '" + username + "';");
	}
	catch(SQLException e) {
	    e.printStackTrace();
	    err = e;
	}

	if(err != null)
	    throw err;
    }	
    
    // Closes the connection to the database.
    public void close()
    {
	if(conn != null)
	    {
		try { conn.close(); }
		catch(Exception e) { }
	    }
    } 
    
    private Connection conn;
    private Statement stmt; 
}
