/*
********************************************************************************
*  Kelli Wiseth
*  kelli[at]alameda-tech-lab[dot]com
*  CIS 255AX
*  Program name: StudentRecords.java
*  Program Description: StudentRecords is a windowed application that processes
*     student records contained in a flat file (student.dat). The application 
*     lets users add one student record at a time, and display all records in the
*     file. 
*
*  Assignment #7
*  10 May 2005
********************************************************************************
*/

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.*;


public class StudentRecords extends JFrame {
   private JButton addStudentButton, displayAllStudentsButton;
   private String firstName, lastName, studentID;
   private double gpa;
   private JTextField textFirstName, textLastName, textID, textGpa;
   private JLabel fNameLabel, lNameLabel, idLabel, gpaLabel;
   private Container container;
   private JPanel entryPanel, buttonPanel, outputPanel;
   private JTextArea outputArea;
   private boolean firstNameBlank, lastNameBlank, studentIDBlank, gpaBlank;  //flags for each field
   private boolean junkFirstName, junkLastName, junkStudentID, junkGpa;
   private Student[] student;
   private StringTokenizer tokenizer;
   private String filename = "student.dat";
   private String line, studentData;
   private int array_size;
   private int array_ctr;
   
   // set up GUI
   public StudentRecords() {
   	
   	  super( "Kelli Wiseth StudentRecords Application" );

      // set up user interface layouts
      entryPanel = new JPanel();
      entryPanel.setLayout(new GridLayout(5, 2, 5, 5));
      
      // get content pane and set its layout
      container = getContentPane();
      
      // create and add labels and textfields
      // Label and entry text field for first name
      fNameLabel = new JLabel("First name ");
      entryPanel.add(fNameLabel);
      fNameLabel.setHorizontalAlignment(SwingConstants.RIGHT);
      textFirstName = new JTextField( 10 );
      textFirstName.setToolTipText("First name can be all caps, all lower-case, or upper- and lower-case.");
      entryPanel.add(textFirstName);
     
      // Label and entry text field for last name
      lNameLabel = new JLabel("Last name ");
      lNameLabel.setHorizontalAlignment(SwingConstants.RIGHT);
      entryPanel.add(lNameLabel);
      textLastName = new JTextField( 10 );
      textLastName.setToolTipText("Last name can be all caps, all lower-case, or upper- and lower-case.");
      entryPanel.add(textLastName);
       
      // Label and entry text field for student id
      idLabel = new JLabel("Student ID ");
      idLabel.setHorizontalAlignment(SwingConstants.RIGHT);
      entryPanel.add(idLabel);
      textID = new JTextField( 10 );
      textID.setToolTipText("Student ID is a series of digits and dashes, as in nnn-nn-nnnn");
      entryPanel.add(textID);
      
      // Label and entry text field for grade point average
      gpaLabel = new JLabel("Grade point average ");
      gpaLabel.setHorizontalAlignment(SwingConstants.RIGHT);
      entryPanel.add(gpaLabel);
      textGpa = new JTextField( 10 );
      textGpa.setToolTipText("Enter GPA as n.nn or n--as in 4 or 4.00");
      entryPanel.add(textGpa);

      // create and add buttons
      addStudentButton = new JButton("Add Student Record");
      entryPanel.add(addStudentButton);
      
      displayAllStudentsButton = new JButton("Display All Student Records");
      entryPanel.add(displayAllStudentsButton);
      
      ButtonHandler handler = new ButtonHandler();
      addStudentButton.addActionListener(handler);
      displayAllStudentsButton.addActionListener(handler);     

      container.add(entryPanel, BorderLayout.NORTH);
      
      outputArea = new JTextArea(10,10);
      outputArea.setEditable(false);
      outputArea.setText("");
      container.add(new JScrollPane(outputArea), BorderLayout.CENTER);
      setSize( 425, 600 );
      setVisible( true );

   } // end constructor StudentRecords
   

   /**********************************************************************
    * quickSort() method provided by M.Green, modified for StudentRecords 
    * The quickSort method takes an array, and the values of the first 
    * and last elements in the array as starting points
    *
    **********************************************************************
   */
    
