
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

/*
 * The main work of the application is done in this JPanel-extending class.
 * This allows the java class to be run as a stand-alone application or
 * as an applet within a web browser.
 */
class RetirementCalculatorPanel
    extends JPanel
    implements ActionListener, FocusListener
{
    private RetirementCalculatorModel myModel;

    private JComponent makeDivider() { return new JLabel(" "); }

    private HashMap<String, JComponent> myFields;
    private JButton myCalculateButton;
    private JButton myPrintButton;
    private JButton myResetButton;
    private JLabel myResultBox;
    private JTextField myNameField;

    class ComboChoice
    {
        private String myDesc;
        private int myVal;
        ComboChoice(int val, String desc)
        {
            myDesc = desc;
            myVal = val;
        }
        public String toString() { return myDesc; }
        public int getVal() { return myVal; }
    }

    private final ComboChoice[] retirement_age_choices = {
        new ComboChoice(55, "55"),
        new ComboChoice(60, "60"),
        new ComboChoice(65, "65"),
        new ComboChoice(70, "70")
    };

    private final ComboChoice[] life_expectancy_choices = {
        new ComboChoice(82, "82 (Male; 50th percentile)"),
        new ComboChoice(86, "86 (Female; 50th percentile)"),
        new ComboChoice(89, "89 (Male; 75th percentile)"),
        new ComboChoice(92, "92 (Female; 75th percentile)"),
        new ComboChoice(94, "94 (Male; 90th percentile)"),
        new ComboChoice(97, "97 (Female; 90th percentile)")
    };

    public RetirementCalculatorPanel()
    {
        super(new BorderLayout());

        myModel = new RetirementCalculatorModel();
        myFields = new HashMap<String, JComponent>();
        JPanel inner = new JPanel(new GridBagLayout());
        JComponent field;
        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.HORIZONTAL;
        c.ipadx = 2;
        c.gridwidth = 1;
        c.gridheight = 1;
        c.gridx = 0;
        c.gridy = 0;

        c.weightx = 0.0;
        inner.add(new JLabel("What is your name?", SwingConstants.RIGHT), c);
        c.gridx++;
        c.weightx = 1.0;
        myNameField = new JTextField();
        myNameField.addFocusListener(this);
        inner.add(myNameField, c);
        c.gridx--;
        c.gridy++;

        c.weightx = 0.0;
        inner.add(new JLabel("What is your age?", SwingConstants.RIGHT), c);
        c.gridx++;
        c.weightx = 1.0;
        field = new JTextField();
        field.addFocusListener(this);
        myFields.put("age", field);
        inner.add(field, c);
        c.gridx--;
        c.gridy++;

        c.gridwidth = 2;
        inner.add(makeDivider(), c);
        c.gridwidth = 1;
        c.gridy++;

        c.weightx = 0.0;
        inner.add(new JLabel("How much annual income will you want in retirement?", SwingConstants.RIGHT), c);
        c.gridx++;
        c.weightx = 1.0;
        field = new JTextField();
        field.addFocusListener(this);
        myFields.put("income wanted", field);
        inner.add(field, c);
        c.gridx--;
        c.gridy++;

        c.weightx = 0.0;
        inner.add(new JLabel("What is your current annual income?",
                    SwingConstants.RIGHT), c);
        c.gridx++;
        c.weightx = 1.0;
        field = new JTextField();
        field.addFocusListener(this);
        myFields.put("current income", field);
        inner.add(field, c);
        c.gridx--;
        c.gridy++;

        c.gridwidth = 2;
        inner.add(makeDivider(), c);
        c.gridy++;
        inner.add(new JLabel("What is your expected annual income after retirement -"), c);
        c.gridwidth = 1;
        c.gridy++;

        c.weightx = 0.0;
        inner.add(new JLabel(" - from an employer pension plan?",
                    SwingConstants.RIGHT), c);
        c.gridx++;
        c.weightx = 1.0;
        field = new JTextField();
        field.addFocusListener(this);
        myFields.put("employer pension", field);
        inner.add(field, c);
        c.gridx--;
        c.gridy++;

        c.weightx = 0.0;
        inner.add(new JLabel(" - from a part-time job?",
                    SwingConstants.RIGHT), c);
        c.gridx++;
        c.weightx = 1.0;
        field = new JTextField();
        field.addFocusListener(this);
        myFields.put("part time income", field);
        inner.add(field, c);
        c.gridx--;
        c.gridy++;

        c.weightx = 0.0;
        inner.add(new JLabel(" - other?", SwingConstants.RIGHT), c);
        c.gridx++;
        c.weightx = 1.0;
        field = new JTextField();
        field.addFocusListener(this);
        myFields.put("other income", field);
        inner.add(field, c);
        c.gridx--;
        c.gridy++;

        c.gridwidth = 2;
        inner.add(makeDivider(), c);
        c.gridwidth = 1;
        c.gridy++;

        c.weightx = 0.0;
        inner.add(new JLabel("How much savings do you have to date?",
                    SwingConstants.RIGHT), c);
        c.gridx++;
        c.weightx = 1.0;
        field = new JTextField();
        field.addFocusListener(this);
        myFields.put("savings", field);
        inner.add(field, c);
        c.gridx--;
        c.gridy++;

        c.weightx = 0.0;
        inner.add(new JLabel("At what age do you expect to retire?",
                    SwingConstants.RIGHT), c);
        c.gridx++;
        c.weightx = 1.0;
        field = new JComboBox(retirement_age_choices);
        myFields.put("retirement age", field);
        inner.add(field, c);
        c.gridx--;
        c.gridy++;

        JPanel p = new JPanel(new BorderLayout());
        p.setAlignmentX(Component.RIGHT_ALIGNMENT);
        p.add(new JLabel("What is your life expectancy?",
                    SwingConstants.RIGHT), BorderLayout.CENTER);
        field = new JComboBox(life_expectancy_choices);
        myFields.put("life expectancy", field);
        p.add(field, BorderLayout.EAST);
        c.gridwidth = 2;
        inner.add(p, c);
        c.gridwidth = 1;
        c.gridy++;

        c.gridwidth = 2;
        inner.add(makeDivider(), c);
        c.gridwidth = 1;
        c.gridy++;

        JPanel buttons = new JPanel();
        buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
        buttons.add(Box.createHorizontalGlue());
        myCalculateButton = new JButton("Calculate");
        myCalculateButton.addActionListener(this);
        buttons.add(myCalculateButton);
        buttons.add(Box.createRigidArea(new Dimension(10, 0)));
        myPrintButton = new JButton("Print");
        myPrintButton.addActionListener(this);
        buttons.add(myPrintButton);
        buttons.add(Box.createRigidArea(new Dimension(10, 0)));
        myResetButton = new JButton("Reset");
        myResetButton.addActionListener(this);
        buttons.add(myResetButton);
        buttons.add(Box.createHorizontalGlue());
        c.gridwidth = 2;
        inner.add(buttons, c);
        c.gridwidth = 1;
        c.gridy++;

        c.gridwidth = 2;
        inner.add(makeDivider(), c);
        c.gridwidth = 1;
        c.gridy++;

        c.gridwidth = 2;
        myResultBox = new JLabel(" ", SwingConstants.CENTER);
        inner.add(myResultBox, c);
        c.gridwidth = 1;
        c.gridy++;

        JScrollPane scrollpane = new JScrollPane(inner,
                ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        add(scrollpane);

        reset();
    }

    public void actionPerformed(ActionEvent e)
    {
        if      (e.getSource() == myCalculateButton)
            calculate();
        else if (e.getSource() == myResetButton)
            reset();
        else if (e.getSource() == myPrintButton)
            print();
    }

    public void focusGained(FocusEvent e)
    {
        if (e.getComponent().getClass() == JTextField.class)
        {
            ((JTextField) e.getComponent()).selectAll();
        }
    }

    public void focusLost(FocusEvent e)
    {
    }

    private void calculate()
    {
        Iterator it = myFields.keySet().iterator();
        while (it.hasNext())
        {
            String key = (String) it.next();
            JComponent comp = myFields.get(key);
            if      (comp.getClass() == JTextField.class)
            {
                JTextField tf = (JTextField) comp;
                myModel.setField(key, Double.parseDouble(tf.getText()));
            }
            else if (comp.getClass() == JComboBox.class)
            {
                JComboBox cb = (JComboBox) comp;
                myModel.setField(key,
                        ((ComboChoice) cb.getSelectedItem()).getVal());
            }
        }

        myModel.calculate();
        myResultBox.setText("<html>The total amount of savings needed at retirement is $" +
                String.format("%,.02f", myModel.getField("total additional savings needed")) +
                ".<br/> The annual amount of savings needed is $" +
                String.format("%,.02f", myModel.getField("annual savings needed")) + "</html>");
    }

    private void reset()
    {
        myModel.reset();
        Iterator it = myFields.keySet().iterator();
        while (it.hasNext())
        {
            String key = (String) it.next();
            JComponent comp = myFields.get(key);
            if      (comp.getClass() == JTextField.class)
            {
                JTextField tf = (JTextField) comp;
                double val = myModel.getField(key);
                tf.setText(String.format(
                            key.equals("age") ? "%.0f" : "%.2f", val));
            }
            else if (comp.getClass() == JComboBox.class)
            {
                JComboBox cb = (JComboBox) comp;
                int val = (int) myModel.getField(key);
                ComboChoice[] ccp = null;
                if (key.equals("retirement age"))
                    ccp = retirement_age_choices;
                else if (key.equals("life expectancy"))
                    ccp = life_expectancy_choices;
                if (ccp != null)
                {
                    for (ComboChoice cc : ccp)
                    {
                        if (cc.getVal() == val)
                        {
                            cb.setSelectedItem(cc);
                            break;
                        }
                    }
                }
            }
        }
        myNameField.setText("");
        myResultBox.setText(" ");
    }

    private String fmt(String fmt, String field)
    {
        return String.format(fmt, myModel.getField(field));
    }

    private void print()
    {
        calculate();
        String endl = "<br/>";
        String msg = "<html>";
        msg += "Name: " + myNameField.getText() + endl;
        msg += "Age: " + fmt("%.0f", "age") + endl;
        msg += "Retirement age: " + fmt("%.0f", "retirement age") + endl;
        msg += "Current income: $" + fmt("%,.02f", "current income") + endl;
        msg += "Desired annual income: $" +
            fmt("%,.02f", "income wanted") + endl;
        msg += endl;
        msg += "Social Security benefit: $" +
            fmt("%,.02f", "social security") + endl;
        msg += "Traditional Employer Pension: $" +
            fmt("%,.02f", "employer pension") + endl;
        msg += "Part-time income: $" + fmt("%,.02f", "part time income") + endl;
        msg += "Other income: $" + fmt("%,.02f", "other income") + endl;
        msg += "Amount to make up each retirement year: $" +
            fmt("%,.02f", "amount to make up") + endl;
        msg += endl;
        msg += "Total amount to save: $" +
            fmt("%,.02f", "amount to save") + endl;
        msg += "Early retirement addition: $" +
            fmt("%,.02f", "early retirement penalty") + endl;
        msg += "Estimated savings at retirement: $" +
            fmt("%,.02f", "estimated savings") + endl;
        msg += endl;
        msg += "Total additional savings needed at retirement: $" +
            fmt("%,.02f", "total additional savings needed") + endl;
        msg += "Required annual savings: $" +
            fmt("%,.02f", "annual savings needed") + endl;
        msg += "</html>";
        JOptionPane.showMessageDialog(null, msg,
                "Basic Retirement Calculator", JOptionPane.PLAIN_MESSAGE);
    }
}

/*
 * The "main" class of this file allows the app to be run as an applet
 * but also provides a "main" method to run as an application.
 */
public class RetirementCalculator extends JApplet
{
    final static private String PROG_NAME = "Josh's Retirement Calculator";

    RetirementCalculatorPanel panel;

    public void init()
    {
        setLayout(new BorderLayout());
        panel = new RetirementCalculatorPanel();
        add(panel, BorderLayout.CENTER);
    }

    public static void main(String[] args)
    {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run()
            {
                JFrame frame = new JFrame(PROG_NAME);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                RetirementCalculatorPanel panel =
                    new RetirementCalculatorPanel();
                frame.getContentPane().add(panel);

                frame.setPreferredSize(new Dimension(600, 450));

                frame.pack();
                frame.setVisible(true);
            }
        });
    }
}
