/**
 * 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.*;
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.
    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.
    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.
    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.
    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; 
}