    public void quickSort(Student student[], int first, int last ) {

    int currentLocation;
    
    if (first >= last)
        return;
      
     // partitioning phase
    currentLocation = partition( student, first, last );

     // sort the left side
    quickSort( student, first, currentLocation - 1 );

      // sort the right side
    quickSort( student, currentLocation + 1, last );
      
    } //closing brace, quickSort method
 
 
   /**********************************************************************
    * partition() method provided by M.Green, modified for StudentRecords
    *
    *
   **********************************************************************
   */
      
   public int partition( Student partArray[], int left, int right ) {
   	
      int position = left; // position of target element

      // continue partition until element in correct position
      while ( true ) {

          // traverse array from right until lastName value of string1 is greater than target string
          while ( ( partArray[ position ].getLastName().compareTo( partArray[ right ].getLastName() ) <= 0 ) && position != right ) {
            right--;
          }

         // element at correct position
         if ( position == right )
            return position;

         // swap first element less than target
         if ( partArray[ position ].getLastName().compareTo(partArray[ right ].getLastName()) > 0 ) {
            swap( partArray, position, right );
            position = right;
           }

         // traverse array from left until lastName greater than target met
         while (( partArray[ left ].getLastName().compareTo(partArray[ position].getLastName()) <= 0 ) && left != position ) {
               ++left;
         }
         // element at correct position
         if ( position == left )
            return position;

         // swap first element greater than target
         if ( partArray[ left ].getLastName().compareTo(partArray[ position ].getLastName()) > 0) {
            swap( partArray, position, left );
            position = left;
         }

      }  // end while

   }  // end method partition

   /**********************************************************************
    * swap() method provided by M.Green, modified for StudentRecords
    * swaps two students in the array 
    *
    *
    **********************************************************************
   */
    
   public void swap( Student array[], int position, int rightLeft ) {

       Student temp = array [position];
       array[ position ] = array[ rightLeft ];
       array[ rightLeft ] = temp;
       }
  

   /**********************************************************************
    * main() method instantiates new StudentRecords class
    **********************************************************************
   */

   public static void main( String args[] ) {
      StudentRecords application = new StudentRecords();
      application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      } //main method 
  
  
  
   /*************************************************************************
    * ButtonHandler is the handler code that's tied to the listeners on the
    * two buttons.
    * 
    * DisplayAllStudentsButton must sort the Student objects in the array 
    * of references.
    * 
    * AddStudentButton validates the field entries before displaying the content
    * and adding to the file. 
    *
    *************************************************************************
  */
  
