/**
  * This program takes the output files in <time,sample> format
  * and plots it using java, for files which are too long
  * it takes optional parameters which include start time
  * and end time for the user convenience
  */
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;

/**
 * 
 * @author Raghu K. Ganti
 * @version 1.0
 *
 */

class Value
{
	/**
	 * The integer acceleration value associated with this sample
	 */
	private int accelValue;
	/**
	 * The double time value associated with this sample
	 */
	private double time;

	/**
	 * Initalize the object with a time (double) and
	 * an integer acceleration value (as obtained by the 10-bit acclerometer)
	 * 
	 * @param time A double time value for this sample
	 * @param accelValue An integer value that is the acceleration associated with this time
	 */
	public Value(double time, int accelValue)
	{
		this.accelValue = accelValue;
		this.time = time;	
	}

	/**
	 * This method returns the integer acceleration value
	 * 
	 * @return The integer acceleration value associated with this sample
	 */
	public int getValue()
	{
		return accelValue;
	}

	/**
	 * This method returns the double time value
	 * 
	 * @return The double time value associated with this sample
	 */
	public double getSampleTime()
	{
		return time;	
	}
}

public class Visualize extends JPanel implements ActionListener{
	/**
	 * The drawing panes, one for each mote
	 */
    private JPanel [] drawingPane;
    /**
     * The scroller bars, one for each drawing pane
     */
	private JScrollPane [] scroller;
	/**
	 * The corners used for ensuring the scroller bars are in place
	 * for each of the drwaing panes
	 */
	private JPanel [] buttonCorner;
	/**
	 * The main tabbed pane that contains all the drawing panes and the
	 * base input pane
	 */
	private JTabbedPane tabbedPane;
	/**
	 * The parent component that contains all other components
	 */
	private static JFrame frame;
	/**
	 * The file dialog for choosing configuration files
	 */
	private JFileChooser inputConfFileChooser;
	/**
	 * The file dialog for choosing one of the accelerometer files
	 */
	private JFileChooser baseFileChooser;
	/**
	 * The display button on the GUI
	 */
	private JButton displayButton;
	/**
	 * The button for accepting configuration file as input, results
	 * in the configuration file chooser dialog opening
	 */
	private JButton inputFileButton;
	/**
	 * The text area that shows the current status of the GUI
	 */
	private JTextArea displayTextArea;
	/**
	 * The button for accepting one of the accelerometer file, results
	 * in the acclerometer file chooser dialog opening
	 */
	private JButton inputButton;
	/**
	 * The button that resets the GUI to its initial state
	 */
	private JButton resetButton; 
	/**
	 * Boolean variable indicating if the configuration file has been
	 * chosen correctly
	 */
	private boolean fileCorrect = false;
	/**
	 * Boolean variable indicating if the accelerometer file has been
	 * chosen correctly
	 */
	private boolean baseFileChosen = false;
	
	/**
	 * The base file name (the one that is common among all accelerometer files)
	 */
	private String baseFileName = "";
	/**
	 * The directory consisting of the accelerometer files
	 */
	private String baseDirName = "";
	/**
	 * The file reader associated with the configuration file
	 */
	private FileReader confFileReader = null;
	/**
	 * The buffered reader associated with the configuration file
	 */
	private BufferedReader confBufferedReader = null;
	/**
	 * The number of motes, that is read from the configuration file
	 */
	private int numMotes = 0;
	/**
	 * The integer array that keeps the ids of the motes
	 */
	private int [] moteIds;
	
	/**
	 * The array of file readers that are associated with X accelerometer files
	 */
	private FileReader [] fileReadersX;
	/**
	 * The array of file readers that are associated with Y accelerometer files
	 */
	private FileReader [] fileReadersY;
	
	/**
	 * The array of buffered readers that are associated with X accelerometer files
	 */
	private BufferedReader [] bufferedReadersX;
	/**
	 * The array of buffered readers that are associated with Y accelerometer files
	 */
	private BufferedReader [] bufferedReadersY;

	/**
	 * The array of array lists that store the X acceleration values
	 */
	private ArrayList [] accelXValues;
	/**
	 * The array of array lists that store the Y acceleration values
	 */
	private ArrayList [] accelYValues;

