package tcra.gui;

import java.awt.*;
import java.awt.event.*;
import java.lang.Math;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import tcra.math.*;
import tcra.programmatic.MethodArgument;
import tcra.programmatic.VoidType;
import tcra.exercises.*;
import tcra.logger.LogWriter;
import tcra.logger.LogSubmitter;

public class TestCaseReasoningAssistant implements
   ActionListener, TreeSelectionListener
{
   private JFrame       frame;
   private JButton      b_ok;
   private JScrollPane  sp_tree;
   private JScrollPane  sp_method;
   private JScrollPane  sp_case;
   private JScrollPane  sp_input;
   private JTable       tb_input;
   private MyTableModel mtm_input;
   private TCRAExercise tcra_instance;
   private JTree        tree;
   private JSplitPane   column;
   private JRadioButton oncampus;
   private JRadioButton offcampus;

   /**
    *  Default constructor.
    */
   public TestCaseReasoningAssistant()
   {      
      frame = new JFrame("Test Case Reasoning Assistant");
      frame.setIconImage(new ImageIcon("images/brain.gif").getImage());
      frame.getContentPane().setLayout(new FlowLayout());
      frame.setSize(640,480);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      buildMenuBar();
      
      buildComponents();
      
//--- Finish up GUI ---
      frame.getContentPane().add(column);
      // This will allow the window to be resized and have the main
      // JSplitPane resize as well.
      frame.addComponentListener(new ComponentAdapter()
      {
         public void componentResized(ComponentEvent event)
         {
            // This bounds the minimum size of the frame.
            frame.setSize(Math.max(100, frame.getWidth()),
                          Math.max(100, frame.getHeight()));

            // JSplitPane column must be a
            // class field for this to work.
            column.setPreferredSize(new Dimension(frame.getWidth()-20,
                                    frame.getHeight()-60));
            column.setDividerLocation(0.5);
         } // end componentResized(ComponentEvent)
      });

//--- Finish init of class variables ---
      tcra_instance = null;
     
      LogWriter lw = LogWriter.getInstance();
      if(!lw.logHasID())
      {
         String temp = JOptionPane.showInputDialog(
                          "Enter a unique ID that you will remember"+
                          "(and is not personally identifiable)");
         // remove invalid characters
         temp.trim();
         temp.replace(' ', '-');
         temp.replace('_', '-');
         temp.replace('\\', '-');
         temp.replace('\t', '-');
         temp.replace('/', '-');
         temp.replace('*', '-');
         temp.replace('?', '-');
         lw.logID(temp);
      } // end if
      lw.logTimestamp();
      
      // always last
      frame.setVisible(true);
   } // end ctor
   
   private void buildMenuBar()
   {
      JMenuBar mb_main = new JMenuBar();

      JMenu m_file = new JMenu("File");
      m_file.setMnemonic(KeyEvent.VK_F);
      m_file.setIcon(new ImageIcon("images/file.gif"));
      JMenuItem mi_open = new JMenuItem("Open");
      mi_open.setMnemonic(KeyEvent.VK_O);
      mi_open.addActionListener(this);
      JMenuItem mi_exit = new JMenuItem("Exit");
      mi_exit.setMnemonic(KeyEvent.VK_X);
      mi_exit.addActionListener(this);
      m_file.add(mi_open);
      m_file.addSeparator();
      m_file.add(mi_exit);
      
      JMenu m_tools = new JMenu("Tools");
      m_tools.setMnemonic(KeyEvent.VK_T);
      JMenuItem mi_submit = new JMenuItem("Submit Log");
      mi_submit.setMnemonic(KeyEvent.VK_S);
      mi_submit.addActionListener(this);
      
      // Change server address based on which is selected
      oncampus = new JRadioButton("On Campus Access", true);
      offcampus = new JRadioButton("Off Campus Access");
      ButtonGroup network = new ButtonGroup();
      network.add(oncampus);
      network.add(offcampus);
      
      m_tools.add(oncampus);
      m_tools.add(offcampus);
      m_tools.add(mi_submit);

      mb_main.add(m_file);
      mb_main.add(m_tools);

      frame.setJMenuBar(mb_main);
   } // end buildMenuBar()
   
   private void buildComponents()
   {
    //--- Tree ---
      sp_tree = new JScrollPane(null);
      sp_tree.setPreferredSize(new Dimension(300,200));
//--- Contract Pane ---
      sp_method = new JScrollPane(null);
      sp_method.setPreferredSize(new Dimension(300,200));
//--- Test Case Display ---
      sp_case = new JScrollPane(null);
      sp_case.setPreferredSize(new Dimension(300,200));
//--- Button ---
      b_ok = new JButton("OK");
      b_ok.setPreferredSize(new Dimension(300, 20));
      b_ok.addActionListener(this);
//--- Input Table ---
      JPanel p_input  = new JPanel();
      p_input.setLayout(new FlowLayout());
      p_input.setPreferredSize(new Dimension(300,200));

      String s_headers[] = {"Variable Name","Value"};
      mtm_input = new MyTableModel(new String[10][2], s_headers);
      tb_input  = new JTable(mtm_input);
      tb_input.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      tb_input.setRowSelectionAllowed(false);
      tb_input.setColumnSelectionAllowed(false);
      tb_input.setPreferredScrollableViewportSize(
                                              new Dimension(275,160));
      sp_input = new JScrollPane(tb_input);

      p_input.add(sp_input);
      p_input.add(b_ok);
//--- Split Panes ---
      JSplitPane left   = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
                                         sp_tree,p_input);
      left.setDividerLocation(0.5);
      JSplitPane right  = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
                                         sp_method,sp_case);
      right.setDividerLocation(0.5);
      column = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,left,right);
      column.setPreferredSize(new Dimension(620,420));
      column.setDividerLocation(0.5);
   } // end buildComponents()

   /**
    * Fires by any component this class is registered to as an
    * ActionListener when its action is performed. The source of
    * the ActionEvent is determined and the proper private function
    * is called.
    *
    * @param ae ActionEvent generated by one of the registered
    *           components.
    */
   public void actionPerformed(ActionEvent ae)
   {
      String actionCommand = ae.getActionCommand();
      if(actionCommand.equals("Exit")) System.exit(0);
      try
      {
         if(actionCommand.equals("Open"))    loadExercise();
         else if(actionCommand.equals("OK")) displayAndCheck();
         else if(actionCommand.equals("Submit Log"))
         {
            if(oncampus.isSelected())
               LogSubmitter.getInstance().submitLog(frame, LogSubmitter.ONCAMPUS);
            else if(offcampus.isSelected())
               LogSubmitter.getInstance().submitLog(frame, LogSubmitter.OFFCAMPUS);
         } // end else if
      }
      catch(Throwable e)
      {
         System.out.println("ERROR: " + e);    
         e.printStackTrace();
      }
   } // end actionPerformed(ActionEvent)


   /**
    * Fires when a method from the JTree has been selected.
    * Fills in the contract display pane and builds the variable table.
    *
    * @param tse TreeSelectionEvent passed from the JTree when a new
    *            tree node has been selected.
    */
   public void valueChanged(TreeSelectionEvent tse)
   {
      sp_case.setViewportView(null);
      
      //Extract method name from tree selection path
      String name = (String)((DefaultMutableTreeNode)
         tse.getPath().getLastPathComponent()).getUserObject();

      java.util.List<MethodContract> l_mc =
         tcra_instance.getContracts();

      // search list of method contracts for the name we retrieved
      int i = 0;
      while(i < l_mc.size() &&
            !(l_mc.get(i).getMethodName().equals(name)))
          ++i;
      // if we found it, load method pane and variable table
      if(i < l_mc.size())
      {
         // save selected MethodContract and argument list
         MethodContract selectedMC = l_mc.get(i);

         // load method pane
         initMethodPane(selectedMC);

         // create array for input data
         initInputTable(selectedMC);
         
         LogWriter lw = LogWriter.getInstance();
         lw.logMethodSelect(name);
      } // end if
      else
      {
         sp_method.setViewportView(null);

         String s_headers[] = {"Variable Name","Value"};
         mtm_input.setDataVector(new String[10][2],s_headers);
      } // end else
   } // end valueChanges(TreeSelectionEvent)

   private void initMethodPane(MethodContract selectedMC)
   {
      JLabel method = new JLabel(selectedMC.toString());
      method.setVerticalAlignment(SwingConstants.TOP);
      sp_method.setViewportView(method);
   } // end initMethodPane(MethodContract)

   private void initInputTable(MethodContract selectedMC)
   {
      java.util.List<MethodArgument> args = selectedMC.getArgs();
      String s_headers[] = {"Argument Name","Value"};
      int size = args.size()*2+2;
      if(!(selectedMC.getReturnType() instanceof VoidType))
         ++size;

      String s_data[][]  = new String[size][2];
      s_data[0][0] = "#" + tcra_instance.getSelfString();
      for(int j = 0; j < args.size(); ++j)
      {
         s_data[j+1][0] = args.get(j).getType() +" #"+
                          args.get(j).getName();
      } // end for
      s_data[args.size()+1][0] = tcra_instance.getSelfString();
      for(int j = 0; j < args.size(); ++j)
      {
         s_data[args.size()+j+2][0] = args.get(j).getType() +" "+
                                      args.get(j).getName();
      } // end for
      if(size > args.size()*2+2)
         s_data[size-1][0] = ""+selectedMC.getReturnType()+ " " +
                             selectedMC.getMethodName()+ "( )";

      // fill table with data
      mtm_input.setDataVector(s_data, s_headers);
   } // end initInputTable(MethodContract)

   /**
    * Executes when the user chooses to open a new file.
    * FileChooser is opened by ExerciseUtilities and the exercise is
    * returned from there. The tree is then built with that exercise.
    *
    * @throws Throwable Any exception that may occur during the use
    *                   of Reflection. 
    */
   private void loadExercise() throws Throwable
   {
	  TCRAExercise temp = null;
	  boolean error_caught = false;
      try
      {
    	 temp = ExerciseUtilities.loadFile();
    	 if(temp != null) tcra_instance = temp;
      } // end try
      catch(ClassNotFoundException cnf)
      {
         JOptionPane.showMessageDialog(frame,
                                   "Chosen file is not an exercise.");
         error_caught = true;
      } // end catch(ClassNotFoundException)
      // chosen file is inside 'exercises' but is not
      // an implementation of TCRAExercise
      catch(ClassCastException cce)
      {
         JOptionPane.showMessageDialog(frame,
                                   "Chosen file is not an exercise.");
         error_caught = true;
      } // end catch(ClassNotFoundException)
      // something else happened
      catch(Throwable e)
      {
         System.out.println(e);
         throw e;
      } // end catch(Throwable)

      // successfully loaded an exercise
      if(temp != null)
      {
         java.util.List<MethodContract> v_mc =
                                         tcra_instance.getContracts();

         /* Build Tree */
         // create root
         DefaultMutableTreeNode root =
            new DefaultMutableTreeNode(
            tcra_instance.getInterfaceName());

         // add methods as children
         for(int i = 0; i < v_mc.size(); ++i)
         {
            root.add(new DefaultMutableTreeNode(
                     v_mc.get(i).getMethodName()));
         } // end for

         // finish tree and set into scroll pane
         tree = new JTree(root);
         tree.getSelectionModel().setSelectionMode(
            TreeSelectionModel.SINGLE_TREE_SELECTION);
         tree.addTreeSelectionListener(this);
         // default selection to method 1
         tree.setSelectionRow(1);
         sp_tree.setViewportView(tree);
      } // end if
      else if(error_caught == false)
      {
         // Open Dialog Box with error
         JOptionPane.showMessageDialog(frame, "Invalid file.");
      } // end else
   } // end loadExercise()

   /**
    * Executed whenever the OK button is pushed. If an exercise is
    * currently loaded, then the user input is given to it for
    * validation. Otherwise the method returns. The user supplied
    * values are displayed in the final method contract in the 
    * lower right pane of the GUI. If the user input is unparsable
    * or fails the method's assertions, then a MessageBox is
    * displayed with the appropriate error.
    */
   private void displayAndCheck()
   {
      sp_case.setViewportView(null);

      // no file loaded
      if(tree == null) return;

      TreePath tp = tree.getSelectionPath();
      String name = (String)((DefaultMutableTreeNode)
                 tp.getLastPathComponent()).getUserObject();

      java.util.List<MethodContract> l_mc =
                                        tcra_instance.getContracts();

      int i = 0;
      while(i < l_mc.size() &&
           !(l_mc.get(i).getMethodName().equals(name)))
         ++i;

      // whatever is selected is not a method
      if(i == l_mc.size()) return; 

      // move editing selection to 0,0 so that changes are
      // finalized
      tb_input.editCellAt(0,0);
      int    num_vals = tb_input.getRowCount();
      String vals[]   = new String[num_vals];

      // read user input from table
      for(int j = 0; j < num_vals; ++j)
      {
         vals[j] = (String)tb_input.getValueAt(j,1);
         // take care of empty cells
         if(vals[j] == null) vals[j] = new String("");
      } // end for

      // Find out what happened during parsing and check
      boolean test = false;
      try
      {
         test = l_mc.get(i).check(vals);
      } // end try
      catch(MathFormatException mfe)
      {
    	  JOptionPane.showMessageDialog(frame, mfe.getMessage(),
    			                        "Warning", JOptionPane.ERROR_MESSAGE);
    	  return;
      } // end catch(MathFormatException)
      
      // fill out the evaluated contract pane
      JLabel l_contract = new JLabel(l_mc.get(i).toInstanceString());
      l_contract.setVerticalAlignment(SwingConstants.TOP);
      sp_case.setViewportView(l_contract);
      
      if(test)
      {
         JOptionPane.showMessageDialog(frame,
            "Test case satisfies all requirements.", "Correct",
            JOptionPane.INFORMATION_MESSAGE);
      } // end if
      else
      {
         JOptionPane.showMessageDialog(frame,
                  "Test Case does not satisfy requirements.",
                  "Warning", JOptionPane.ERROR_MESSAGE);
      } // end else
      
      LogWriter.getInstance().logInput(mtm_input, test);
   } // end displayAndCheck()
} // end class