package com.horstmann.labrat;

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.GridBagLayout;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.beans.EventHandler;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;
import java.util.Scanner;
import java.util.prefs.Preferences;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JEditorPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;

public class GraderFrame extends JFrame
{
   public static void main(String[] args)
   {
      // System.setProperty("com.horstmann.labrat.problem", "file:///home/cay/books/bigj3/testbank/Ch03/CC3_1.zip");
      JFrame frame;
      if (System.getProperty("com.horstmann.labrat.check") != null)
      {
         frame = new CheckFrame();
         frame.pack();
      }         
      else
      {
         frame = new GraderFrame();
         frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
      }
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setVisible(true);
   }

   public GraderFrame()
   {
      prefs = Preferences.userNodeForPackage(getClass());

      URL appIconUrl = getClass().getResource("icon32.gif");
      ImageIcon appIcon = new ImageIcon(appIconUrl);
      setIconImage(appIcon.getImage());

      JPanel top = new JPanel();
      top.setLayout(new GridBagLayout());

      JPanel buttonPanel = new JPanel();
      buttonPanel.add(codecompButton);
      buttonPanel.add(studentButton);
      buttonPanel.add(graderButton);
      top.add(buttonPanel, new GBC(0, 0).setSpan(2, 1).setAnchor(GBC.WEST)
            .setInsets(4));
      top.add(runButton, new GBC(0, 1).setAnchor(GBC.WEST).setInsets(2));
      top.add(new JSeparator(), new GBC(0, 2).setSpan(2, 1).setFill(
            GBC.HORIZONTAL).setInsets(2));
      reportScroll = new JScrollPane(reportOutput);
      pane.addTab("Report", reportScroll);
      top.add(pane, new GBC(0, 3).setSpan(2, 1).setFill(GBC.BOTH).setWeight(1,
            1));
      antScroll = new JScrollPane(antOutput);
      pane.addTab("Ant Output", antScroll);
      
      dirPanel = new JPanel();
      dirPanel.setLayout(new GridBagLayout());
      
      dirPanel.add(submissionDirectoryLabel, new GBC(0, 0).setAnchor(GBC.WEST)
            .setInsets(4));
      dirPanel.add(submissionDirectory, new GBC(0, 1).setFill(GBC.HORIZONTAL)
            .setWeight(1, 0).setInsets(4));
      dirPanel.add(submissionDirectoryPicker, new GBC(1, 1).setInsets(2).setFill(
            GBC.HORIZONTAL));

      dirPanel.add(solutionDirectoryLabel, new GBC(0, 2).setInsets(4).setAnchor(
            GBC.WEST));
      dirPanel.add(solutionDirectory, new GBC(0, 3).setFill(GBC.HORIZONTAL)
            .setWeight(1, 0).setInsets(4).setAnchor(GBC.WEST));
      dirPanel.add(solutionDirectoryPicker, new GBC(1, 3).setInsets(2));
      dirPanel.add(new JPanel(), new GBC(0, 4).setFill(GBC.BOTH).setWeight(1, 1));

      pane.addTab("Directories", dirPanel);
      
      add(top, BorderLayout.CENTER);

      consoleOutputStream = new PrintStream(new OutputStream()
      {
         public void write(int b)
         {
         } // never called

         public void write(byte[] b, int off, int len)
         {
            antOutput.append(new String(b, off, len));
         }
      });

      modeGroup.add(studentButton);
      modeGroup.add(graderButton);
      modeGroup.add(codecompButton);
      studentButton.setSelected(true);
      studentButton.addActionListener(EventHandler.create(ActionListener.class,
            this, "setMode"));
      graderButton.addActionListener(EventHandler.create(ActionListener.class,
            this, "setMode"));
      codecompButton.addActionListener(EventHandler.create(ActionListener.class,
            this, "setMode"));

      fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

      submissionDirectoryPicker.addActionListener(EventHandler.create(
            ActionListener.class, this, "chooseSubmissionDirectory"));
      solutionDirectoryPicker.addActionListener(EventHandler.create(
            ActionListener.class, this, "chooseSolutionDirectory"));
      runButton.addActionListener(EventHandler.create(ActionListener.class,
            this, "runAnt"));

      submissionDirectory.setText(prefs.get("submissionDirectory", ""));
      solutionDirectory.setText(prefs.get("solutionDirectory", ""));

      addWindowListener(new WindowAdapter()
      {
         public void windowClosing(WindowEvent event)
         {
            prefs.put("submissionDirectory", submissionDirectory.getText());
            prefs.put("solutionDirectory", solutionDirectory.getText());
         }
      });

      editArea = new JTextArea();
      editArea.setFont(new Font("monospaced", Font.PLAIN, editArea.getFont().getSize()));
      editScroll = new JScrollPane(editArea);
      
      
      String problemURLName = System
            .getProperty("com.horstmann.labrat.problem");
      if (problemURLName != null)
      {
         try
         {
            URL problemURL = new URL(problemURLName);
            loadProblem(problemURL);
            loadEdit();
            // TODO: What if already present?
            pane.add("Edit", editScroll);
            pane.setSelectedComponent(editScroll);
            submissionDirectory.setText(problemDir.getAbsolutePath());            
         }
         catch (MalformedURLException ex)
         {
            problemDir = null;
         }
         catch (IOException ex)
         {
            problemDir = null;            
         }
      }
      if (problemDir != null)
         codecompButton.setSelected(true);

      setMode();
   }
   