	/**
	 * This method handles all the actions performed in the GUI, such
	 * as button clicks
	 * 
	 * @param e The default action performed event for the class that
	 * implements action listener
	 */
	public void actionPerformed(ActionEvent e)
	{
		File chosenFile = null;
		
		if (e.getActionCommand() == "About")
		{
			//Popup a dialog describing this program
			JOptionPane.showMessageDialog(frame, "Visualize Accelerometer Data ver. 1.0\n Author: Raghu K. Ganti\n Contact:rganti2@cs.uiuc.edu");
		}
		else if (e.getActionCommand() == "Exit")
		{
			frame.dispose();
		}
		else if (e.getActionCommand() == "Choose conf file")
		{
			inputConfFileChooser = new JFileChooser();
			inputConfFileChooser.setCurrentDirectory(new File("."));
			inputConfFileChooser.setDialogTitle("Choose conf file");
			inputConfFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
			
			inputConfFileChooser.setAcceptAllFileFilterUsed(false);
			if (inputConfFileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION)
			{
				chosenFile = inputConfFileChooser.getSelectedFile();
				if (!chosenFile.canRead())
				{
					JOptionPane.showMessageDialog(frame, "Unable to open file, read error!");
				}
				else
				{
					try{
					if (readConfigurationFile(chosenFile.toString()))
					{
						displayTextArea.append("Configuration file "+chosenFile.toString()+" chosen \n"); 
						fileCorrect = true;
						if (baseFileChosen)
							displayButton.setEnabled(true);
					}
					}
					catch (IOException ie)
					{
						JOptionPane.showMessageDialog(frame, "Exception reading the configuration file!");
					}
				}
					
			}
		}
		else if (e.getActionCommand() == "Choose base file")
		{
			File baseFile;
			
			//get the base file name by splitting on ".dat", checking of the existence
			//of the files will be done later
			baseFileChooser = new JFileChooser();
			baseFileChooser.setCurrentDirectory(new File("."));
			baseFileChooser.setDialogTitle("Choose conf file");
			baseFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
			
			baseFileChooser.setAcceptAllFileFilterUsed(false);
			if (baseFileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION)
			{
				baseFile = baseFileChooser.getSelectedFile();
				if (!baseFile.canRead())
				{
					JOptionPane.showMessageDialog(frame, "Base file cannot be read, Error");
				}
				else
				{
					baseFileChosen = true;
					//get the base file name here
					String [] baseFileSplit = (baseFileChooser.getSelectedFile().getName()).split("[.]");
					baseDirName = baseFileChooser.getCurrentDirectory().toString();
					//ignore the last 3 strings!
					baseFileName = "";
					int i;
					
					for (i = 0; i < baseFileSplit.length - 4; i++)
					{
						baseFileName += baseFileSplit[i];
						baseFileName += ".";
					}
					baseFileName += baseFileSplit[i];
					
					if (fileCorrect)
						displayButton.setEnabled(true);
					
					displayTextArea.append("File chosen is: "+baseFile.toString()+"\n");
					displayTextArea.append("Base file is: "+baseFileName+"\n");
				}
			}
		}
		else if (e.getActionCommand() == "Display")
		{
			//check if the values of conf file and the base file name
			//are correct, then only display after setting the readers and reading the files
			
			//check if conf file is correct
			if (!fileCorrect)
			{
				JOptionPane.showMessageDialog(frame, "Please set the configuration file correctly!");
				return;
			}
			//check for existence of files
			if (!checkAndOpenFiles(baseFileName, baseDirName))
			{
				JOptionPane.showMessageDialog(frame, "Error while opening files, check text area for more information");
				
				return;
			}
			inputButton.setEnabled(false);
			inputFileButton.setEnabled(false);
			resetButton.setEnabled(true);
	        setVisualizePane();
		}
		else if (e.getActionCommand() == "Reset")
		{
			//reset the display to original values
			for (int i = 0; i < numMotes; i++)
			{
				tabbedPane.remove(1);
				fileReadersX = null;
				fileReadersY = null;
				bufferedReadersX = null;
				bufferedReadersY = null;
				accelXValues = null;
				accelYValues = null;
			}
			displayButton.setEnabled(false);
			drawingPane = null;
			scroller = null;
			displayTextArea.setText("Display area - current status\n");
			fileCorrect = false;
			baseFileChosen = false;
			baseFileName = "";
			baseDirName = "";
			confFileReader = null;
			confBufferedReader = null;
			numMotes = 0;
			moteIds = null;
			inputButton.setEnabled(true);
			inputFileButton.setEnabled(true);
			resetButton.setEnabled(false);
		}
	}
	
