package edu.columbia.cs.cs1007.roster;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.TreeSet;

/**
 * This class reads in a student roster from a file, and sorts the 
 * entries by uni, major, or year.  Sorted results are written
 * to another file, and some simple statistics about the roster
 * are displayed on the screen.
 
 @author Julia Stoyanovich (jds2109@columbia.edu)
 * COMS 1007, Summer 2009
 *
 */
public class Roster {
  
  private static enum SORT_ORDER {UNI, MAJOR, YEAR}
    
  // students are kept sorted on uni in the natural order
  private TreeSet<Student> _students; 
  
  // input and output file handles
  private FileReader _inFP;
  private FileWriter _outFP;
  
  // sorting options
  private SORT_ORDER _sort;
  private boolean _reverseSortOrder = false;
  
  // maximum number of students to read from the file
  private int _numStudents = Integer.MAX_VALUE;
  
  /**
   * Constructor.
   */
  public Roster() {
    _students = new TreeSet<Student>();  
  }
  
  /**
   * Parsing of command line arguments.
   @param args -i inFileName -o outFileName -s|r uni|major|year [-n numStudents]
   @throws FileNotFoundException
   @throws IOException
   @throws IllegalArgumentException
   @throws NumberFormatException
   */
  public void parseArguments(String[] argsthrows 
                    FileNotFoundException, 
                    IOException,
                    IllegalArgumentException,
                    NumberFormatException {
    
    // arguments: -i inFileName -o outFileName -s|r uni|major|year [-n numStudents]
    
    // we expect an even number of arguments: command followed by parameter
    if (args.length %!= 0) {
      throw new IllegalArgumentException("Invalid number of arguments.");
    }
    
    for (int i=0; i<args.length; i+=2) {
      // a command always has length 2: "-" followed by a letter
      if (args[i].length() != 2) {
        throw new IllegalArgumentException("Not a valid command option " + args[i]);
      }
      
      char command = args[i].charAt(1);
      String param = args[i+1].trim();
      
      switch (command) {
        case 'i'// input file name
          _inFP = new FileReader(param);
          break;
        case 'o'// output file name
          _outFP = new FileWriter(param);
          break;
        case 's'// sort in the natural order
          _sort = SORT_ORDER.valueOf(param.toUpperCase());
          _reverseSortOrder = false;
          break;
        case 'r'// sort in reverse order
          _sort = SORT_ORDER.valueOf(param.toUpperCase());          
          _reverseSortOrder = true;
          break;
        case 'n'// max number of students to read in
          _numStudents = Integer.parseInt(param);
          if (_numStudents <= 0) {
            throw new IllegalArgumentException("Number of students must be positive.");
          }
          break;
        default:
          throw new IllegalArgumentException("Not a valid command option -" + command);
          // note no "break;" here, since if we got here -- an exception is thrown, 
          // and the next line is unreachable.
      }
    }
    // make sure that the required parameters are set
    if (_inFP == null) {
      throw new IllegalArgumentException("No input file specified.");
    }
    if (_outFP == null) {
      throw new IllegalArgumentException("No output file specified.");
    }
    if (_sort == null) {
      throw new IllegalArgumentException("No sort order specified.");
    }
  }
  
  /**
   * Return the usage string.
   @return Usage: Roster -i inFileName -o outFileName -s|r uni|major|year [-n numStudents]
   */
  public String printUsage() {
    // input and output files and sort order (either s or r) are required, 
    // number of students is optional
    return "Usage: Roster -i inFileName -o outFileName -s|r uni|major|year [-n numStudents]";
  }

  /**
   * Read and parse the input file, initializing the _students data structure.
   */
  public void readInput() {
    
    // open a scanner on the input file
    Scanner scanner = new Scanner(_inFP);
    int numProcessedLines=0;
    
    // what is the current year?
    Calendar rightNow = Calendar.getInstance();
    int currentYear = rightNow.get(Calendar.YEAR);
    
    while (scanner.hasNextLine() && (numProcessedLines < _numStudents)) {
      
      String line = scanner.nextLine();
      try {
        // lines are expected to be of the form:uni,name,gradYear,major        
        String[] values = line.split(",");
        
        if (values.length < 4) {
          throw new InputMismatchException("Not enough values");
        }
        // parse the four fields
        String name = values[0];        
        String uni = values[1];
        int gradYear = Integer.parseInt(values[2].trim());
        String major = values[3];
        
        // note use of our exception type, IllegalGradYearException
        if (gradYear < currentYear) {
          throw new IllegalGradYearException();
        }
        
        // add the student to the TreeSet
        _students.add(new Student(uni, name, gradYear, major));
        numProcessedLines++;
        
      catch (InputMismatchException ime) {
        System.out.println("Invalid line: " + line);
        
      catch (IllegalGradYearException igye) {
        System.out.println(igye.getMessage() "\t" + line);
      
      catch (NumberFormatException nfe) {
        System.out.println(nfe.getMessage());
      }
    }
    // close the open scanner
    scanner.close();
  }
  
  /**
   * Sort students on uni, major, or graduation year, 
   *   in the requested sort order.
   @return a sorted Student[] 
   */
  public Student[] sort() {
    
    Student[] sortedStudents;

    if ((_sort == SORT_ORDER.UNI&& _reverseSortOrder) {
      // sort on UNI in reverse order
      sortedStudents = _students.descendingSet().toArray(new Student[0]);
    else {
      // sort on UNI in natural order
      sortedStudents = _students.toArray(new Student[0])
    }
    
    if (_sort == SORT_ORDER.YEAR) {

      // an inner class that implements a comparator on grad. year 
      class YearComparator implements Comparator<Student> {
        
        public int compare(Student one, Student another) {
          int cmp=0;
          if (one.getGradYear() < another.getGradYear()) {
            cmp = -1;
          else if (one.getGradYear() > another.getGradYear()) {
            cmp = 1;
          }
          return cmp;
        }
      }
      // instantiate the comparator
      YearComparator comparator = new YearComparator();

      // note that sorting of sortedStudents happens in place!
      if (!_reverseSortOrder) {
        // sort on year in the natural order
        Arrays.sort(sortedStudents, comparator)
      else {
        // sort on year in the reverse order
        Arrays.sort(sortedStudents, Collections.reverseOrder(comparator));         
      }
      
    else if (_sort == SORT_ORDER.MAJOR) {
      
      // an inner class that implements a comparator on major 
      class MajorComparator implements Comparator<Student> {  
        public int compare(Student one, Student another) {
          return one.getMajor().compareTo(another.getMajor());
        }
      }

      // instantiate the comparator
      MajorComparator comparator = new MajorComparator();  
      if (!_reverseSortOrder) {
        // sort in the natural order on major
        Arrays.sort(sortedStudents, comparator)
      else {
        // sort in reverse order on major
        Arrays.sort(sortedStudents, Collections.reverseOrder(comparator));         
      }
    }
    return sortedStudents;
  }
  
  /**
   * Return some simple statistics, as a string.
   @return number of student that were processed
   */
  public String printStatistics() {
    return "There are " + _students.size() " students";
  }

  /**
   * Output student data to an output file.
   @param students 
   @throws IOException
   */
  public void writeToFile(Student[] studentsthrows IOException {
    int i=0;
    for (Student student : students) {
      if (i>0) {
        _outFP.write("\n");
      }
      i++;
      _outFP.write(student.toString());
    }
  }
  
  /**
   * Close any open file pointers.  
   */
  public void closeFilePointers() {

    try {
      if (_inFP != null) {
        _inFP.close();
      }
    catch (IOException ioe) {
      // Note that the exception is suppressed.
      // Acceptable only in the finally{} block.
    }

    try {
      if (_outFP != null) {
        _outFP.close();
      }
    catch (IOException ioe) {}
  }

  public static void main(String[] args) {
    
    Roster roster = new Roster();    
    try {
      roster.parseArguments(args);
      
      roster.readInput();
      
      System.out.println(roster.printStatistics());
      
      Student[] sortedStudents = roster.sort();
      roster.writeToFile(sortedStudents);
      
      roster.closeFilePointers();
      
    catch (NumberFormatException nfe) {
      System.out.println("Invalid number: " + nfe.getMessage());
      System.out.println(roster.printUsage());
      
    catch (IllegalArgumentException iae) {
      System.out.println("You invoked the program in an illegal manner: " + iae.getMessage());
      System.out.println(roster.printUsage());
      
    catch (FileNotFoundException fnfe) {
      System.out.println("File not found: " + fnfe.getMessage());
      System.out.println(roster.printUsage());
      
    catch (IOException ioe) {
      System.out.println("IO Exception: " + ioe.getMessage());
      System.out.println(roster.printUsage());
      
    finally {
      roster.closeFilePointers();
    }
  }
}