   private void loadEdit() throws IOException
   {
      Properties props = new Properties();
      props.load(new FileInputStream(new File(problemDir, "check.properties")));
      // TODO: May be some other file???
      String mainclass = props.getProperty("mainclass");
      if (mainclass == null)
         throw new IOException("no mainclass");
      Scanner in = new Scanner(new File(problemDir, mainclass + ".java"));
      StringBuilder builder = new StringBuilder();
      while (in.hasNextLine())
      {
         builder.append(in.nextLine());
         builder.append("\n");
      }
      editArea.setText(builder.toString());
   }
   
   private void saveEdit() throws IOException
   {
      Properties props = new Properties();
      props.load(new FileInputStream(new File(problemDir, "check.properties")));
      // TODO: May be some other file???
      String mainclass = props.getProperty("mainclass");
      if (mainclass == null)
         throw new IOException("no mainclass");
      PrintWriter out = new PrintWriter(new File(problemDir, mainclass + ".java"));
      out.print(editArea.getText());
      out.close();
   }

   public void runAnt()
   {
      if (mode.equals("codecomp"))
      {
         try
         {
            saveEdit();
         }
         catch (IOException ex)
         {
            ex.printStackTrace();
            return;
         }
      }
         
      pane.setSelectedComponent(antScroll);
      antOutput.setText("");
      final AntRunner runner = new AntRunner();
      File baseDir = new File(submissionDirectory.getText());
      runner.setBaseDir(baseDir);
      if (mode.equals("grade"))
         runner.putProperty("testcases.dir", solutionDirectory.getText());
      runner.setOut(consoleOutputStream);
      runner.setErr(consoleOutputStream);

      Thread t = new Thread(new Runnable()
      {
         public void run()
         {
            runner.run(mode, "check");
            SwingUtilities.invokeLater(new Runnable()
            {
               public void run()
               {
                  try
                  {
                     // TODO: Remove the old document. Otherwise it doesn't get
                     // updated.
                     reportOutput = new JEditorPane();
                     reportOutput.setPage(runner.getReport().getReportFile()
                           .toURL());
                     reportScroll = new JScrollPane(reportOutput);
                     // TODO: How do we know report tab always at 0
                     pane.removeTabAt(0);
                     pane.insertTab("Report", null, reportScroll, "Report", 0);
                     pane.setSelectedComponent(reportScroll);
                  }
                  catch (IOException ex)
                  {
                     ex.printStackTrace();
                  }
               }
            });
         }
      });

      t.start();
   }