	/**
	 * This method checks for the existence of the accelerometer
	 * files, if there is a read or existence error, it will show up
	 * in the text area on the GUI. Else, the files will be opened
	 * 
	 * @param localBaseFileName The base file name as a plain string
	 * @param pathName The path to the accelerometer files
	 * @return Returns false if an error occurs in existence or read of the files
	 */
	private boolean checkAndOpenFiles(String localBaseFileName, String pathName)
	{
		//get the names of the files
		String [] filenamesX = new String[numMotes];
		String [] filenamesY = new String[numMotes];
		
		for (int i = 0; i < numMotes; i++)
		{
			filenamesX[i] = pathName + "/" + localBaseFileName + "." + moteIds[i] + ".pp.x";
			filenamesY[i] = pathName + "/" + localBaseFileName + "." + moteIds[i] + ".pp.y";
			
			//DEBUG statement
//			System.out.println("File name x: "+filenamesX[i]);
//			System.out.println("File name y: "+filenamesY[i]);
		}
		
		//check for read and existence of file names, and display errors in the text area
		for (int i = 0; i < numMotes; i++)
		{
			File xFile = new File(filenamesX[i]);
			if ((!xFile.exists()) || (!xFile.canRead()))
			{
				displayTextArea.append("ERROR: Unable to read file "+filenamesX[i]);
				
				return false;
			}
			
			File yFile = new File(filenamesY[i]);
			if ( (!yFile.exists()) || (!yFile.canRead()))
			{
				displayTextArea.append("ERROR: Unable to read file "+filenamesY[i]);
				
				return false;
			}
		}
		
		accelXValues = new ArrayList[numMotes];
		accelYValues = new ArrayList[numMotes];
		fileReadersX = new FileReader[numMotes];
		fileReadersY = new FileReader[numMotes];
		bufferedReadersX = new BufferedReader[numMotes];
		bufferedReadersY = new BufferedReader[numMotes];
		
		for (int i = 0; i < numMotes; i++)
		{
			accelXValues[i] = new ArrayList<Value>();
			accelYValues[i] = new ArrayList<Value>();
		}
		
		//Now, all files exist and can be read, so open them!
		for (int i = 0; i < numMotes; i++)
		{
			try{
			fileReadersX[i] = new FileReader(filenamesX[i]);
			fileReadersY[i] = new FileReader(filenamesY[i]);
			}
			catch (IOException ioe)
			{
				//will not cause any exception as existence and read have been checked already!
			}
			
			bufferedReadersX[i] = new BufferedReader(fileReadersX[i]);
			bufferedReadersY[i] = new BufferedReader(fileReadersY[i]);
			
			try{
			readInputFiles(bufferedReadersX[i], accelXValues[i]);
			readInputFiles(bufferedReadersY[i], accelYValues[i]);
			}
			catch (IOException ioe)
			{
				JOptionPane.showMessageDialog(frame, "IO Error!");
			}
		}
		
		return true;
	}
	
	/**
	 * This method reads the configuration file and extracts the
	 * number of motes and their ids
	 * 
	 * @param chosenFile A string that is the name of the configuration file
	 * @return Returns true if the configuration file is correctly formatted else returns false
	 * @throws IOException
	 */
	private boolean readConfigurationFile(String chosenFile) throws IOException
	{
		//Read the configuration file, get the number of motes, and the ids for them
		confFileReader = new FileReader(chosenFile);
		confBufferedReader = new BufferedReader(confFileReader);
		
		String readLine;
		ArrayList<String> moteIdList = new ArrayList<String>();
		
		while ((readLine = confBufferedReader.readLine()) != null)
		{
			String [] splitString = readLine.split("\\s+");
			if (splitString[0] != "")
			{
				numMotes++;
				moteIdList.add(splitString[0]);
			}
		}
		//System.out.println("numMotes: "+numMotes+" array list length:"+moteIdList.size());
		moteIds = new int[numMotes];
		for (int i = 0; i < moteIds.length; i++)
		{
			try{
			moteIds[i] = Integer.parseInt(moteIdList.get(i));
			}
			catch (NumberFormatException nfe)
			{
				JOptionPane.showMessageDialog(frame, "Configuration file format incorrect");
				return false;
			}
		}
		
		return true;
	}

