package com.example.model;

import java.util.*;
import java.io.*;
import java.sql.*;
import javax.servlet.*;

/**
 * The Scheduler class serves as the Model that calculates all free times
 * within our web application.  In this function, the user has the ability
 * to change the time constraints on the given schedule, add user busy times
 * to the schedule, calculate the free times, and access only their busy/free
 * times.
 *
 * @author Matthew Arbaugh, Yana Novikova, Steve Parise, Tara Strobel
 * @version CISC474:A10
 */
public class Scheduler {

  private Connection myCon;
  private Statement  myStmt;
  private String     driverName = "org.gjt.mm.mysql.Driver";
  private String     jdbcURL = null;
  private String     pw = null;
  private String     user = null;

  static final int   DAYS_IN_WEEK = 7;
  static final int   MINUTES_IN_DAY = 1440;

  private boolean    busyTimes[][];
  private int        timeConstraint_startTime;
  private int        timeConstraint_endTime; 

  /**
   * The default constructor initializes a 2-D boolean array
   * named busyTimes to false indicating that the Scheduler has
   * no busy times set.  The boolean array consists of the following
   * structure: busyTimes[DAYS_IN_WEEK][MINUTES_IN_DAY+1];
   */
  public Scheduler() {
	// initialize the array of busy times to false indicating that
	// the schedule is clear
	busyTimes = new boolean[DAYS_IN_WEEK][MINUTES_IN_DAY+1];
	for(int i=0; i<DAYS_IN_WEEK; i++) 
		for(int j=0; j<=MINUTES_IN_DAY; j++)
			busyTimes[i][j] = false;

	// set default time constraints of 8:00am (480minutes) 
	// to 5:00pm (1020 minutes)
	timeConstraint_startTime = 480;
	timeConstraint_endTime = 1020;
  }// end Scheduler()

  /**
   * The init method creates a new instance of our driver.  
   *
   * @throws ServletException
   */
  public void init() throws ServletException{
    try {
         Class.forName(driverName).newInstance();
        }
    catch (Exception e) {
          e.printStackTrace();
        }
  }//end init()

  /**
   * Accessor function used to set the start time constraint of the scheduler.
   *
   * @param sh The starting hour of the time constraint.
   * @param sm The starting minute of the time constraint.
   */
  public void setTimeConstraint_StartTime(int sh, int sm) {
      int start = sh*60 + sm;
      timeConstraint_startTime = start;
  } //end setTimeConstraint_StartTime()

  /**
   * Accessor function used to set the end time constraint of the scheduler.
   *
   * @param eh The ending hour of the time constraint.
   * @param em The ending minute of the time constraint.
   */
  public void setTimeConstraint_EndTime(int eh, int em) {
      int end = eh*60 + em;
      timeConstraint_endTime = end;
  } // end setTimeConstraint_EndTime()

  /**
   * Erases all busy times that are currently in the schedule.
   */
  public void clearSchedule()
  {
       for(int i=0; i<DAYS_IN_WEEK; i++) 
		for(int j=0; j<MINUTES_IN_DAY; j++)
			busyTimes[i][j] = false;
  } //end clearSchedule()

  /**
   * This method takes the users that were selected in the checkboxes, performs a select
   * query on the database to get each of the busy times associated with each input user
   * and then updates the busy times array in this class to account for the busy times in
   * the database.
   *
   * @param users A String Array of users containing all usernames that were selected.
   * @return A String containing the code for a table containing all free times.
   */
  public String getUserSchedule(String users[]) {
	
	ResultSet results;
	String qry;

	// for each user that was selected
	for(int i=0; i<users.length; i++)
	{
	   // create a query to perform on the database to get a list of busy times
	   // associated with each input user
	   qry = "Select * from BusyTimes where username='" + users[i] + "'";

	   try {
		// connect to the database and execute a query to get all of hte busy times
		// for the user as brought in via checkboxes
 		connector();
	   	results = myStmt.executeQuery(qry);

	   	while(results.next()) {
		   // use a helper function to convert the string to a time unit and then update
		   // the array with busy times
	           this.updateBooleanArray(toTimeUnit(results.getString("busy"))); 		
		}

		// close the database and statement to avoid memory leaks
 		closer();
		myStmt.close();
	   }
	   catch(Exception e) {
		e.printStackTrace();
	   }
	}

 	// calculate the free times in this Scheduler object
	String freeTimes = this.getFreeTimes();

	// return a string -- this string represents a table with all of the free times in it
	return freeTimes;
   } //end getUserSchedule()

