// Copyright 1984-2019 The MathWorks, Inc.
// All Rights Reserved.

import com.mathworks.mps.client.MATLABException;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.text.DecimalFormat;

/**
 * GUI-based Bond Pricing Tool.
 */
public class BondPricingTool extends JFrame {

    public static void main(final String[] args) throws Exception {        
        final BondToolsFactory toolsFactory = new ServerBondToolsFactory( args[0] );
        //final BondToolsFactory toolsFactory = new BondToolsStubFactory();
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    new BondPricingTool( toolsFactory );
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public BondPricingTool (final BondToolsFactory bondToolsFactory) throws Exception {
        super("Bond Pricing Tool");

        // Very important!  Dispose the factory to clean up native resources on shutdown.
        //
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                bondToolsFactory.dispose();
            }
        });

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch (Exception x) {
            // OK
        }

        this.bondTools = bondToolsFactory.newInstance();

        requestSpeedMeter = new RequestSpeedMeter();
        final JPanel panel = new JPanel();
        panel.setLayout(new GridBagLayout());
      
        faceValue = addSlider(panel, 2, 2, "Face Value:", 0, 1000, 1000);
        couponYield = addSlider(panel, 2, 4, "Coupon Yield:", 0, 1000, 50);
        interestRate = addSlider(panel, 2, 6, "Interest Rate (%):", 0, 100, 6);
        numberOfPayments = addSlider(panel, 2, 8, "Number of Payments:", 0, 100, 30);

        JLabel bondPriceLabel = new JLabel("Bond Price:");
        final JPanel horizontalLine = new JPanel() {
            public Dimension getPreferredSize() {
                return new Dimension(1, 1);
            }

            public void paint(Graphics g) {
                g.setColor(UIManager.getColor("control").darker());
                g.fillRect(0, 0, getWidth(), getHeight());
            }
        };

        bondPriceLabel.setFont(bondPriceLabel.getFont().deriveFont(Font.BOLD, 14));
        GridBagConstraints c = new GridBagConstraints();
        c.gridx = 2;
        c.gridy = 10;
        c.gridwidth = 5;
        c.gridheight = 1;
        panel.add(horizontalLine, c);
        c.gridx = 2;
        c.gridy = 12;
        panel.add(bondPriceLabel, c);
        bondPrice = new JLabel();
        bondPrice.setFont(bondPrice.getFont().deriveFont(Font.BOLD, 14));
        c.gridx = 5;
        c.gridy = 12;
        c.gridwidth = 3;
        c.gridheight = 1;
        panel.add(bondPrice, c);
        add(panel);

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);

        recalculate();

        pack();
        setLocationRelativeTo(null);
        setVisible(true);

        statViewer = new JDialog();
        statViewer.setUndecorated(true);
        statViewer.add(requestSpeedMeter);
        statViewer.pack();

        final Runnable statPositioner = new Runnable() {
            public void run() {
                statViewer.setLocation(getX() + getWidth() - statViewer.getWidth(), getY() + getHeight() + 6);
            }
        };

        addComponentListener(new ComponentAdapter() {
            public void componentMoved(ComponentEvent e) {
                statPositioner.run();
            }
        });

        statPositioner.run();

        addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                Point clickLocation = e.getPoint();
                SwingUtilities.convertPointToScreen(clickLocation, BondPricingTool.this);
                Point horizontalLineLocation = horizontalLine.getLocation();
                SwingUtilities.convertPointToScreen(horizontalLineLocation, horizontalLine.getParent());

                if (clickLocation.getY() > horizontalLineLocation.getY()) {
                    statViewer.setVisible(!statViewer.isVisible());
                    toFront();
                }
            }
        });
    }

    private final BondTools bondTools;
    private final JSlider faceValue;
    private final JSlider couponYield;
    private final JSlider interestRate;
    private final JSlider numberOfPayments;
    private final JLabel bondPrice;
    private final RequestSpeedMeter requestSpeedMeter;
    private final double precision = 100.0;
    private final JDialog statViewer;

    private void recalculate () {
        try {
            final long start = System.nanoTime();
            final double price = bondTools.pricecalc( (double)faceValue.getValue(),
                                                      (double)couponYield.getValue(),
                                                      (double)interestRate.getValue() / 100.0,
                                                      (double)numberOfPayments.getValue() );
            final long end = System.nanoTime();
            final double time = Math.floor( (double)(end-start)*precision/1000000.0 ) / precision;

            bondPrice.setText(new DecimalFormat("############0.00").format((double)(int)(price * 100.0)/100.0));
            requestSpeedMeter.logTime(time);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (MATLABException e) {
            e.printStackTrace();
        }
    }
    
    private JSlider addSlider(final JComponent parent, int x, int y, final String name, int min, int max, int init) {
        final JSlider slider = new JSlider(JSlider.HORIZONTAL, min, max, init);
        
        final JTextField textField = new JTextField(5) {
            protected void processKeyEvent(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_UP
                    || e.getKeyCode() == KeyEvent.VK_DOWN
                    || e.getKeyCode() == KeyEvent.VK_LEFT
                    || e.getKeyCode() == KeyEvent.VK_RIGHT
                    || e.getKeyCode() == KeyEvent.VK_PAGE_UP
                    || e.getKeyCode() == KeyEvent.VK_PAGE_DOWN
                    || e.getKeyCode() == KeyEvent.VK_DELETE
                    || e.getKeyCode() == KeyEvent.VK_TAB
                    || e.getKeyCode() == KeyEvent.VK_BACK_SPACE
                    || Character.isDigit(e.getKeyChar())) {

                    super.processKeyEvent(e);
                }
            }
        };

        textField.setFont(textField.getFont().deriveFont(12f));
        textField.setColumns(5);

        final int range = max - min + 1;
        if ( range >= 100) {
            slider.setMajorTickSpacing( ( range - 1) /5 );
            slider.setPaintTicks(true);
            slider.setPaintLabels(true);
        }

        slider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                recalculate();
                String valueString = Integer.toString(slider.getValue());
                if (!textField.getText().equals(valueString)) {
                    textField.setText(valueString);
                } 
            }
        });

        textField.getDocument().addDocumentListener(new DocumentListener() {
            public void insertUpdate(DocumentEvent e) {
                try {
                    int value = Integer.parseInt(textField.getText());
                    if (value >= slider.getMinimum() && value <= slider.getMaximum())
                        slider.setValue(value);
                }
                catch (NumberFormatException x) {
                    // OK
                }
            }

            public void removeUpdate(DocumentEvent e) {
                insertUpdate(e);
            }

            public void changedUpdate(DocumentEvent e) {
                insertUpdate(e);
            }
        });

        textField.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                adjust(e, KeyEvent.VK_UP, 1);
                adjust(e, KeyEvent.VK_DOWN, -1);
                adjust(e, KeyEvent.VK_PAGE_UP, slider.getMaximum() / 10);
                adjust(e, KeyEvent.VK_PAGE_DOWN, -slider.getMaximum() / 10);
            }

            private void adjust(KeyEvent e, int code, int value) {
                int adjusted = Math.min(slider.getMaximum(), Math.max(slider.getValue() + value, slider.getMinimum()));

                boolean selectAll = textField.getSelectionStart() == 0 && textField.getSelectionEnd() == textField.getText().length();
                if (e.getKeyCode() == code) {
                    slider.setValue(adjusted);
                    if (selectAll) {
                        SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                                textField.selectAll();
                            }
                        });
                    }
                }
            }
        });

        textField.addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent e) {
                textField.selectAll();
            }
        });

        textField.setText(Integer.toString(slider.getValue()));

        JLabel label = new JLabel(name);
        label.setFont(label.getFont().deriveFont(12f));
        GridBagConstraints c = new GridBagConstraints();
        c.gridx = x;
        c.gridy = y;
        parent.add(label, c); 
        c.gridx = x + 2;
        parent.add(textField, c); 
        c.gridx = x + 4;
        parent.add(slider, c); 
        return slider;
    }

} // class BondPricingTool
