/*
 * KioskStatsAnalysis.java
 *
 * Created on January 28, 2004, 1:33 PM
 */

package PER.exhibit;

import java.io.*;
import java.text.DateFormat;
import java.util.Date;
import java.sql.Time;
import java.util.GregorianCalendar;
import java.util.Calendar;
import java.text.SimpleDateFormat;
import java.lang.reflect.*;
import java.util.Vector;

/**
 * Gathers statistical information from the StatsLog output files.
 *
 * @author Emily Hamner
 */

public class KioskStatsAnalysis extends javax.swing.JFrame {
    private long MS_PER_SECOND = 1000;
    private long MS_PER_MINUTE = MS_PER_SECOND * 60;
    private long MS_PER_HOUR = MS_PER_MINUTE * 60;
    private long MS_PER_DAY = MS_PER_HOUR * 24;
    private long MS_PER_MONTH = MS_PER_DAY * 30;
    private long MS_PER_YEAR = MS_PER_MONTH * 12;
    
    private final javax.swing.JFileChooser fileChooser;
    private java.io.File inputFile;
    private java.io.File summaryOutputFile;
    private java.io.File fullOutputFile;
    private long attractTime; //total time in attract loop (in milliseconds) (includes screen timeouts also)
    private long missionTime; //total time engaged in mission (in millisecons)
    private long timeoutTime; //total time idle before timeout occured (in milliseconds). This assumes 180 seconds for mission central timeout.
    private int numMissions; //number of missions that contribute to the mission time
    private int numLoops; //number of times in attract loop that contribute to the attract time
    private int numTimeoutsMC; //number of times mission central timed out
    private int numTimeoutsPOV; //number of times the rover POV screen timed out (someone did not hit try again or quit)
    private int numTryAgain; //number of times the try again button was pushed
    private int numQuit; //number of times the quit button was pushed
    private DateFormat df; //date format used to read timestamps
    private SimpleDateFormat sdf; //date format used to print time lengths
    private long offset; //timezone offset in milliseconds
    Vector ml; //mission lengths
    private PrintStream summaryOutputStream;
    private PrintStream fullOutputStream;
    
    /** Creates new form KioskStatsAnalysis */
    public KioskStatsAnalysis() {
        initComponents();
        
        attractTime = 0;
        missionTime = 0;
        timeoutTime = 0;
        numMissions = 0;
        numLoops = 0;
        numTimeoutsMC = 0;
        numTimeoutsPOV = 0;
        ml = new Vector();
        summaryOutputStream = null;
        fullOutputStream = null;
        
        //create file chooser for input and output files
        fileChooser = new javax.swing.JFileChooser("c:/");
        //filter for only text files
        fileChooser.setAcceptAllFileFilterUsed(false);
        fileChooser.setFileFilter(new PER.rover.Filter(PER.rover.Filter.TEXT_FILTER));
        
        //date format
        df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM);
        //sdf = new SimpleDateFormat("hh:mm:ss");
        sdf = new SimpleDateFormat("y:MM:dd:HH:mm:ss");
        