  /**
   * This function adds the busy time unit from the servlet into the database and then into
   * the scheduler.
   *
   * @param tu A TimeUnit object that represents the time to add to the schedule.
   * @param username The username of the person to add to the scheudler.
   */
   public void addBusyTime(TimeUnit tu, String uname) {
      String qry = "Insert into BusyTimes values('" + uname + "','" + tu.toString() + "')"; 

      try {
	// connect to database and add busy time
	connector();
	myStmt.executeUpdate(qry);
	closer();
	myStmt.close();
      }
      catch(Exception e) {
	e.printStackTrace();
      }
   }

  /**
   * This function deletes the busy time unit from the servlet into the database and then into 
   * the scheduler.
   *
   * @param tu A TimeUnit object that represents the time to delete from the schedule.
   * @param username The username of the person to delete from the scheudler.
   */
   public void deleteBusyTime(String tu, String uname) {
     
       String qry = "Delete from BusyTimes where username='" + uname + "' and busy='" + tu + "'";

      try {
        // connect to database and delete busy time
        connector();
        myStmt.executeUpdate(qry);
        closer();
        myStmt.close();
      }
      catch(Exception e) {
        e.printStackTrace();
      }
   }


  /**
   * Generates html code to create a table of free times to return to the user.  
   * The busy times array is traversed and all elements whose value is 'false'
   * is added to a string to return to the user.
   * 
   * @return A string containing html code to form a table with all of the free 
   *         times available in the schedule
   */
  public String getFreeTimes() {

	String   freeTimes    = "<table border=1 width=\"750px\"><tr>";
	double   start        = -1;
	double   end          = -1;
	boolean  freeTimeFlag = false;
	TimeUnit tempTime     = new TimeUnit(); 

	// loop for each day of the week 
        for(int i=0; i<DAYS_IN_WEEK; i++) {
	    
		freeTimes += "<td valign=top style=\"font-family:'Georgia','Times New Roman';font-size:9pt;\" >";

		// loop for the entire day within set time constraints
                for(int j=timeConstraint_startTime; j<=timeConstraint_endTime; j++) {

		    	// while there is a free time (busyTimes=false) and the flag is false - set flags
                       	if((busyTimes[i][j] == false) && (freeTimeFlag == false)) { 
				start = j;
				freeTimeFlag = true; 
			}

			//detect the end of the free time block (busyTimes=true = start of busy block of time
			if((busyTimes[i][j] == true) && (freeTimeFlag == true)) {
				end = j-1;
				freeTimeFlag = false; 
			
				// transform the time in minutes into its numerical equivalent	
				double startHours = Math.floor(start/60);
				double startMinutes = start - (60*startHours);
				double endHours = Math.floor(end/60);
				double endMinutes = end - (60*endHours);

				// create a time unit object`
			 	tempTime.setTimeUnit(i,
						 (int)startHours,
						 (int)startMinutes,
						 (int)endHours,
						 (int)endMinutes);

				// add the timeUnit object to the table
				freeTimes += tempTime.toString() + "<br> ";
			}

			//special case where the free time block runs past minutes in the day
			if((freeTimeFlag == true) && (busyTimes[i][j] == false) &&
			  (j == timeConstraint_endTime)) {

			    	end = j;
			    	freeTimeFlag = false; 
			   
			    	// transform the time in minutes into its hour and minute equivalent
			    	double startHours = Math.floor(start/60);
			    	double startMinutes = start - (60*startHours);
			    	double endHours = Math.floor(end/60);
			    	double endMinutes = end - (60*endHours);

				// create a time unit object
			    	tempTime.setTimeUnit(i,
					     (int)startHours,
					     (int)startMinutes,
					     (int)endHours,
					     (int)endMinutes);

				// add the timeUnit object to the table
			    	freeTimes += tempTime.toString() + "<br> ";
			}

		}
		freeTimes += "</td>";
	}
	freeTimes += "</tr></table>";
	return freeTimes;
    } //end getFreeTimes()

    /**
     * Takes in a TimeUnit object and goes through and updates the busy times array to account
     * for this new TimeUnit object.
     *
     * @param busyBlock A TimeUnit object indicating a block of time that the user is busy
     */
    public void updateBooleanArray(TimeUnit busyBlock)
    {
        int currentDay = busyBlock.getDay();
        int start = busyBlock.getStartHour()*60 + busyBlock.getStartMinutes();
        int end = busyBlock.getEndHour()*60 + busyBlock.getEndMinutes();

        for(int j=0; j<MINUTES_IN_DAY; j++) {
           if( (j > start) && (j <= (end - 1)) ) {
                 busyTimes[currentDay][j] = true;
           }
        }
    } //end updateBooleanArray() 

