package cz.fidentis.analyst.symmetry;

import cz.fidentis.analyst.core.ControlPanel;
import cz.fidentis.analyst.core.ControlPanelBuilder;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import org.openide.windows.WindowManager;

/**
 * Control panel for symmetry plane.
 *
 * @author Natalia Bebjakova
 * @author Radek Oslejsek
 */
public final class SymmetryPanel extends ControlPanel {
    
    /*
     * Handled actions
     */
    public static final String ACTION_COMMAND_SHOW_HIDE_PLANE = "show-hide symmetry plane";
    public static final String ACTION_COMMAND_RECOMPUTE = "recompute";
    
    /*
     * Mandatory design elements
     */
    public static final String ICON = "symmetry28x28.png";
    public static final String NAME = "Symmetry";
    
    /*
     * Configuration of panel-specific GUI elements
     */
    public static final int MAX_SIGNIFICANT_POINTS = 500;
    
    /*
     * Computational state
     */
    private final SymmetryConfig config = new SymmetryConfig();
    private final SymmetryConfig tempConfig = new SymmetryConfig();
    
    /*
     * GUI primitives holding the configuration state:
     */
    private JTextField signPointsTF = null;
    private JTextField minCurvatureTF = null;
    private JTextField minAngleCosTF = null;
    private JTextField minNormAngleCosTF = null;
    private JTextField maxRelDistTF = null;
    private final JCheckBox averaging;
    private final JCheckBox showSymmetryPlane;
    
    /**    
     * Constructor.
     * 
     * @param action Action listener
     */
    public SymmetryPanel(ActionListener action) {
        this.setName(NAME);
        
        ControlPanelBuilder builder = new ControlPanelBuilder(this);
        
        builder.addCaptionLine("Computation options:");
        builder.addLine();
        
        signPointsTF = builder.addSliderOptionLine(
                (ActionEvent e) -> { 
                    showSignPointsHelp(); 
                }, 
                "Significant points", 
                MAX_SIGNIFICANT_POINTS, 
                (ActionEvent e) -> { 
                    tempConfig.setSignificantPointCount(ControlPanelBuilder.parseLocaleInt(signPointsTF)); 
                }
        );
        builder.addLine();
        
        minCurvatureTF = builder.addSliderOptionLine(
                (ActionEvent e) -> { 
                    showMinCurvHelp();     
                }, 
                "Min. curvature ratio", 
                -1, 
                (ActionEvent e) -> { 
                    tempConfig.setMinCurvRatio(ControlPanelBuilder.parseLocaleDouble(minCurvatureTF));
                });
        builder.addLine();
        
        minAngleCosTF = builder.addSliderOptionLine(
                (ActionEvent e) -> { 
                    showMinAngleCosHelp();
                }, 
                "Min. angle cosine", 
                -1, 
                (ActionEvent e) -> { 
                    tempConfig.setMinAngleCos(ControlPanelBuilder.parseLocaleDouble(minAngleCosTF));
                });
        builder.addLine();
        
        minNormAngleCosTF = builder.addSliderOptionLine(
                (ActionEvent e) -> { 
                    showNormalAngleHelp();     
                }, 
                "Normal angle", 
                -1, 
                (ActionEvent e) -> { 
                    tempConfig.setMinNormAngleCos(ControlPanelBuilder.parseLocaleDouble(minNormAngleCosTF));
                });
        builder.addLine();
        
        maxRelDistTF = builder.addSliderOptionLine(
                (ActionEvent e) -> { 
                    showRelDistHelp();     
                }, 
                "Relative distance", 
                -1, 
                (ActionEvent e) -> { 
                    tempConfig.setMaxRelDistance(ControlPanelBuilder.parseLocaleDouble(maxRelDistTF));
                });
        builder.addLine();
        
        averaging = builder.addCheckBoxOptionLine(
                (ActionEvent e) -> { 
                    showAveragingHelp(); 
                }, 
                "Averaging", 
                config.isAveraging(), 
                (ActionEvent e) -> { 
                    tempConfig.setAveraging(((JToggleButton) e.getSource()).isSelected());
                });
        builder.addLine();
        
        builder.addCaptionLine("Visualization options:");
        builder.addLine();
        
        showSymmetryPlane = builder.addCheckBoxOptionLine(
                null, 
                "Show symmetry plane", 
                true, 
                createListener(action, ACTION_COMMAND_SHOW_HIDE_PLANE)
        ); 
        builder.addLine();
        
        builder.addButtons(
                List.of("Reset changes", 
                        "Reset to defaults", 
                        "(Re)compute"), 
                List.of(
                        (ActionEvent e) -> {
                            setTempConfig(config);
                        },
                        (ActionEvent e) -> {
                            setTempConfig(new SymmetryConfig());
                        },
                        (ActionEvent e) -> {
                            config.copy(tempConfig); // confirm changes
                            action.actionPerformed(new ActionEvent( // recompute
                                    e.getSource(), 
                                    ActionEvent.ACTION_PERFORMED, 
                                    ACTION_COMMAND_RECOMPUTE)
                            ); 
                            action.actionPerformed(new ActionEvent( // and then show or keep hidden
                                    showSymmetryPlane,
                                    ActionEvent.ACTION_PERFORMED,
                                    ACTION_COMMAND_SHOW_HIDE_PLANE)
                            );
                        }
                )
        );
        
        setTempConfig(new SymmetryConfig()); // intialize values;
    }
    
    /**
     * Returns symmetry plane configuration
     * @return symmetry plane configuration
     */
    public SymmetryConfig getSymmetryConfig() {
        return this.config;
    }
    
