package hirondelle.starfield.gui;

import hirondelle.starfield.catalog.parser.Catalog;
import hirondelle.starfield.physics.InputParameterException;
import hirondelle.starfield.physics.InputParameters;
import hirondelle.starfield.physics.Starfield;
import hirondelle.starfield.physics.StarfieldStats;
import hirondelle.starfield.projection.Projector;
import hirondelle.starfield.util.Consts;
import hirondelle.starfield.util.UiUtil;
import hirondelle.starfield.util.Util;

import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.RootPaneContainer;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.factories.CC;
import com.jgoodies.forms.layout.FormLayout;

/** 
 Screen containing the graphical interface for this application.
 
 <P>The screen is simple, and provides a convenient means to enter input parameters fairly quickly.
 No import-export of input parameters is implemented. 
*/
final class Screen {

  /** Create the main screen, and then show it. */
  void buildAndShow(){
    fFrame = new JFrame(Consts.APP_NAME); 
    fFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    fFrame.getContentPane().add(buildPanel());  
    UiUtil.centerAndShow(fFrame);
  }
  
  // PRIVATE
  private JFrame fFrame;  
  private JTextField fBeta = new JTextField("0.993");
  private JTextField fLimitingMagnitude = new JTextField("5.0");
  private JComboBox<Catalog> fCatalog = new JComboBox<Catalog>(Catalog.values());
  private JTextField fCatalogDir = new JTextField("C:\\astro-cat\\yale-bright-star-cat");
  private JComboBox<Projector> fProjection = new JComboBox<Projector>(Projector.values());
  private JTextField fOutputFile = new JTextField("C:\\TEMP\\starfield1.png");
  private JTextArea fMessages = new JTextArea(10, 10);
  private JTextField fImageSize = new JTextField("800");
  private JTextField fMagnification = new JTextField("1");
  private JCheckBox fOpenFile = new JCheckBox();
  private JTextField fDirectionRA = new JTextField("0");
  private JTextField fDirectionDec = new JTextField("90");
  private JTextField fDirectionPhi = new JTextField("0");
  private JButton fApply = new JButton("Generate Image");
  
  private JPanel buildPanel(){
    String COLUMNS = "left:pref, 3dlu, p:grow, 12dlu, p";
    String ROWS = "p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 9dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 9dlu, p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p ";
    FormLayout layout = new FormLayout(COLUMNS, ROWS);
    PanelBuilder builder = new PanelBuilder(layout);
    builder.setDefaultDialogBorder();
    fBeta.setToolTipText("0..1: speed of the spacecraft, as a fraction of the speed limit");
    builder.addLabel("Lorentz Boost Beta:", CC.xy(1, 1));
    builder.add(fBeta, CC.xy(3, 1));
    builder.addLabel("Limiting Magnitude:", CC.xy(1, 3));
    fLimitingMagnitude.setToolTipText("The magnitude at which stars are taken to be invisible to the average human eye");
    builder.add(fLimitingMagnitude, CC.xy(3, 3));
    builder.addLabel("Catalog:", CC.xy(1, 5));
    fCatalog.setToolTipText("The name of the star catalog");
    builder.add(fCatalog, CC.xy(3, 5));
    builder.addLabel("Catalog Directory:", CC.xy(1, 7));
    fCatalogDir.setToolTipText("The directory (folder) containing the downloaded star catalog data (and only that data)");
    builder.add(fCatalogDir, CC.xy(3, 7));
    builder.addLabel("Projection:", CC.xy(1, 9));
    fProjection.setToolTipText("'The projection formula for projecting stars onto the image plane");
    builder.add(fProjection, CC.xy(3, 9));
    builder.addLabel("Output File:", CC.xy(1, 11));
    fOutputFile.setToolTipText("The location and name of the image file generated by this tool");
    builder.add(fOutputFile, CC.xy(3, 11));
    fOpenFile.setSelected(true);
    builder.addLabel("Open Output File:", CC.xy(1, 13));
    fOpenFile.setToolTipText("Open the generated image file when finished");
    builder.add(fOpenFile, CC.xy(3, 13));
    builder.addLabel("Image Size In Pixels:", CC.xy(1, 15));
    fImageSize.setToolTipText("The width (and height) of the generated image");
    builder.add(fImageSize, CC.xy(3, 15));
    builder.addLabel("Magnification (half-sky only):", CC.xy(1, 17));
    fMagnification.setToolTipText("Zoom factor. Applies to the half-sky projections only.");
    builder.add(fMagnification, CC.xy(3, 17));
    
    builder.addLabel("Direction of Motion RA:", CC.xy(1, 19));
    fDirectionRA.setToolTipText("Direction of motion of the spacecraft. Right ascension in degrees, 0..360.");
    builder.add(fDirectionRA, CC.xy(3, 19));
    builder.addLabel("Direction of Motion Dec:", CC.xy(1, 21));
    builder.add(fDirectionDec, CC.xy(3, 21));
    fDirectionDec.setToolTipText("Direction of motion of the spacecraft. Declination in degrees, -90..+90.");
    builder.addLabel("Rotation:", CC.xy(1, 23));
    fDirectionPhi.setToolTipText("Rotate the image around the center (the direction of motion). Degrees, 0..360.");
    builder.add(fDirectionPhi, CC.xy(3, 23));
    
    fApply.addActionListener(new ApplyAction());
    builder.add(fApply, CC.xy(5,23));
    
    builder.addLabel("Messages:", CC.xy(1, 25));
    //redirect stdout to the messages area - stderr too?
    fMessages.setEditable(false);
    Document document = fMessages.getDocument();
    DocumentPrintStream documentPrintStream = new DocumentPrintStream(document, System.out);
    System.setOut(documentPrintStream);    
    builder.add(new JScrollPane(fMessages), CC.xywh(1,27,5,1));
    
    return builder.getPanel();    
  }
  