    /**
     * This function doesn't do anything right now!!
     */
    public void addBusyBlock(TimeUnit busyBlock, String username_from_form) throws SQLException{

	String temp = busyBlock.toString();	

	try{
		//connector();
		String insert_query = "INSERT INTO parise.BusyTimes " + 
				      "values('" + username_from_form + "', '" + temp + "')";	
		myStmt.executeQuery(insert_query);
	        //closer();
        	//myStmt.close();
	}

        catch (SQLException sqlEx) {
        throw sqlEx;
        }
	/**	int start = busyBlock.getStartHour()*60 + busyBlock.getStartMinutes();
		int end = busyBlock.getEndHour()*60 + busyBlock.getEndMinutes();

		for(int j=0; j<MINUTES_IN_DAY; j++) {
	    	if( (j > start) && (j <= (end - 1)) ) {
			busyTimes[currentDay][j] = true; 
	    	}
		}*/


    }  //end addBusyBlock

/**    public List getUsersAndBusyTimes() throws SQLException {
	List usertimes = new LinkedList();
	
	try{
		connector();
		String users_query = "Select DISTINCT username from parise.BusyTimes";
		ResultSet result = myStmt.executeQuery(users_query);

		while(result.next())
		{
	  	String userName = result.getString("user");
	  	usertimes.add((userName + ":")); 
	  	usertimes.add("\n");
          
	  	String busy_query = "Select busy from parise.BusyTimes WHERE username='" + userName + "'";
	  	ResultSet busyTimes = myStmt.executeQuery(busy_query);
	
	  	while(busyTimes.next()) 
			usertimes.add(("        " + busyTimes.getString("busy"))); 
		}	  
	
		closer();
		myStmt.close();
	}

	catch (SQLException sqlEx) {
	  usertimes.add(sqlEx);
          throw sqlEx;
	}
	    
	finally{
	  return usertimes;
	}

    }
*/

    /**
     * Closes database connection
     *
     * @throws SQLException
     */
    void closer () throws SQLException {
         myCon.close();
    } //end closer()

    /** 
     * Opens database connection with the following username and password.
     *
     * @throws SQLException
     */
    void connector() throws SQLException{
         try{
              final String URL= jdbcURL;
              final String username=user;
              final String password=pw;

              myCon = DriverManager.getConnection(URL, username, password);
	      myStmt = myCon.createStatement();
         }
         catch (SQLException sqlEx) {
               throw sqlEx;
         }
         catch (Exception e) {
                System.err.println(e);
         }
    } //end connector()

    /**
     * Sets the connection variables required to access the SQL database.
     *
     * @param URL The URL of the database.
     * @param username The username of the database that we are accessing.
     * @param password The password of the database that we are accessing.
     */
    public void setSqlConnVars(String URL, String username, String password)
    {
         jdbcURL    = URL;
         user       = username;
         pw         = password;
    } //end setSQLConnVars()

    /**
     * Conversion method used to convert a string that is input by the user 
     * into a TimeUnit object to be used by the Scheduler class.
     *
     * @param str The busy String indicating a busy block of time.
     */
    public TimeUnit toTimeUnit(String str) {
	int d = 0;
	String str_day = str.substring(0,3);

	if(str_day.equals("Sun")) d=0;
	else if(str_day.equals("Mon")) d=1;
        else if(str_day.equals("Tue")) d=2;
        else if(str_day.equals("Wed")) d=3;
        else if(str_day.equals("Thu")) d=4;
        else if(str_day.equals("Fri")) d=5;
        else if(str_day.equals("Sat")) d=6;
	else d=0;

	String str_sh = str.substring(5,7);
	int s_h = Integer.parseInt(str_sh);

        String str_sm = str.substring(8,10);
        int s_m = Integer.parseInt(str_sm);

        String str_eh = str.substring(11,13);
        int e_h = Integer.parseInt(str_eh);

        String str_em = str.substring(14,16);
        int e_m = Integer.parseInt(str_em);

	TimeUnit tu = new TimeUnit(d, s_h, s_m, e_h, e_m);	
	return tu;
    } //end toTimeUnit()

/**public void updateBooleanArray(TimeUnit busyBlock)
{
               int currentDay = busyBlock.getDay();
               int start = busyBlock.getStartHour()*60 + busyBlock.getStartMinutes();
               int end = busyBlock.getEndHour()*60 + busyBlock.getEndMinutes();

               for(int j=0; j<MINUTES_IN_DAY; j++) {
               if( (j > start) && (j <= (end - 1)) ) {
                       busyTimes[currentDay][j] = true; 
                }
        	}

}*/

}