    @Override
    public ImageIcon getIcon() {
        return getStaticIcon();
    }
    
    /**
     * Static implementation of the {@link #getIcon()} method.
     * 
     * @return Control panel icon
     */
    public static ImageIcon getStaticIcon() {
        return new ImageIcon(SymmetryPanel.class.getClassLoader().getResource("/" + ICON));
    }
    
    private void setTempConfig(SymmetryConfig config) {
        // update input fields
        this.signPointsTF.setText(ControlPanelBuilder.intToStringLocale(config.getSignificantPointCount()));
        this.minCurvatureTF.setText(ControlPanelBuilder.doubleToStringLocale(config.getMinCurvRatio()));
        this.minAngleCosTF.setText(ControlPanelBuilder.doubleToStringLocale(config.getMinAngleCos()));
        this.minNormAngleCosTF.setText(ControlPanelBuilder.doubleToStringLocale(config.getMinNormAngleCos()));
        this.maxRelDistTF.setText(ControlPanelBuilder.doubleToStringLocale(config.getMaxRelDistance()));
        this.averaging.setSelected(config.isAveraging());
        
        // fire changes (update sliders)
        this.signPointsTF.postActionEvent();
        this.minCurvatureTF.postActionEvent();
        this.minAngleCosTF.postActionEvent();
        this.minNormAngleCosTF.postActionEvent();
        this.maxRelDistTF.postActionEvent();
        
        // update config
        tempConfig.copy(config);
    }
    
    private void showSignPointsHelp() {
        JOptionPane.showMessageDialog(WindowManager.getDefault().findTopComponent("SingleFaceTab"),
                "Entered number represents amount of points of the mesh that are taken into account" + System.lineSeparator()
                        + "while counting the plane of approximate symmetry." + System.lineSeparator() 
                        + System.lineSeparator()
                        + "Higher number → longer calculation, possibly more accurate result." + System.lineSeparator()
                        + "Lower number → shorter calculation, possibly less accurate result.", 
                "Significant points",
                0, 
                new ImageIcon(getClass().getResource("/points.png"))
        );
                
    }
     
    private void showMinAngleCosHelp() {
        JOptionPane.showMessageDialog(WindowManager.getDefault().findTopComponent("SingleFaceTab"),
                "Entered number represents how large the angle between normal vector of candidate plane and the vector" + System.lineSeparator()
                        + "of two vertices can be to take into account these vertices while counting the approximate symmetry."  + System.lineSeparator()
                        + System.lineSeparator()
                        + "Higher number → fewer pairs of vertices satisfy the criterion → shorter calculation, possibly less accurate result." + System.lineSeparator()
                        + "Lower number → more pairs of vertices satisfy the criterion → longer calculation, possibly more accurate result.",
                "Minimum angle",
                0, 
                new ImageIcon(getClass().getResource("/angle.png"))
        );
    }
    
    private void showNormalAngleHelp() {                                             
        JOptionPane.showMessageDialog(WindowManager.getDefault().findTopComponent("SingleFaceTab"),
                "Entered number represents how large the angle between normal vector of candidate plane and vector" + System.lineSeparator()
                        + "from subtraction of normal vectors of two vertices can be to take into account these vertices while counting the approximate symmetry." + System.lineSeparator()
                         + System.lineSeparator()
                        + "Higher number → fewer pairs of vertices satisfy the criterion → shorter calculation, possibly less accurate result." + System.lineSeparator()
                        + "Lower number → more pairs of vertices satisfy the criterion → longer calculation, possibly more accurate result.",
                "Minimum normal angle",
                0, 
                new ImageIcon(getClass().getResource("/angle.png"))
        );
    }   
    
    private void showMinCurvHelp() {                                         
        JOptionPane.showMessageDialog(WindowManager.getDefault().findTopComponent("SingleFaceTab"),
                "Entered number represents how similar the curvature in two vertices must be" + System.lineSeparator()
                        + "to take into account these vertices while counting the plane of approximate symmetry." + System.lineSeparator()
                        + "The higher the number is the more similar they must be." + System.lineSeparator()
                        + System.lineSeparator()
                        + "Higher number → fewer pairs of vertices satisfy the criterion → shorter calculation, possibly less accurate result." + System.lineSeparator()
                        + "Lower number → more pairs of vertices satisfy the criterion → longer calculation, possibly more accurate result.",
                "Minimum curvature ratio",
                0, 
                new ImageIcon(getClass().getResource("/curvature.png"))
        );
    }                                        

    private void showRelDistHelp() {
        JOptionPane.showMessageDialog(WindowManager.getDefault().findTopComponent("SingleFaceTab"),
                "Entered number represents how far middle point of two vertices can be from candidate plane of symmetry" + System.lineSeparator()
                        + "to give this plane vote. Plane with highest number of votes is plane of approximate symmetry." + System.lineSeparator()
                        + System.lineSeparator()
                        + "Higher number → more pairs of vertices satisfy the criterion → longer calculation, possibly more accurate result." + System.lineSeparator()
                        + "Lower number → fewer pairs of vertices satisfy the criterion → shorter calculation, possibly less accurate result.",
                "Maximum relative distance from plane",
                0, 
                new ImageIcon(getClass().getResource("/distance.png"))
        );
    }
    
    private void showAveragingHelp() {
        JOptionPane.showMessageDialog(WindowManager.getDefault().findTopComponent("SingleFaceTab"),
                "TO DO",
                "TO DO",
                0, 
                new ImageIcon(getClass().getResource("/distance.png"))
        );
    }
}