	/**
	 * Initializes the main public class with the various base GUI components
	 */
    public Visualize() {
        super(new BorderLayout());
        
        //Create the main menu items for this panel
        JMenuBar menuBar = new JMenuBar();
        
        JMenu file = new JMenu("File");
        JMenuItem fileItem;
        file.add(fileItem = new JMenuItem ("Open"));
        fileItem.addActionListener(this);
        file.addSeparator();
        file.add(fileItem = new JMenuItem ("Exit"));
        fileItem.addActionListener(this);
        menuBar.add(file);
        
        JMenu about = new JMenu("About");
        about.add(fileItem = new JMenuItem("About"));
        fileItem.addActionListener(this);
        menuBar.add(about);
        
        frame.setJMenuBar(menuBar);
        
        //create the tabbed pane here (this pane will be the one added
        //to the main component)
        tabbedPane = new JTabbedPane();
        
        //create a panel for getting info from the user
        JPanel inputPanel = new JPanel();
        inputPanel.setBackground(Color.WHITE);
        
        GridBagLayout layout = new GridBagLayout();
        GridBagConstraints constraints = new GridBagConstraints();
        
        //Now to add the input dialogs on this panel
        inputPanel.setLayout(layout);
        
        //label to describe what to do
        JTextPane inputTextPane = new JTextPane();
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 0.0;
        constraints.anchor = GridBagConstraints.PAGE_START;
        
        constraints.gridwidth = GridBagConstraints.REMAINDER;
        
        inputTextPane.setPreferredSize(new Dimension(100, 100));
        inputTextPane.setText("Please do the following:\n 1. Choose base file, it can be any one of the x/y files of the" +
        		"smart attire sensor network that you want to visualize. \n 2. Choose the configuration " +
        		"file. For further information, please refer to help.");
        inputTextPane.setEditable(false);
        layout.setConstraints(inputTextPane, constraints);
        inputPanel.add(inputTextPane);
        
        resetButton = new JButton("Reset");
        layout.setConstraints(resetButton, constraints);
        resetButton.addActionListener(this);
        resetButton.setEnabled(false);
        inputPanel.add(resetButton);
        
        inputFileButton = new JButton("Choose base file");
        constraints.weightx = 4.0;
        constraints.insets = new Insets(5, 5, 5, 5);
        constraints.gridwidth = 1;
        layout.setConstraints(inputFileButton, constraints);
        inputFileButton.addActionListener(this);
        inputPanel.add(inputFileButton);
        
        inputButton = new JButton("Choose conf file");
        constraints.weightx = 1.0;
        constraints.gridwidth = 1;
        layout.setConstraints(inputButton, constraints);
        inputButton.addActionListener(this);
        inputPanel.add(inputButton);
        
        displayButton = new JButton("Display");
        constraints.gridwidth = GridBagConstraints.REMAINDER;
        layout.setConstraints(displayButton, constraints);
        displayButton.addActionListener(this);
        displayButton.setEnabled(false);
        inputPanel.add(displayButton);
        
        displayTextArea = new JTextArea();
        displayTextArea.setEditable(false);
        displayTextArea.setBorder(BorderFactory.createEtchedBorder(Color.BLACK, Color.BLUE));
        displayTextArea.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
        displayTextArea.setText("Display area - current status\n");
        //displayTextArea.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 10));
        JScrollPane displayScrollPane = new JScrollPane(displayTextArea);
        displayScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        displayScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        //displayScrollPane.setPreferredSize(new Dimension(600, 300));
        
        constraints.gridheight = GridBagConstraints.REMAINDER;
        constraints.weighty = 3.0;
        layout.setConstraints(displayTextArea, constraints);
        inputPanel.add(displayTextArea);
        
        //Add the input panel on the tabbed pane
        tabbedPane.add(inputPanel, 0);
        tabbedPane.setTitleAt(0, "Input");
        
    	add(tabbedPane, BorderLayout.CENTER);
		setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
    }

    /**
     * This method is called when the display button is pressed on the GUI.
     * It will add the required tabbed panes to display the accelerometer
     * data.
     */
    private void setVisualizePane()
    {
		//For the text field to read the values from a given file
		//Set up the drawing area.
    	drawingPane = new DrawingPane[numMotes];
    	scroller = new JScrollPane[numMotes];
    	buttonCorner = new JPanel[numMotes];
    	
    	for (int i = 0; i < numMotes; i++)
    	{
	        drawingPane[i] = new DrawingPane(i);
	        drawingPane[i].setBackground(Color.white);
			drawingPane[i].setSize(800,600);
	
			buttonCorner[i] = new JPanel();
		    //Put the drawing area in a scroll pane.
	//		createScroller(scroller, drawingPane, buttonCorner);
			
		    //Put the drawing area in a scroll pane.
		    scroller[i] = new JScrollPane(drawingPane[i]);
	    	scroller[i].setPreferredSize(new Dimension(800,600));
			scroller[i].setCorner(JScrollPane.UPPER_LEFT_CORNER, buttonCorner[i]);
			scroller[i].setCorner(JScrollPane.LOWER_LEFT_CORNER,new Corner());
			scroller[i].setCorner(JScrollPane.UPPER_RIGHT_CORNER,new Corner());
			
	    	tabbedPane.add(scroller[i], i+1);
	    	tabbedPane.setTitleAt(i+1, "Mote "+(i+1));
    	}
    	displayButton.setEnabled(false);
    }
    