        Calendar now = Calendar.getInstance();
        offset = now.get(Calendar.ZONE_OFFSET) + now.get(Calendar.DST_OFFSET);
    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    private void initComponents() {//GEN-BEGIN:initComponents
        java.awt.GridBagConstraints gridBagConstraints;

        jLabel1 = new javax.swing.JLabel();
        inputTextField = new javax.swing.JTextField();
        jLabel2 = new javax.swing.JLabel();
        outputTextField = new javax.swing.JTextField();
        inputBrowseButton = new javax.swing.JButton();
        outputBrowseButton = new javax.swing.JButton();
        analyzeButton = new javax.swing.JButton();
        jLabel3 = new javax.swing.JLabel();
        fullOutputTextField = new javax.swing.JTextField();
        fullOutputBrowseButton = new javax.swing.JButton();

        getContentPane().setLayout(new java.awt.GridBagLayout());

        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                exitForm(evt);
            }
        });

        jLabel1.setText("Kiosk stats file");
        getContentPane().add(jLabel1, new java.awt.GridBagConstraints());

        inputTextField.setMinimumSize(new java.awt.Dimension(200, 20));
        inputTextField.setPreferredSize(new java.awt.Dimension(200, 20));
        getContentPane().add(inputTextField, new java.awt.GridBagConstraints());

        jLabel2.setText("Summary output file");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        getContentPane().add(jLabel2, gridBagConstraints);

        outputTextField.setPreferredSize(new java.awt.Dimension(200, 20));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        getContentPane().add(outputTextField, gridBagConstraints);

        inputBrowseButton.setText("Browse");
        inputBrowseButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                inputBrowseButtonActionPerformed(evt);
            }
        });

        getContentPane().add(inputBrowseButton, new java.awt.GridBagConstraints());

        outputBrowseButton.setText("Browse");
        outputBrowseButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                outputBrowseButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        getContentPane().add(outputBrowseButton, gridBagConstraints);

        analyzeButton.setText("Analyze");
        analyzeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                analyzeButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 3;
        getContentPane().add(analyzeButton, gridBagConstraints);

        jLabel3.setText("Full output file");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        getContentPane().add(jLabel3, gridBagConstraints);

        fullOutputTextField.setPreferredSize(new java.awt.Dimension(200, 20));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        getContentPane().add(fullOutputTextField, gridBagConstraints);

        fullOutputBrowseButton.setText("Browse");
        fullOutputBrowseButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                fullOutputBrowseButtonActionPerformed(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 2;
        getContentPane().add(fullOutputBrowseButton, gridBagConstraints);

        pack();
    }//GEN-END:initComponents
    
    private void fullOutputBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fullOutputBrowseButtonActionPerformed
        int returnVal = fileChooser.showOpenDialog(null);
        try{
            if (returnVal == javax.swing.JFileChooser.APPROVE_OPTION) {
                fullOutputFile = fileChooser.getSelectedFile();
                //display file name
                fullOutputTextField.setText(fullOutputFile.getName());
                
                FileOutputStream fos = new FileOutputStream(fullOutputFile, true);
                fullOutputStream = new PrintStream(fos, true);
                fullOutputStream.println("input file, type, length, length in secs, start date, end date");
            }else
                fullOutputStream = null;
        } catch (Exception e) {
            fullOutputStream = null;
        }
    }//GEN-LAST:event_fullOutputBrowseButtonActionPerformed
    
    private void analyzeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_analyzeButtonActionPerformed
        try{
            //open the file.
            FileReader in = new FileReader(inputFile);
            StreamTokenizer st = new StreamTokenizer(in);
            st.parseNumbers();
            st.eolIsSignificant(true);
            st.ordinaryChar('\u002f');
            
            //reset variables
            attractTime = 0;
            missionTime = 0;
            timeoutTime = 0;
            numMissions = 0;
            numLoops = 0;
            numTimeoutsMC = 0;
            numTimeoutsPOV = 0;
            numTryAgain = 0;
            numQuit = 0;
            ml = new Vector();
            
            int column = 1; //track current column
            Date lastLoopStart = new Date(); //last time attract loop started
            lastLoopStart.setTime(0);
            Date lastMissionStart = new Date(); //last time receiving panorama started
            lastMissionStart.setTime(0);
            long loopTime; //temporary variable
            Date endDate = new Date(); //temporary variable
            endDate.setTime(0);
            
            while(st.nextToken() != st.TT_EOF){
                if(st.ttype == st.TT_WORD){
                    //ignore titles
                }else if(st.ttype == st.TT_NUMBER){
                    switch (column){
                        case 1:
                            //log initialized
                            //reset values
                            lastMissionStart.setTime(0);
                            lastLoopStart.setTime(0);
                            break; //? why was this not here before?
                        case 2:
                            //Start Attract
                            if(lastMissionStart.getTime() != 0){
                                //end of last mission and start of new attract loop
                                endDate = parseTimeStamp(st);
                                addMission(lastMissionStart,endDate);
                                lastMissionStart.setTime(0);
                                lastLoopStart = endDate;
                            }else{
                                //start of new attract loop
                                lastLoopStart = parseTimeStamp(st);
                            }
                            break;
                        case 3:
                            //Stop Attract
                            if(lastLoopStart.getTime() != 0){
                                endDate = parseTimeStamp(st);
                                loopTime = endDate.getTime() - lastLoopStart.getTime();
                                if(loopTime > MS_PER_HOUR){
                                    println("Attract Loop time is > 1 hour. This loop will not be included in the summary stats.");
                                    println("     Start time   = "+lastLoopStart+".");
                                    println("     End time     = "+endDate+".");
                                    println("     Elapsed Time = "+format(loopTime));
                                }else{
                                    attractTime += loopTime;
                                    numLoops ++;
                                }
                                fullOutputprintln("loop",loopTime,lastLoopStart,endDate);
                                lastLoopStart.setTime(0);
                            }
                            break;
                        case 4:
                            //Start receiving panorama
                            if(lastMissionStart.getTime() != 0){
                                //end of last mission and start of new mission
                                endDate = parseTimeStamp(st);
                                addMission(lastMissionStart,endDate);
                                lastMissionStart = endDate;
                            }else{
                                //start of a new mission
                                lastMissionStart = parseTimeStamp(st);
                            }
                            break;
                        /*case 9:
                            //stop rover POV screen
                            if(lastMissionStart.getTime() != 0){
                                endDate = parseTimeStamp(st);
                                addMission(lastMissionStart,endDate);
                                lastMissionStart.setTime(0);
                            }
                            break;
                         */
                        case 10:
                            //try again button pushed
                            if(lastMissionStart.getTime() != 0){
                                parseTimeStamp(st);
                                numTryAgain++;
                            }
                            break;
                        case 11:
                            //quit button pushed
                            if(lastMissionStart.getTime() != 0){
                                parseTimeStamp(st);
                                numQuit++;
                            }
                            break;
                        case 12:
                            //mission central timeout
                            if(lastMissionStart.getTime() != 0){
                                endDate = parseTimeStamp(st);
                                Date lastTouched = new Date(endDate.getTime()-180000); //account for timeout
                                addMission(lastMissionStart,lastTouched);
                                lastMissionStart.setTime(0);
                                
                                timeoutTime += 180000; //assume 180 seconds
                                numTimeoutsMC++;
                                fullOutputprintln("MCtimeout",180000,lastTouched,endDate);
                            }
                            break;
                        case 13:
                            //rover POV screen timeout
                            if(lastMissionStart.getTime() != 0){
                                endDate = parseTimeStamp(st);
                                Date lastTouched = new Date(endDate.getTime()-60000); //account for timeout
                                addMission(lastMissionStart,lastTouched);
                                lastMissionStart.setTime(0);
                                
                                timeoutTime += 60000;
                                numTimeoutsPOV++;
                                fullOutputprintln("POVtimeout",60000,lastTouched,endDate);
                            }
                            break;
                        case 49:
                            //fatal error
                            if(lastMissionStart.getTime() != 0){
                                endDate = parseTimeStamp(st);
                                addMission(lastMissionStart,endDate);
                                lastMissionStart.setTime(0);
                            }
                            break;
                        case 50:
                            //program closed
                            if(lastMissionStart.getTime() != 0){
                                endDate = parseTimeStamp(st);
                                addMission(lastMissionStart,endDate);
                                lastMissionStart.setTime(0);
                            }
                            break;
                    }
                }else if(st.ttype == st.TT_EOL){
                    //reset column
                    column = 1;
                }else {
                    if((char)st.ttype == ','){
                        column ++;
                    }else{
                        //System.out.println("character "+(char)st.ttype);
                    }
                }
            }
        }catch (java.io.IOException e){
            javax.swing.JOptionPane.showMessageDialog(this,
            "Error reading the file "+inputFile.getName()+".\n"
            +e+"\n",
            "Error", javax.swing.JOptionPane.ERROR_MESSAGE);
        }
        
        
        println("Input file: "+inputFile.getName());
        println("total attractTime = "+format(attractTime));
        println("total missionTime = "+format(missionTime));
        println("total timeoutTime = "+format(timeoutTime));
        println("num attract loops = "+numLoops);
        println("numMissions = "+numMissions);
        println("num mission central timouts (3 minutes) = "+numTimeoutsMC);
        println("num roverPOV timouts (1 minute) = "+numTimeoutsPOV);
        println("average mission length = "+missionLengthAvg()/1000+" secs");
        println("mission length standard deviation = "+missionLengthSD()/1000+" secs");
        println("num times try again button pushed = "+numTryAgain);
        println("num times quit button pushed = "+numQuit);
        println("");
    }//GEN-LAST:event_analyzeButtonActionPerformed
    
    /** Prints the string to the output file and to System.out.
     */
    private void println(String s){
        System.out.println(s);
        if(summaryOutputStream != null)
            summaryOutputStream.println(s);
    }
    
    /** Prints a line to the fullOutputFile if there is one.
     *
     *@param s "loop" or "mission"
     *@param length The loop or mission length
     *@param startDate The time when the loop or mission started
     *@param endDate The time when the loop or mission ended
     */
    private void fullOutputprintln(String s,long length,Date startDate,Date endDate){
        if(fullOutputStream != null){
            fullOutputStream.println(
            inputFile.getName()+","
            +s+","
            +format(length)+","
            +length/1000+","
            +startDate+","
            +endDate
            );
        }
    }
    
    /** Calculates the mission length from the startDate and endDate,
     * adds the mission length to the total mission time, increments the number
     * of missions, and stores the mission length in the ml vector. Also prints
     * a line to the fullOutputFile if there is one.
     */
    private void addMission(Date startDate, Date endDate){
        long missionLength = endDate.getTime() - startDate.getTime();
        missionTime += missionLength;
        numMissions ++;
        ml.add(new Long(missionLength));
        fullOutputprintln("mission",missionLength,startDate,endDate);
    }
    
    /** Takes a time in milliseconds and returns a string of the format
     * hh:mm:ss.
     */
    private String format(long time){
        String str = "";
        /*//years
        int years = (int)(time / MS_PER_YEAR);
        time %= MS_PER_YEAR;
        //months
        int months = (int)(time / MS_PER_MONTH);
        time %= MS_PER_MONTH;
        //days
        int days = (int)(time / MS_PER_DAY);
        time %= MS_PER_DAY;
         */
        //hours
        int hours = (int)(time / MS_PER_HOUR);
        time %= MS_PER_HOUR;
        //minutes
        int minutes = (int)(time / MS_PER_MINUTE);
        time %= MS_PER_MINUTE;
        //seconds
        int seconds = (int)(time / MS_PER_SECOND);
        
        /*if(years > 0)
            str += years + ":";
        if(years > 0 || months > 0)
            str += months + ":";
        if(years > 0 || months > 0 || days > 0)
            str += days + ":";
         */
        if(hours < 10)
            str += "0" + hours + ":";
        else
            str += hours + ":";
        if(minutes < 10)
            str += "0" + minutes + ":";
        else
            str += minutes + ":";
        if(seconds < 10)
            str += "0" + seconds;
        else
            str += seconds;
        return str;
    }
    
    /** Calculates and returns the average mission length (in milliseconds).
     */
    private double missionLengthAvg(){
        return missionTime / numMissions;
    }
    
    /** Calculates and returns the mission length standard deviation (in milliseconds).
     */
    private double missionLengthSD(){
        double sd = 0;
        double avg = missionLengthAvg();
        double temp;
        for(int i=0; i < ml.size(); i++){
            temp = ((Long)ml.get(i)).longValue() - avg;
            sd += temp * temp;
        }
        sd /= (ml.size()-1);
        sd = Math.sqrt(sd);
        return sd;
    }
    
    private void outputBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_outputBrowseButtonActionPerformed
        int returnVal = fileChooser.showOpenDialog(null);
        try{
            if (returnVal == javax.swing.JFileChooser.APPROVE_OPTION) {
                summaryOutputFile = fileChooser.getSelectedFile();
                //display file name
                outputTextField.setText(summaryOutputFile.getName());
                
                FileOutputStream fos = new FileOutputStream(summaryOutputFile, true);
                summaryOutputStream = new PrintStream(fos, true);
            }else
                summaryOutputStream = null;
        } catch (Exception e) {
            summaryOutputStream = null;
        }
    }//GEN-LAST:event_outputBrowseButtonActionPerformed
    
    private void inputBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_inputBrowseButtonActionPerformed
        int returnVal = fileChooser.showOpenDialog(null);
        
        if (returnVal == javax.swing.JFileChooser.APPROVE_OPTION) {
            inputFile = fileChooser.getSelectedFile();
            //display file name
            inputTextField.setText(inputFile.getName());
        } //else Open command cancelled by user.
    }//GEN-LAST:event_inputBrowseButtonActionPerformed
    
    /** Returns the time of day in milliseconds, with 0 being 12:00 AM.
     * Assumes the first number in the date has already been read in from
     * the stream tokenizer and ignores the rest of the date.
     */
   /* private long parseTimeStamp(StreamTokenizer st){
        long timeOfDay = 0;
        try{
    
            //read in rest of date
            st.nextToken();
            st.nextToken();
            st.nextToken();
            st.nextToken();
    
            //hour
            st.nextToken();
            System.out.println("hour "+st.nval);
            timeOfDay = timeOfDay + (int)st.nval * 3600000;
            st.nextToken(); //:
            //minute
            st.nextToken();
            System.out.println("minute "+st.nval);
            timeOfDay = timeOfDay + (int)st.nval * 60000;
            st.nextToken(); //:
            //seconds
            st.nextToken();
            System.out.println("seconds "+st.nval);
            timeOfDay = timeOfDay + (int)st.nval * 1000;
            //am/pm
            st.nextToken();
            System.out.println(" "+st.sval);
            if(st.sval.equals("PM")){
                timeOfDay = timeOfDay + 12 * 3600000;
            }
        }catch (java.io.IOException e){
            javax.swing.JOptionPane.showMessageDialog(this,
            "Error reading the file "+inputFile.getName()+".\n"
            +e+"\n",
            "Error", javax.swing.JOptionPane.ERROR_MESSAGE);
        }
    
        return timeOfDay;
    }
    */
    private Date parseTimeStamp(StreamTokenizer st){
        String s = Integer.toString((int)st.nval); //month
        
        try{
            //read in rest of date
            st.nextToken(); // '/'
            s += (char)st.ttype;
            st.nextToken(); //date
            s += Integer.toString((int)st.nval);
            st.nextToken(); // '/'
            s += (char)st.ttype;
            st.nextToken(); //year
            s += "200";
            s += Integer.toString((int)st.nval);
            s += " "; //space
            st.nextToken(); //hour
            s += Integer.toString((int)st.nval);
            st.nextToken(); // ':'
            s += (char)st.ttype;
            st.nextToken(); //minute
            s += Integer.toString((int)st.nval);
            st.nextToken(); // ':'
            s += (char)st.ttype;
            st.nextToken(); //seconds
            s += Integer.toString((int)st.nval);
            s += " "; //space
            st.nextToken(); //am/pm
            s += st.sval;
            //System.out.println(s);
            
        }catch (java.io.IOException e){
            javax.swing.JOptionPane.showMessageDialog(this,
            "Error reading the file "+inputFile.getName()+".\n"
            +e+"\n",
            "Error", javax.swing.JOptionPane.ERROR_MESSAGE);
            
        }
        try{
            return df.parse(s);
        }catch (java.text.ParseException e){
            System.out.println("ParseException "+e);
        }
        System.exit(0);
        return null;
    }
    
    /** Exit the Application */
    private void exitForm(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_exitForm
        System.exit(0);
    }//GEN-LAST:event_exitForm
    
    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        new KioskStatsAnalysis().show();
    }
    
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton analyzeButton;
    private javax.swing.JButton fullOutputBrowseButton;
    private javax.swing.JTextField fullOutputTextField;
    private javax.swing.JButton inputBrowseButton;
    private javax.swing.JTextField inputTextField;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JButton outputBrowseButton;
    private javax.swing.JTextField outputTextField;
    // End of variables declaration//GEN-END:variables
    
}
