/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/
package org.apache.batik.ext.swing;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.io.Serializable;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.Border;
import javax.swing.text.Document;

/**
 * The <tt>JAffineTransformChooser</tt> is a pane that contains
 * controls to let a user select the various components that 
 * make up an <tt>AffineTransform</tt>
 *
 * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
 * @version $Id: JAffineTransformChooser.java,v 1.5 2003/04/11 13:58:16 vhardy Exp $
 */
public class JAffineTransformChooser extends JGridBagPanel{
    public static final String LABEL_ANGLE
        = "JAffineTransformChooser.label.angle";

    public static final String LABEL_DEGREE
        = "JAffineTransformChooser.label.degree";

    public static final String LABEL_PERCENT
        = "JAffineTransformChooser.label.percent";

    public static final String LABEL_ROTATE
        = "JAffineTransformChooser.label.rotate";

    public static final String LABEL_SCALE
        = "JAffineTransformChooser.label.scale";

    public static final String LABEL_RX
        = "JAffineTransformChooser.label.rx";

    public static final String LABEL_RY
        = "JAffineTransformChooser.label.ry";

    public static final String LABEL_SX
        = "JAffineTransformChooser.label.sx";

    public static final String LABEL_SY
        = "JAffineTransformChooser.label.sy";

    public static final String LABEL_TRANSLATE
        = "JAffineTransformChooser.label.translate";

    public static final String LABEL_TX 
        = "JAffineTransformChooser.label.tx";

    public static final String LABEL_TY
        = "JAffineTransformChooser.label.ty";
    
    public static final String CONFIG_TEXT_FIELD_WIDTH 
        = "JAffineTransformChooser.config.text.field.width";

    public static final String CONFIG_TOP_PAD
        = "JAffineTransformChooser.config.top.pad";

    public static final String CONFIG_LEFT_PAD
        = "JAffineTransformChooser.config.left.pad";

    public static final String CONFIG_BOTTOM_PAD
        = "JAffineTransformChooser.config.bottom.pad";

    public static final String CONFIG_RIGHT_PAD
        = "JAffineTransformChooser.config.right.pad";

    /**
     * The <tt>AffineTransform</tt> value represented by the 
     * chooser
     */
    protected AffineTransform txf;

    /**
     * The Model for the x-axis translate component
     */
    protected DoubleDocument txModel = new DoubleDocument();

    /**
     * The Model for the y-axis translate component
     */
    protected DoubleDocument tyModel = new DoubleDocument();
    
    /**
     * The Model for the x-axis scale component
     */
    protected DoubleDocument sxModel = new DoubleDocument();

    /**
     * The Model for the y-axis scale component
     */
    protected DoubleDocument syModel = new DoubleDocument();

    /**
     * The Model for the x-axis rotation center
     */
    protected DoubleDocument rxModel = new DoubleDocument();

    /**
     * The Model for the y-axis rotation center
     */
    protected DoubleDocument ryModel = new DoubleDocument();

    /**
     * The Model for the rotation
     */
    protected DoubleDocument rotateModel = new DoubleDocument();

    protected static final double RAD_TO_DEG = 180./Math.PI;
    protected static final double DEG_TO_RAD = Math.PI/180.;

    /**
     * Default constructor
     */
    public JAffineTransformChooser(){
        build();
        setAffineTransform(new AffineTransform());
    }
      
    /**
     * Adds the control components into this panel.
     */
    protected void build(){
        Component txyCmp = buildPanel(Resources.getString(LABEL_TRANSLATE),
                                      Resources.getString(LABEL_TX),
                                      txModel,
                                      Resources.getString(LABEL_TY),
                                      tyModel,
                                      "",
                                      "",
                                      true);
        
        Component sxyCmp = buildPanel(Resources.getString(LABEL_SCALE),
                                      Resources.getString(LABEL_SX),
                                      sxModel,
                                      Resources.getString(LABEL_SY),
                                      syModel,
                                      Resources.getString(LABEL_PERCENT),
                                      Resources.getString(LABEL_PERCENT),
                                      true);

        Component rCmp = buildRotatePanel();
                                 
        add(txyCmp,  0, 0, 1, 1, CENTER, BOTH, 1, 1);
        add(sxyCmp,  1, 0, 1, 1, CENTER, BOTH, 1, 1);
        add(rCmp,    0, 1, 2, 1, CENTER, BOTH, 1, 1);
    }

    protected Component buildRotatePanel(){
        JGridBagPanel panel = new JGridBagPanel();

        Component anglePanel = buildPanel(Resources.getString(LABEL_ROTATE),
                                          Resources.getString(LABEL_ANGLE),
                                          rotateModel,
                                          null,
                                          null,
                                          Resources.getString(LABEL_DEGREE),
                                          null,
                                          false);

        Component centerPanel = buildPanel("",
                                           Resources.getString(LABEL_RX),
                                           rxModel,
                                           Resources.getString(LABEL_RY),
                                           ryModel,
                                           null,
                                           null,
                                           false);

        panel.add(anglePanel,   0, 0, 1, 1, CENTER, BOTH, 1, 1);
        panel.add(centerPanel,  1, 0, 1, 1, CENTER, BOTH, 1, 1);

        setPanelBorder(panel, Resources.getString(LABEL_ROTATE));

        return panel;
    }