  /** Apply the user input to the creation of a new image. */
  private final class ApplyAction implements ActionListener {
    @Override public void actionPerformed(ActionEvent aEvent) {
      long start = System.currentTimeMillis();
      
      Util.log("JRE total memory: " + Runtime.getRuntime().totalMemory() + Consts.NL);
      Util.log("JRE free memory: " + Runtime.getRuntime().freeMemory() + Consts.NL);
      Util.log(""+Consts.NL);
      
      InputParameters input = null;
      try {
        startWaitCursor();
        input = new InputParameters(
          fBeta.getText(), fLimitingMagnitude.getText(), fCatalogDir.getText(), 
          fCatalog.getSelectedItem().toString(), fOutputFile.getText(), fProjection.getSelectedItem().toString(),
          fImageSize.getText(), fMagnification.getText(), fDirectionRA.getText(), fDirectionDec.getText(), fDirectionPhi.getText()
        );
        Util.log(input);
        Util.log(""+Consts.NL);
        Starfield starfield = new Starfield(input);
        //output of stats to the command line, PLUS generate an image of the stars
        StarfieldStats stats = starfield.calculate();
        Util.log(stats.toString());
        long end = System.currentTimeMillis();
        Util.log(Consts.NL + "Done. Elapsed time: " + (end-start)/1000.0D + " seconds." + Consts.NL);
        Util.log("--------------------------------------------------------"+Consts.NL);
        if (fOpenFile.isSelected()){
          Desktop.getDesktop().open(input.getOutputFile());
        }
      }
      catch (InputParameterException ex){
        for(String error : ex.getErrors()){
          Util.log(error + Consts.NL);
        }
        Util.log("Error: Aborting, arguments not correct. Please see javadoc for more information.");
      }
      catch (IOException ex) {
        Util.log("Unable to open output image file.");
      }
      finally {
        stopWaitCursor();
      }
    }
  }

  /** Used for redirecting stdout to the messages area. */
  private static final  class DocumentPrintStream extends PrintStream {
    public DocumentPrintStream(Document document, OutputStream delegateStream) {
        super(delegateStream);
        fDocument = document;
    }
    @Override public void print(String string) {
        int offset = fDocument.getLength();
        try {
          fDocument.insertString(offset, string, null);
        } 
        catch (BadLocationException e) {
          //ignore
        }
        super.print(string); // write to the delegate stream
    }
    private Document fDocument;
  }
  
  private void startWaitCursor() {
    RootPaneContainer root = (RootPaneContainer) fFrame.getRootPane().getTopLevelAncestor();
    root.getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    root.getGlassPane().setVisible(true);
 }
 
  private void stopWaitCursor() {
      RootPaneContainer root = (RootPaneContainer) fFrame.getRootPane().getTopLevelAncestor();
      root.getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      root.getGlassPane().setVisible(false);
  }  
}