    public class DrawingPane extends JPanel
    {
    	//File name associated with this drawing pane
    	private int paneID;
    
    	/**
    	 * This is the constructor for the drawing pane. It takes as the
    	 * parameter an integer that points to the acceleration values
    	 * that are to be displayed on this drawing pane
    	 * 
    	 * @param paneID The integer that points to the acceleration values for this drawing pane
    	 */
    	public DrawingPane(int paneID)
    	{
    		this.paneID = paneID; 
    	}
    	
    	/**
    	 * This method paints the drawing pane, and you can call the revalidate method
    	 * which will in turn call this method for repainting the drawing pane.
    	 * 
    	 * @param g Graphics handle to draw on this drawing pane
    	 */
    	protected void paintComponent(Graphics g) {
			final int OFFSET = 250;
			final int X_OFFSET = 300;
			final int Y_OFFSET = 75;
            super.paintComponent(g);
            
            int size = accelXValues[paneID].size();
            
            g.drawString("X axis", 10, 10);
            g.drawString("Y axis", 10, OFFSET + 10);
            
			for (int i=1;i<size;i++)
			{
				Value prevXValue = (Value)accelXValues[paneID].get(i-1);
				Value plotXValue = (Value)accelXValues[paneID].get(i);
				if (i%50 == 0)
				{	
					g.drawLine(2*i,OFFSET,2*i,OFFSET+20);
					g.drawString(Double.toString(plotXValue.getSampleTime()),2*i,OFFSET+30);
				}
				g.drawLine(2*(i-1),prevXValue.getValue() - X_OFFSET, 2*i, plotXValue.getValue() - X_OFFSET);
				
				//now draw the y lines
				Value prevYValue = (Value)accelYValues[paneID].get(i-1);
				Value plotYValue = (Value)accelYValues[paneID].get(i);
				g.drawLine(2*(i-1), prevYValue.getValue() - Y_OFFSET, 2*i, plotYValue.getValue() - Y_OFFSET);
				
				if (2*i > this.getHeight())
				{
					this.setPreferredSize(new Dimension(2*i,this.getHeight()));
					this.revalidate();
				}
			}
        }
    }

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Make sure we have nice window decorations.
		JFrame.setDefaultLookAndFeelDecorated(true);

        //Create and set up the window.
        frame = new JFrame("Visualize Accelerometer Data");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Create and set up the content pane.
        JComponent newContentPane = new Visualize();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);

        //Display the window.
        frame.pack();
		frame.setSize(800,600);
        frame.setVisible(true);
    }
    
    /**
     * This method will read the input file that is provided to it
     * and populate the array list that it has been passed as a parameter.
     * 
     * @param br The buffered reader that corresponds to the file that is being read
     * @param values The array list that is to be populated with Value class objects
     * @throws IOException
     */
    private void readInputFiles(BufferedReader br, ArrayList<Value> values) throws IOException
    {
		String readLine;
		double sample = 0;
		int count = 0;
		
		while ((readLine = br.readLine()) != null)
		{
			String [] splitString = readLine.split("\\s+");
			//Skip the synch line while plotting
			if (readLine.charAt(0) == 's')
				continue;
			//Skip the time line while plotting
			if (readLine.length() == 13)
				continue;
			//double sample = Double.parseDouble(splitString[0]);
			sample = count * 0.04;
			count++;
			int value = Integer.parseInt(splitString[2]);

			Value newValue = new Value(sample,value);
			values.add(newValue);
		}
		/*else
		{
			while ((readLine = br.readLine()) != null)
			{
				String [] splitString = readLine.split("\\s+");
				//skip the synch line while plotting
				if (readLine.charAt(0) == 's')
					continue;
				//Skip the time line too
				if (readLine.length() == 13)
					continue;
				double sample = Double.parseDouble(splitString[0]);
				int value = Integer.parseInt(splitString[1]);
				if ((sample >= startTime) && (sample <= endTime))
				{
					Value newValue = new Value(sample,value);
					values.add(newValue);
				}
				if (sample > endTime)
					break;
			}
		}*/
    }

    /**
     * This is the main method that will call the createAndShowGUI and invoke
     * the GUI. This method is the one called at the start of the program.
     * 
     * @param args The command line arguments that are default to the main method
     * @throws IOException
     */
    public static void main(String[] args) throws IOException
    {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
    
    public class Corner extends JComponent
    {
    	protected void paintComponent(Graphics g)
    	{
    		g.setColor(Color.ORANGE);
    		g.fillRect(0,0,getWidth(), getHeight());	
    	}	
    }
}