    protected Component buildPanel(String panelName,
                                   String tfALabel,
                                   Document tfAModel,
                                   String tfBLabel,
                                   Document tfBModel,
                                   String tfASuffix,
                                   String tfBSuffix,
                                   boolean setBorder){
        JGridBagPanel panel = new JGridBagPanel();
        
        addToPanelAtRow(tfALabel, tfAModel, tfASuffix, panel, 0);
        if(tfBLabel != null){
            addToPanelAtRow(tfBLabel, tfBModel, tfBSuffix, panel, 1);
        }

        // Create a border
        if(setBorder){
            setPanelBorder(panel, panelName);
        }

        return panel;

    }

    public void setPanelBorder(JComponent panel, String panelName){
        Border border 
            = BorderFactory.createTitledBorder
            (BorderFactory.createEtchedBorder(), panelName);

        int topPad = Resources.getInteger(CONFIG_TOP_PAD);
        int leftPad = Resources.getInteger(CONFIG_LEFT_PAD);
        int bottomPad = Resources.getInteger(CONFIG_BOTTOM_PAD);
        int rightPad = Resources.getInteger(CONFIG_RIGHT_PAD);

        border 
            = BorderFactory.createCompoundBorder
            (border,
             BorderFactory.createEmptyBorder(topPad, leftPad, 
                                             bottomPad, rightPad));

        panel.setBorder(border);
    }

    protected void addToPanelAtRow(String label,
                                   Document model,
                                   String suffix,
                                   JGridBagPanel p,
                                   int row){
        JTextField tf = new JTextField(Resources.getInteger(CONFIG_TEXT_FIELD_WIDTH));
        tf.setDocument(model);
        p.add(new JLabel(label),    0, row, 1, 1, WEST, HORIZONTAL, 0, 0);
        p.add(tf,                   1, row, 1, 1, CENTER, HORIZONTAL, 1, 0);
        p.add(new JLabel(suffix),   2, row, 1, 1, WEST, HORIZONTAL, 0, 0);
    }

    public AffineTransform getAffineTransform(){
        double sx = sxModel.getValue()/100.;
        double sy = syModel.getValue()/100.;
        double theta = rotateModel.getValue()*DEG_TO_RAD;
        double rx = rxModel.getValue();
        double ry = ryModel.getValue();
        double tx = txModel.getValue();
        double ty = tyModel.getValue();

        double[] m = new double[6];

        m[0] = sx*Math.cos(theta);
        m[1] = sx*Math.sin(theta);
        m[2] = -sy*Math.sin(theta);
        m[3] = sy*Math.cos(theta);
        m[4] = tx + rx - rx*Math.cos(theta) + ry*Math.sin(theta);
        m[5] = ty + ry - rx*Math.sin(theta) - ry*Math.cos(theta);

        txf = new AffineTransform(m);

        return txf;
    }

    public void setAffineTransform(AffineTransform txf){
        if(txf == null){
            txf = new AffineTransform();
        }

        this.txf = txf;
        
        /**
         * Now, update model
         */
        double[] m = new double[6];
        txf.getMatrix(m);
        
        // Translation
        txModel.setValue(m[4]);
        tyModel.setValue(m[5]);
        
        // Scale, in percentages
        double sx = Math.sqrt(m[0]*m[0] + m[1]*m[1]);
        double sy = Math.sqrt(m[2]*m[2] + m[3]*m[3]);
        sxModel.setValue(100*sx);
        syModel.setValue(100*sy);
        
        // Rotation
        double theta = 0;
        if(m[0] > 0){
            theta = Math.atan2(m[1], m[0]);
        }
        
        // Rotate
        rotateModel.setValue(RAD_TO_DEG*theta);
        rxModel.setValue(0);
        ryModel.setValue(0);
    }        

    /**
     * Displays the panel in a modal dialog box.
     * @param cmp the dialog's parent component
     * @param title the dialog's title
     *
     * @return null if the dialog was cancelled. Otherwise, the value entered
     *         by the user.
     */
    public static AffineTransform showDialog(Component cmp,
                                             String title){
        final JAffineTransformChooser pane 
            = new JAffineTransformChooser();

        AffineTransformTracker tracker = new AffineTransformTracker(pane);
        JDialog dialog = new Dialog(cmp, title, true, pane, tracker, null);
        dialog.addWindowListener(new Closer());
        dialog.addComponentListener(new DisposeOnClose());

        dialog.show(); // blocks until user brings dialog down...

        return tracker.getAffineTransform();
    }
    