   public void chooseSubmissionDirectory()
   {
      fileChooser.setCurrentDirectory(new File(submissionDirectory.getText()));
      if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION)
         submissionDirectory.setText(fileChooser.getSelectedFile().toString());
   }

   public void chooseSolutionDirectory()
   {
      fileChooser.setCurrentDirectory(new File(solutionDirectory.getText()));
      if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION)
         solutionDirectory.setText(fileChooser.getSelectedFile().toString());
   }

   public void setMode()
   {
      if (codecompButton.isSelected())
      {
         
         mode = "codecomp";
         runButton.setText("Test");
         pane.setSelectedComponent(editScroll);
      }
      else if (graderButton.isSelected())
      {
         mode = "grade";
         runButton.setText("Grade");
         pane.setSelectedComponent(dirPanel);
      }
      else if (studentButton.isSelected())
      {
         mode = "check";
         runButton.setText("Check");
         pane.setSelectedComponent(dirPanel);
      }

      solutionDirectoryPicker.setEnabled(graderButton.isSelected());
      solutionDirectory.setEnabled(graderButton.isSelected());
      
      repaint();
   }

   /**
    * Gets problem zip file and unzips it into work directory
    * @param url the URL of the zip file
    */
   public void loadProblem(URL url)
   {
      // TODO: what do we do when the problem directory exists?

      problemDir = getProblemDirectory(url);
      problemDir.mkdirs();
      try
      {
         InputStream in = url.openStream();
         ZipInputStream zip = new ZipInputStream(in);

         ZipEntry entry;
         while ((entry = zip.getNextEntry()) != null)
         {
            if (entry.isDirectory())
            {
               // Assume directories are stored parents first then children.
               System.err.println("Extracting directory: " + entry.getName());
               // This is not robust, just for demonstration purposes.
               (new File(problemDir, entry.getName())).mkdirs();
               continue;
            }
            else
            {
               System.err.println("Extracting file: " + entry.getName());
               copyInputStream(zip, new BufferedOutputStream(
                     new FileOutputStream(new File(problemDir, entry.getName()))));
            }
            zip.closeEntry();
         }

         zip.close();
      }
      catch (IOException ioe)
      {
         ioe.printStackTrace();
         return;
      }
   }

   private static void copyInputStream(InputStream in, OutputStream out)
         throws IOException
   {
      byte[] buffer = new byte[1024];
      int len;

      while ((len = in.read(buffer)) >= 0)
         out.write(buffer, 0, len);
      out.close();
   }

   /**
    * Gets the base directory, either specified as the system property
    * "com.horstmann.labrat.basedir" or .labrat inside the user's home directory
    * @return the base directory
    */
   private static File getBaseDirectory()
   {
      String baseDirName = System.getProperty("com.horstmann.labrat.basedir");
      if (baseDirName == null)
         baseDirName = System.getProperty("user.home") + File.separator
               + ".labrat";
      return new File(baseDirName);
   }

   /**
    * Derives a problem directory from a URL
    * @param url the URL of the problem zip file
    * @return the directory into which to unzip
    */
   private static File getProblemDirectory(URL url)
   {
      String urlPath = url.getPath();
      int i = urlPath.lastIndexOf("/");
      i = Math.max(i, urlPath.lastIndexOf("\\"));
      i = Math.max(i, urlPath.lastIndexOf(":"));
      int j = urlPath.lastIndexOf(".");
      if (j == -1) j = urlPath.length();
      String name = urlPath.substring(i + 1, j);
      return new File(getBaseDirectory(), name);
   }

   private JLabel submissionDirectoryLabel = new JLabel("Submission Directory");
   private JTextField submissionDirectory = new JTextField();
   private JLabel solutionDirectoryLabel = new JLabel("Test Case Directory");
   private JTextField solutionDirectory = new JTextField();
   private JButton submissionDirectoryPicker = new JButton("...");
   private JButton solutionDirectoryPicker = new JButton("...");
   private JButton runButton = new JButton("Check");
   private JRadioButton studentButton = new JRadioButton("Assignment Self-Check");
   private JRadioButton graderButton = new JRadioButton("Grade Student Assignment");
   private JRadioButton codecompButton = new JRadioButton("Code Completion Exercise");
   private ButtonGroup modeGroup = new ButtonGroup();

   private JTabbedPane pane = new JTabbedPane();
   private JTextArea antOutput = new JTextArea();
   private JScrollPane antScroll;
   private JEditorPane reportOutput = new JEditorPane();
   private JScrollPane reportScroll;
   private JPanel dirPanel;
   
   private JFileChooser fileChooser = new JFileChooser();
   private JTextArea editArea = new JTextArea();
   private JScrollPane editScroll;

   private Preferences prefs;
   private String mode;

   private File problemDir; // directory for current problem

   private PrintStream consoleOutputStream;

   private static int DEFAULT_WIDTH = 600;
   private static int DEFAULT_HEIGHT = 400;
}
