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

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