    /**
     * Creates a new modal dialog box that can be used to 
     * configure an <tt>AffineTransform</tt>
     *
     * @param cmp the dialog's parent component
     * @param title the dialog's title
     *
     */
    public static Dialog createDialog(Component cmp,
                                      String title){
        final JAffineTransformChooser pane 
            = new JAffineTransformChooser();
        
        AffineTransformTracker tracker = new AffineTransformTracker(pane);
        Dialog dialog = new Dialog(cmp, title, true, pane, tracker, null);
        dialog.addWindowListener(new Closer());
        dialog.addComponentListener(new DisposeOnClose());

        return dialog;
    }
    

    public static void main(String args[]){
        AffineTransform t = showDialog(null, "Hello");
        // AffineTransform.getScaleInstance(.25, .25));
        // new AffineTransform()); 
        // AffineTransform.getShearInstance(1, 1));
        if(t == null){
            System.out.println("Cancelled");
        }
        else{
            System.out.println("t = " + t);
        }
    }

    /*
     * Class which builds a AffineTransform chooser dialog consisting of
     * a JAffineTransformChooser with "Ok", "Cancel", and "Reset" buttons.
     *
     * Note: This needs to be fixed to deal with localization!
     */
    public static class Dialog extends JDialog {
        private JAffineTransformChooser chooserPane;
        private AffineTransformTracker tracker;

        public static final String LABEL_OK 
            = "JAffineTransformChooser.label.ok";

        public static final String LABEL_CANCEL 
            = "JAffineTransformChooser.label.cancel";

        public static final String LABEL_RESET
            = "JAffineTransformChooser.label.reset";

        public static final String ACTION_COMMAND_OK
            = "OK";

        public static final String ACTION_COMMAND_CANCEL 
            = "cancel";


        public Dialog(Component c, String title, boolean modal,
                      JAffineTransformChooser chooserPane,
                      AffineTransformTracker okListener, ActionListener cancelListener) {
            super(JOptionPane.getFrameForComponent(c), title, modal);
        
            this.chooserPane = chooserPane;
            this.tracker = okListener;

            String okString = Resources.getString(LABEL_OK);
            String cancelString = Resources.getString(LABEL_CANCEL);
            String resetString = Resources.getString(LABEL_RESET);

            Container contentPane = getContentPane();
            contentPane.setLayout(new BorderLayout());
            contentPane.add(chooserPane, BorderLayout.CENTER);

            /*
             * Create Lower button panel
             */
            JPanel buttonPane = new JPanel();
            buttonPane.setLayout(new FlowLayout(FlowLayout.CENTER));
            JButton okButton = new JButton(okString);
            getRootPane().setDefaultButton(okButton);
            okButton.setActionCommand(ACTION_COMMAND_OK);
            if (okListener != null) {
                okButton.addActionListener(okListener);
            }
            okButton.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        hide();
                    }
                });
            buttonPane.add(okButton);

            JButton cancelButton = new JButton(cancelString);

            addKeyListener(new KeyAdapter(){
                    public void keyPressed(KeyEvent evt){
                        if(evt.getKeyCode() == KeyEvent.VK_ESCAPE){
                            hide();
                        }
                    }
                });
            
            cancelButton.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        hide();
                    }
                });
            
            buttonPane.add(cancelButton);

            JButton resetButton = new JButton(resetString);
            resetButton.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        reset();
                    }
                });
            buttonPane.add(resetButton);
            contentPane.add(buttonPane, BorderLayout.SOUTH);

            pack();
            setLocationRelativeTo(c);
        }

        public void show() {
            tracker.reset();
            super.show();
        }

        public AffineTransform showDialog(){
            this.show();
            return tracker.getAffineTransform();
        }

        public void reset() {
            chooserPane.setAffineTransform(new AffineTransform());
        }

        public void setTransform(AffineTransform at){
            if(at == null){
                at = new AffineTransform();
            }

            chooserPane.setAffineTransform(at);
        }



    }

    static class Closer extends WindowAdapter implements Serializable{
        public void windowClosing(WindowEvent e) {
            Window w = e.getWindow();
            w.hide();
        }
    }
    
    static class DisposeOnClose extends ComponentAdapter implements Serializable{
        public void componentHidden(ComponentEvent e) {
            Window w = (Window)e.getComponent();
            w.dispose();
        }
    }
    
}


class AffineTransformTracker implements ActionListener, Serializable {
    JAffineTransformChooser chooser;
    AffineTransform txf;

    public AffineTransformTracker(JAffineTransformChooser c) {
        chooser = c;
    }

    public void actionPerformed(ActionEvent e) {
        txf = chooser.getAffineTransform();
    }

    public AffineTransform getAffineTransform() {
        return txf;
    }

    public void reset(){
        txf = null;
    }
}