   private class ButtonHandler implements ActionListener {
     public String fNameStringUpper, fNameStringLower, lNameStringUpper, lNameStringLower;
     public String initCapTemp, fNameSansCap, lNameSansCap;
 	 
   	  
  	 public void actionPerformed(ActionEvent event) {	  	  

 	  	
     if (event.getSource() == addStudentButton) {
  		 outputArea.setText("");
             
         if ((textFirstName.getText().trim().length() == 0) &&
       	       (textLastName.getText().trim().length() == 0) &&
        	    (textID.getText().trim().length() == 0) &&
        	      (textGpa.getText().trim().length() == 0))  {
        	  outputArea.setText("\nPlease enter valid student information in all fields.");
        	  firstNameBlank=true;
        	  lastNameBlank=true;
        	  studentIDBlank=true;
        	  gpaBlank=true; 
        	 }

         // big "else" covers lots of different types of text input issues. Some of these
         // perhaps could be taken care of by try-catch blocks?
         else {

           // firstName text input validation
           if (textFirstName.getText().trim().length() == 0) {
     		   firstNameBlank = true;
               outputArea.append("\nPlease enter a valid first name or " + 
                                    "initial--don't leave blank."); 
                }
		   else {
			   firstNameBlank = false;
			   if (!textFirstName.getText().trim().matches("[a-zA-Z]*([ a-zA-Z]+)")) {
      	           junkFirstName = true;
      	           outputArea.append("\nFirst name entry should contain letters only. Please try again. ");
       	           }
      	       else {
                  junkFirstName = false;
                  fNameStringLower = textFirstName.getText().toLowerCase().trim();
      	          }
         	  } //closing brace else for routine firstName
           
           // lastName text validation
		   if (textLastName.getText().trim().length() == 0) {
               lastNameBlank = true;
               outputArea.append("\nPlease enter a valid last name--do not leave blank. "); 
      	       }
		   else {
			   lastNameBlank = false;
     		   if (!textLastName.getText().trim().matches( "[a-zA-z]+([ '-][a-zA-Z]+)*" )) {
			      junkLastName = true;
      	          outputArea.append("\nLast name field should contain letters only. Please try again. ");
       	          }
       	       else {
       	       	 junkLastName = false;
			   // Come back to this statement later and fix.. don't change toLowerCase if
			   // the letters already are in upper and lower case (eg, McCartney), look for 
			   // inner-caps (eg, LaCroix), and so on
			    
       	     	 lNameStringLower = textLastName.getText().toLowerCase().trim();
     	         }
			  } //closing brace else validation routine lastName
			  
           
           // studentID text input validation
           if (textID.getText().trim().length()==0) {
	             studentIDBlank = true;
                 outputArea.append("\nPlease enter a valid Student ID (nnn-nn-nnnn). Don't leave blank. "); 
      	         }
      	   else {
      	        studentIDBlank = false;
			    if (!textID.getText().trim().matches("\\d{3}-\\d{2}-\\d{4}")) {
 			       junkStudentID = true;
 			       outputArea.append("\nStudentID should be in the format nnn-nn-nnnn. Please try again. ");  
			        }
 			     else
 			        junkStudentID = false;
 			     }       
		   // gpa text input validation  
      	   if (textGpa.getText().trim().length() == 0) {
	           gpaBlank = true;
       	       outputArea.append("\nPlease enter the student's grade point average (n, or n.nn). Don't leave blank. "); 
      	       }    
		   else {
			   gpaBlank = false;
			   if (!textGpa.getText().trim().matches( "[0-4]\\.*\\d*" ) ) {
				   junkGpa=true;
				   outputArea.append("\nGPA must be in the range of 0 through 4.00. Please try again. ");  
				   }
			   else
				   junkGpa = false;
			   }
         } // closing brace for big else that handles all text input validation (assuming all fields have 
           // some entered data
			   
   	/****************************************************************************
   	 * Assuming we've passed all checks in the nested if/else statements  above, 
     * we should now have neither garbage (nor empty) text fields, so we can now go ahead 
     * and add the student to the student data file [student.dat]. I'm saving the 
     * first name and last names with initial caps; however, the logic (below) doesn't 
     * account for some naming nuances (for example, "O'Malley," "LaCroix," or 
     * "Mary Lou," although these are allowed)--but I've fiddled far too long with 
     * this section of the code, so I'll leave such subtleties for future improvement. 
     ******************************************************************************
    */

        if ( (!firstNameBlank) && (!lastNameBlank)&& (!studentIDBlank) && (!gpaBlank) &&
                  (!junkFirstName) && (!junkLastName) && (!junkStudentID) && (!junkGpa))  {
       	   fNameStringUpper = fNameStringLower.toUpperCase();
      	   fNameSansCap = fNameStringLower.substring(1);
           initCapTemp = fNameStringUpper.substring(0, 1);
           firstName = initCapTemp.concat(fNameSansCap);
           lNameStringUpper = lNameStringLower.toUpperCase();
      	   lNameSansCap = lNameStringLower.substring(1);
           initCapTemp = lNameStringUpper.substring(0, 1);
           lastName = initCapTemp.concat(lNameSansCap);
           studentID = textID.getText().trim();
           gpa = Double.parseDouble(textGpa.getText().trim());

           //create a Student object
           Student tempStudentRef = new Student(firstName, lastName, studentID, gpa);

           // display the student info 
           outputArea.setText(lastName + " " + firstName + " " + studentID + " " + gpa);
    
           //add the student to the student.dat file
           try {
          	  File file = new File (filename);
              PrintWriter pw = new PrintWriter(new FileWriter(file, true));
              pw.print(lastName + ":" + firstName + ":" + studentID + ":" + gpa);
              pw.println();
              pw.close();
             }
           catch(IOException exception){
              outputArea.setText("\nProblem creating the file. Here's what Java tells us: " + exception);
              }
           //clear the input fields  
           textFirstName.setText("");
           textLastName.setText("");
           textID.setText("");
           textGpa.setText("");
       }// closing brace, if statement, executes only if all validation checks have been met
  
  } // closing brace for addStudentButton-event-source
   	    
   	/*********************************************************************
   	 * displayAllStudentsButton event handler creates an array of references
   	 * to Student objects after first obtaining a reference to the student.dat
   	 * file, opening the file, finding out the size (by counting lines in the
   	 * file), then iterating through the lines of data to initialize the 
   	 * Student objects...
   	 *
   	 *********************************************************************
   	*/

     if (event.getSource() == displayAllStudentsButton) {  	    	
  		 outputArea.setText("");
//  		 System.out.println("begining of the button handler processing...");   //debug
  		 
  		 // find out how many records are in the file by opening the file and
  		 // iterating through the lines, adding to a counter; then close the file
  		 try {
  	       	array_size = 0;
  	       	FileReader fr = new FileReader(filename);
  	       	BufferedReader inFile = new BufferedReader(fr);
  	       	line = inFile.readLine();
	  //		System.out.println("obtain the array_size ...");   //debug
            
            while (line != null) {
  	       	    line = inFile.readLine();
  	       		array_size++;
//	  		    System.out.println("obtain the array_size ..." + array_size);   //debug
  	            }
  	      	//close the file
  		    inFile.close();
           
           } //closing brace, try
           
   		 catch (FileNotFoundException exception) {
  	       	 outputArea.setText("\nThe " + filename + " file cannot be found. Additional detail (if any): " + exception);
  	         }
         catch (IOException exception) {
  	       	 outputArea.setText("\nProblem with input/output on the file. Additional detail (if any): " + exception);
  	         }
          
         Student student[] = new Student[array_size];
         try {
             array_ctr=0;
             FileReader reader = new FileReader(filename);
             BufferedReader studentFile = new BufferedReader(reader);
         //    System.out.println("inside the try{} block, after creating the array of students " + student);   //debug
        	 studentData = studentFile.readLine();
            
             // Obtain the data elements as string tokens
             while (studentData !=null) {
                tokenizer = new StringTokenizer (studentData, ":");
                lastName = tokenizer.nextToken();
                firstName = tokenizer.nextToken();
         	    studentID = tokenizer.nextToken();
         	    try {
         	      gpa = Double.parseDouble(tokenizer.nextToken());
         	      }
         	    catch (NumberFormatException exception) {
         	      gpa = 0.00;
         	      }
                //populate the array of Student references using the data we've just
                //extracted as tokens from the file
                student[array_ctr] = new Student (lastName, firstName, studentID, gpa);
                studentData = studentFile.readLine();    
                array_ctr++;
             } //while (studentData!=null)

             // The Student objects now exist, so we can close the file
             studentFile.close();
 
             } //try
   		 catch (FileNotFoundException exception) {
  	       	 outputArea.append("\nThe " + filename + " file cannot be found. Additional detail (if any): " + exception);
  	         }
         catch (IOException exception) {
  	       	 outputArea.append("\nProblem with input/output on the file. Additional detail: " + exception);
  	         }
         catch (NumberFormatException exception) {
         		outputArea.append("\nError in input.  Line ignored");
      		 }
          
        /************************************************************************
         * Sort the contents of the array and output the student info
         *
         ************************************************************************
         */
          
        // send the array of Student references to the quickSort method, along with
        // the first position and ending position

        if (student.length > 0) {
           	 quickSort(student, 0, student.length-1);
        	 
        	 for (int ctr = 0; ctr < student.length; ctr++) {
        	     outputArea.append("\n" + student[ctr].toString());
             //    System.out.println("show student toString() " + student[ctr].toString());   //debug
           	 
        	    }
             }
        
        else {
          	outputArea.append("\nSorry, the student.dat file is completely empty. There are no students.");
            }
        
        } //closing brace for event--displayAllButton handler

     }  //closing brace for actionPerformed
 
   } //closing brace for ButtonHandler inner class

} // closing brace for class StudentRecords