
package uk.co.wingpath.registration;

import java.util.*;
import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.math.BigInteger;
import uk.co.wingpath.util.*;

class Gui
{
    private String productName;
    private Details details;
    private Key key;
    private Details oldVersion;
    private String licence;
    private Registration.Callback callback;
    private JFrame dialog;
    private JRootPane rootPane;
    private Container contentPane;
    private CardLayout cardLayout;
    private Map<String,Card> cards;
    private Action cancelAction;

    Gui (String productName,
        final Details details, Details oldVersion,
        final Registration.Callback callback)
    {
        this.productName = productName;
        this.details = details;
        this.oldVersion = oldVersion;
        this.callback = callback;
        key = null;
        licence = "";

        dialog = new JFrame ();
        URL iconUrl =
            Gui.class.getClassLoader ().getResource ("image/wings.png");
        dialog.setIconImage (new ImageIcon (iconUrl).getImage ());
        dialog.setTitle (productName + " Registration - Wingpath");

        contentPane = dialog.getContentPane ();
        cardLayout = new CardLayout ();
        contentPane.setLayout (cardLayout);

        cancelAction =
            new AbstractAction ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    dialog.setVisible (false);
                    boolean registered = details.status == Details.EVAL ||
                        details.status == Details.FULL;
                    boolean full = details.status == Details.FULL;
                    if (!registered)
                        System.exit (9);
                    if (details.writeRequired)
                        Registration.saveRegistration (details);
                    Registration.startChecker (details.product,
                        details.majorVersion);
                    callback.registered (full);
                }
            };

        dialog.setDefaultCloseOperation (WindowConstants.DO_NOTHING_ON_CLOSE);
        dialog.addWindowListener (
            new WindowAdapter ()
            {
                public void windowClosing (WindowEvent e)
                {
                    cancelAction.actionPerformed (null);
                }
            });

        rootPane = dialog.getRootPane ();
        InputMap inputMap = rootPane.getInputMap (
            JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        inputMap.put (KeyStroke.getKeyStroke (KeyEvent.VK_ESCAPE, 0), "cancel");
        rootPane.getActionMap ().put ("cancel", cancelAction);

        cards = new HashMap<String,Card> ();
        addCard (new Eval1Card ());
        addCard (new Eval2Card ());
        addCard (new Full1Card ());
        addCard (new Full2Card ());
        addCard (new RemindCard ());
        addCard (new DateCard ());
        addCard (new UpgradeVCard ());
        addCard (new UpgradeLCard ());

        dialog.pack ();

        // Position dialog in centre of screen

        dialog.setLocationRelativeTo (null);
    }

    private void addCard (Card card)
    {
        contentPane.add (card.getPanel (), card.getTag ());
        cards.put (card.getTag (), card);
    }

    void show (String tag)
    {
        Card card = cards.get (tag);
        if (card == null)
            throw new IllegalArgumentException ("Bad tag: " + tag);
        cardLayout.show (contentPane, card.getTag ());
        card.initialize ();
        dialog.setVisible (true);
    }

    void browse (String page, String query)
    {
        try
        {
            URI uri = new URI ("https://wingpath.co.uk/" + page + "?" + query);
            Browser.browse (uri);
        }
        catch (URISyntaxException ex)
        {
// ex.printStackTrace ();
        }
    }

    String encodeParam (String value)
    {
        value = value.trim ();
        try
        {
            value = URLEncoder.encode (value, "UTF-8");
        }
        catch (UnsupportedEncodingException e)
        {
// e.printStackTrace ();
        }
        return value;
    }

    // Quick check whether licence number looks valid.
    public boolean isValidLicence (String str)
    {
        return 
            str.length () > 2 &&
            str.charAt (1) == Crypt.CHAR_LICENCE &&
            Crypt.decrypt (str.substring (2), str.charAt (0)) != null;
    }

    private void saveKey ()
    {
        details.user = details.newUser;
        details.company = details.newCompany;
        details.setKey (key);
        Registration.saveRegistration (details);

        switch (details.status)
        {
        case Details.FULL:
            dialog.setVisible (false);
            Registration.startChecker (details.product, details.majorVersion);
            callback.registered (true);
            break;

        case Details.EVAL:
            dialog.setVisible (false);
            Registration.startChecker (details.product, details.majorVersion);
            callback.registered (false);
            break;

        default:
            JOptionPane.showMessageDialog (dialog,
                "Your evaluation period for\n" +
                productName + " has expired.",
                "Error", JOptionPane.ERROR_MESSAGE);
            show (oldVersion == null ? "full1" : "upgradev");
            break;
        }
    }

    private interface Card
    {
        String getTag ();
        JPanel getPanel ();
        void initialize ();
    }

    private interface FieldListener
    {
        void fieldChanged ();
    }

    private class ButtonPanel
        extends JPanel
    {
        ButtonPanel ()
        {
            setLayout (new FlowLayout (FlowLayout.RIGHT));
        }

        JButton addButton (String label, ActionListener listener)
        {
            JButton button = new JButton (label);
            button.setRequestFocusEnabled (false);
            button.addActionListener (listener);
            add (button);
            return button;
        }
    }

    private static GridBagConstraints createConstraints (int gridx, int gridy)
    {
        GridBagConstraints constraints = new GridBagConstraints ();
        constraints.gridx = gridx;
        constraints.gridy = gridy;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.weightx = 0.0;
        constraints.weighty = 0.0;
        constraints.insets = new Insets (2, 5, 2, 5);
        constraints.anchor = GridBagConstraints.NORTHWEST;
        constraints.fill = GridBagConstraints.NONE;
        return constraints;
    }

    private JTextField addField (String name, JPanel panel, int gridy,
        boolean editable, final FieldListener listener)
    {
        JLabel label = new JLabel (name);
        JTextField field = new JTextField (30);
        field.setEditable (editable);
        label.setLabelFor (field);
        GridBagConstraints constraints = createConstraints (0, gridy);
        panel.add (label, constraints);
        constraints.gridx = 1;
        panel.add (field, constraints);

        field.getDocument ().addDocumentListener (
            new DocumentListener ()
            {
                public void insertUpdate (DocumentEvent e)
                {
                    listener.fieldChanged ();
                }

                public void removeUpdate (DocumentEvent e)
                {
                    listener.fieldChanged ();
                }

                public void changedUpdate (DocumentEvent e)
                {
                    listener.fieldChanged ();
                }
            });

        field.addActionListener (
            new ActionListener ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    listener.fieldChanged ();
                }
            });

        field.addFocusListener (
            new FocusListener ()
            {
                public void focusGained (FocusEvent e)
                {
                }

                public void focusLost (FocusEvent e)
                {
                    if (!e.isTemporary ())
                        listener.fieldChanged ();
                }
            });

        return field;
    }

    private void addText (JPanel panel, int gridy, String text)
    {
        JEditorPane pane = new JEditorPane ();
        pane.putClientProperty (JEditorPane.HONOR_DISPLAY_PROPERTIES,
            Boolean.TRUE);
        pane.setBackground (panel.getBackground ());
        JLabel label = new JLabel ();
        pane.setFont (label.getFont ());
        pane.setEditable (false);
        pane.setContentType ("text/html");
        pane.setText ("<html><br>" + text);
        // JLabel label = new JLabel ("<html><br>" + text);
        GridBagConstraints constraints = createConstraints (0, gridy);
        constraints.gridwidth = 2;
        // panel.add (label, constraints);
        panel.add (pane, constraints);
    }

    private ButtonPanel addButtonPanel (JPanel panel, int gridy)
    {
        ButtonPanel buttonPanel = new ButtonPanel ();
        GridBagConstraints constraints = createConstraints (1, gridy);
        constraints.anchor = GridBagConstraints.EAST;
        panel.add (buttonPanel, constraints);
        return buttonPanel;
    }

    /*
    * Card displayed when the program is first run (i.e. the user has not yet entered
    * a registration key).
    * It has fields for the User Name and Company Name, and possibly a Licence
    * field. The Licence field is provided for users who have purchased a
    * licence without evaluating it on this machine. The Licence field is not
    * displayed if the user has a full licence for an older version of the
    * product (as they would then upgrade rather than purchase a new licence).
    * There is a Continue button, which displays the Eval2 card
    * if no licence (or an obviously invalid licence) has been entered, and
    * displays the Full2 card if an apparently valid licence has been entered.
    */
    private class Eval1Card
        implements Card, FieldListener
    {
        private JPanel panel;
        private JButton applyButton;
        private JTextField userField;
        private JTextField companyField;
        private JTextField licenceField;

        Eval1Card ()
        {
            panel = new JPanel ();
            panel.setLayout (new GridBagLayout ());
            int gridy = 0;

            String text =
                "Before you can use " + productName + " you need to obtain a<br>" +
                "registration key (evaluation keys are free).<br><br>" +
                "Please enter your name and company name (enter 'None'<br>" +
                "for the company name if you are a private individual).<br><br>";
            if (oldVersion == null)
            {
                text +=
                    "If you have purchased a licence, enter the licence number.<br><br>" +
                    "If you want an evaluation key, leave the licence field empty.<br><br>";
            }
            text += "Then click the 'Continue' button.<br><br>";
            addText (panel, gridy++, text);
            userField = addField ("User", panel, gridy++, true, this);
            companyField = addField ("Company", panel, gridy++, true, this);
            if (oldVersion == null)
            {
                licenceField = addField ("Licence", panel, gridy++, true, this);
            }
            else
            {
                licenceField = null;
            }

            ButtonPanel buttonPanel = addButtonPanel (panel, gridy++);
            applyButton = buttonPanel.addButton ("Continue",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        details.newUser = userField.getText ().trim ();
                        details.newCompany = companyField.getText ().trim ();
                        if (licenceField == null)
                            licence = "";
                        else
                            licence = licenceField.getText ().trim ();
                        if (details.newUser.equals ("") ||
                            details.newCompany.equals (""))
                        {
                            JOptionPane.showMessageDialog (dialog,
                                "You must enter a user name and a company name",
                                "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                        Registration.saveRegistration (details);
                        if (licence.equals ("") || !isValidLicence (licence))
                            show ("eval2");
                        else
                            show ("full2");
                    }
                });
            applyButton.setEnabled (false);

            buttonPanel.addButton ("Cancel", cancelAction);
        }

        public void initialize ()
        {
            userField.setText (details.newUser);
            companyField.setText (details.newCompany);
            userField.requestFocusInWindow ();
            rootPane.setDefaultButton (applyButton);
        }

        public void fieldChanged ()
        {
            String user = userField.getText ().trim ();
            String company = companyField.getText ().trim ();
            applyButton.setEnabled (!user.equals ("") && !company.equals (""));
        }

        public JPanel getPanel ()
        {
            return panel;
        }

        public String getTag ()
        {
            return "eval1";
        }

        public boolean isEnabled ()
        {
            return true;
        }

        public boolean hasUnappliedChanges ()
        {
            return false;
        }

        public boolean hasError ()
        {
            return false;
        }
    }

    /*
    * Card displayed when the user has entered the User Name and Company Name
    * in Eval1 card, but has not entered an apparently valid licence.
    * It displays the Product ID, and has a 'Get Key' that displays the
    * registration web page in the user's browser. If the user has a full
    * registration for an earlier version of the program, there is an 'Update'
    * button that dispays the version-upgrade web page.
    * There is a field for entering a registration key, and a 'Register'
    * button, which checks the registration key and continues running of the
    * program if OK.
    */
    private class Eval2Card
        implements Card, FieldListener
    {
        private JPanel panel;
        private JButton getKeyButton;
        private JButton upgradeButton;
        private JButton applyButton;
        private JTextField userField;
        private JTextField companyField;
        private JTextField idField;
        private JTextField keyField;

        Eval2Card ()
        {
            panel = new JPanel ();
            panel.setLayout (new GridBagLayout ());
            int gridy = 0;

            userField = addField ("User", panel, gridy++, false, this);
            companyField = addField ("Company", panel, gridy++, false, this);
            idField = addField ("Product ID", panel, gridy++, false, this);

            String text;
            if (oldVersion == null)
            {
                text =
                    "Please click 'Get key' to go to the registration page of the<br>" +
                    "Wingpath web site (https://wingpath.co.uk/eval_register.php)<br>" +
                    "and use the details displayed above to obtain an<br>" +
                    "evaluation registration key.<br>";
            }
            else
            {
                text =
                    "If you want to evaluate " + productName + ", click 'Get Key' to<br>" +
                    "to go to the registration page of the Wingpath<br>" +
                    "web site (https://wingpath.co.uk/eval_register.php)<br>" +
                    "and use the details displayed above to obtain an<br>" +
                    "evaluation registration key.<br>" +
                    "If you want to upgrade " + productName + ", click 'Upgrade' to<br>" +
                    "to go to the upgrade page of the Wingpath<br>" +
                    "web site (https://wingpath.co.uk/upgradev.php)<br>" +
                    "and use the details displayed above to purchase an<br>" +
                    "upgraded registration key.<br>";
            }
            addText (panel, gridy++, text);

            ButtonPanel buttonPanel = addButtonPanel (panel, gridy++);
            getKeyButton = buttonPanel.addButton ("Get key",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        String query =
                            "user=" + encodeParam (userField.getText ()) + "&" +
                            "company=" + encodeParam (companyField.getText ()) + "&" +
                            "code=" + encodeParam (idField.getText ());
                        browse ("eval_register.php", query);
                    }
                });

            if (oldVersion != null)
            {
                upgradeButton = buttonPanel.addButton ("Upgrade",
                    new ActionListener ()
                    {
                        public void actionPerformed (ActionEvent e)
                        {
                            String query =
                                "user=" + encodeParam (userField.getText ()) + "&" +
                                "company=" + encodeParam (companyField.getText ()) + "&" +
                                "code=" + encodeParam (idField.getText ());
                            browse ("upgradev.php", query);
                        }
                    });
            }

            addText (panel, gridy++,
                "Enter the registration key below and then " +
                "click 'Register'.<br>"
                );

            keyField = addField ("Key", panel, gridy++, true, this);

            buttonPanel = addButtonPanel (panel, gridy++);
            applyButton = buttonPanel.addButton ("Register",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        String keyStr = keyField.getText ().trim ();
                        if (keyStr.equals (""))
                        {
                            JOptionPane.showMessageDialog (dialog,
                                "You must enter a registration key",
                                "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }

                        key = Key.create (keyStr);
                        if (key == null ||
                            !key.isValid (details, details.newUser,
                                details.newCompany))
                        {
                            JOptionPane.showMessageDialog (dialog,
                                "Invalid key",
                                "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }

                        if (key.checkDate ())
                            saveKey ();
                        else
                            show ("date");
                    }
                });
            applyButton.setEnabled (false);

            buttonPanel.addButton ("Cancel", cancelAction);
        }

        public void initialize ()
        {
            userField.setText (details.newUser);
            companyField.setText (details.newCompany);
            idField.setText (details.getProductId (oldVersion));
            keyField.requestFocusInWindow ();
            rootPane.setDefaultButton (getKeyButton);
        }

        public void fieldChanged ()
        {
            String keyStr = keyField.getText ().trim ();
            applyButton.setEnabled (!keyStr.equals (""));
            rootPane.setDefaultButton (
                keyStr.equals ("") ? getKeyButton : applyButton);
        }

        public JPanel getPanel ()
        {
            return panel;
        }

        public String getTag ()
        {
            return "eval2";
        }

        public boolean isEnabled ()
        {
            return true;
        }

        public boolean hasUnappliedChanges ()
        {
            return false;
        }

        public boolean hasError ()
        {
            return false;
        }
    }

    /*
    * Card displayed each time the program is started during the evaluation
    * period.
    * It asks whether the user wants to purchase a licence or an upgrade, and
    * if so displays the Full1 card. If not, it continues with running the
    * program.
    */
    private class RemindCard
        implements Card
    {
        private JPanel panel;
        private JButton yesButton;

        RemindCard ()
        {
            panel = new JPanel ();
            panel.setLayout (new GridBagLayout ());
            int gridy = 0;
            int daysLeft = details.daysLeft ();

            String text =
                "Your evaluation period for " + productName + "<br>" +
                "will expire " +
                    (daysLeft <= 1 ?  "today." :
                        "in " + daysLeft + " days.<br>") +
                "<br>" +
                "To continue using " + productName + " after the<br>" +
                " evaluation period, you need to purchase<br>";
            if (oldVersion == null)
            {
                text += " a product licence.<br>" +
                "<br>" +
                "Do you want to purchase a licence now?<br>";
            }
            else
            {
                text += " an upgrade.<br>" + 
                "<br>" +
                "Do you want to purchase an upgrade now?<br>";
            }
            addText (panel, gridy++, text);

            ButtonPanel buttonPanel = addButtonPanel (panel, gridy++);
            yesButton = buttonPanel.addButton ("Yes",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        show (oldVersion == null ? "full1" : "upgradev");
                    }
                });

            buttonPanel.addButton ("No", cancelAction);
        }

        public void initialize ()
        {
            rootPane.setDefaultButton (yesButton);
        }

        public JPanel getPanel ()
        {
            return panel;
        }

        public String getTag ()
        {
            return "remind";
        }

        public boolean isEnabled ()
        {
            return true;
        }

        public boolean hasUnappliedChanges ()
        {
            return false;
        }

        public boolean hasError ()
        {
            return false;
        }
    }

    /*
    * Card displayed if the computer date appears to be wrongly set when an
    * evaluation key is entered.
    * It gives the user the opportunity to correct the date before the key
    * is used.
    */
    private class DateCard
        implements Card
    {
        private JPanel panel;
        private JButton continueButton;
        private JButton cancelButton;

        DateCard ()
        {
            panel = new JPanel ();
            panel.setLayout (new GridBagLayout ());
            int gridy = 0;

            addText (panel, gridy++,
                "The date on your computer may be set incorrectly.<br><br>" +
                "If you do not correct the date before continuing,<br>" +
                "the evaluation period may be affected.<br>"
                );

            ButtonPanel buttonPanel = addButtonPanel (panel, gridy++);
            continueButton = buttonPanel.addButton ("Continue",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        saveKey ();
                    }
                });

            cancelButton = buttonPanel.addButton ("Cancel", cancelAction);
        }

        public void initialize ()
        {
            rootPane.setDefaultButton (cancelButton);
        }

        public JPanel getPanel ()
        {
            return panel;
        }

        public String getTag ()
        {
            return "date";
        }

        public boolean isEnabled ()
        {
            return true;
        }

        public boolean hasUnappliedChanges ()
        {
            return false;
        }

        public boolean hasError ()
        {
            return false;
        }
    }

    /*
    * Card displayed when the evaluation period has expired, when the
    * user indicates in RemindCard that they want to purchase a licence or
    * upgrade, and when the user selects Help->Register/Upgrade.
    * If the user has a full registration for an earlier version of the
    * product, the UpgradeV card is displayed instead of this one.
    * This card has fields for the User Name, Company Name and Licence number,
    * and a 'Continue' button, which displays the Full2 card.
    */
    private class Full1Card
        implements Card, FieldListener
    {
        private JPanel panel;
        private JButton purchaseButton;
        private JButton applyButton;
        private JTextField userField;
        private JTextField companyField;
        private JTextField licenceField;

        Full1Card ()
        {
            panel = new JPanel ();
            panel.setLayout (new GridBagLayout ());
            int gridy = 0;

            addText (panel, gridy++,
                "To continue using " + productName +
                    " after the evaluation<br>" +
                "period, you need to purchase a licence.<br>" +
                "Please click 'Purchase' to go to the purchase page of the<br>" +
                "Wingpath web site (https://wingpath.co.uk/purchase.php).<br>");

            ButtonPanel buttonPanel = addButtonPanel (panel, gridy++);
            purchaseButton = buttonPanel.addButton ("Purchase",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        int product = ((details.majorVersion - 1) *
                            Crypt.MAX_PRODUCT + details.product);
                        String query = "product=" + product;
                        browse ("purchase.php", query);
                    }
                });

            addText (panel, gridy++,
                "When you have received your licence number,<br>" +
                "enter your name, company name (enter 'None' for<br>" +
                "the company name if you are a private individual)<br>" +
                "and licence number below, and click the 'Continue'<br>" +
                "button.<br><br>"
                );

            userField = addField ("User", panel, gridy++, true, this);
            companyField = addField ("Company", panel, gridy++, true, this);
            licenceField = addField ("Licence", panel, gridy++, true, this);

            buttonPanel = addButtonPanel (panel, gridy++);
            applyButton = buttonPanel.addButton ("Continue",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        details.newUser = userField.getText ().trim ();
                        details.newCompany = companyField.getText ().trim ();
                        licence = licenceField.getText ().trim ();
                        if (details.newUser.equals ("") ||
                            details.newCompany.equals ("") ||
                            licence.equals (""))
                        {
                            JOptionPane.showMessageDialog (dialog,
                                "You must enter a user name, a company name, " +
                                    "and the licence number",
                                "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                        Registration.saveRegistration (details);
                        if (licence.equals ("") || !isValidLicence (licence))
                            show ("eval2");
                        else
                            show ("full2");
                    }
                });

            buttonPanel.addButton ("Cancel", cancelAction);
        }

        public void initialize ()
        {
            if (oldVersion != null)
            {
                show ("upgradev");
                return;
            }
            userField.setText (details.newUser);
            companyField.setText (details.newCompany);
            licenceField.setText (licence);
            userField.requestFocusInWindow ();
            rootPane.setDefaultButton (purchaseButton);
        }

        public void fieldChanged ()
        {
            String user = userField.getText ().trim ();
            String company = companyField.getText ().trim ();
            String licence = licenceField.getText ().trim ();
            boolean entered = 
                !user.equals ("") &&
                !company.equals ("") &&
                !licence.equals ("");
            applyButton.setEnabled (entered);
            rootPane.setDefaultButton (entered ? applyButton : purchaseButton);
        }

        public JPanel getPanel ()
        {
            return panel;
        }

        public String getTag ()
        {
            return "full1";
        }

        public boolean isEnabled ()
        {
            return true;
        }

        public boolean hasUnappliedChanges ()
        {
            return false;
        }

        public boolean hasError ()
        {
            return false;
        }
    }

    /*
    * Card displayed when the user has entered the User Name, Company Name and
    * licence in Full1Card. It displays the Product ID.
    * It has a 'Get key' button, which opens the website registration page
    * in the browser, and a 'Register' button, which checks the registration
    * key and continues running of the program if OK.
    */
    private class Full2Card
        implements Card, FieldListener
    {
        private JPanel panel;
        private JButton getKeyButton;
        private JButton applyButton;
        private JTextField userField;
        private JTextField companyField;
        private JTextField licenceField;
        private JTextField idField;
        private JTextField keyField;

        Full2Card ()
        {
            panel = new JPanel ();
            panel.setLayout (new GridBagLayout ());
            int gridy = 0;

            userField = addField ("User", panel, gridy++, false, this);
            companyField = addField ("Company", panel, gridy++, false, this);
            licenceField = addField ("Licence", panel, gridy++, false, this);
            idField = addField ("Product ID", panel, gridy++, false, this);

            addText (panel, gridy++,
                "Please click 'Get key' to go to the registration page of the<br>" +
                "Wingpath web site (https://wingpath.co.uk/full_register.php)<br>" +
                "and use the details displayed above to obtain a full<br>" +
                "registration key.<br>");

            ButtonPanel buttonPanel = addButtonPanel (panel, gridy++);
            getKeyButton = buttonPanel.addButton ("Get key",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        String query =
                            "user=" + encodeParam (userField.getText ()) + "&" +
                            "company=" + encodeParam (companyField.getText ()) + "&" +
                            "code=" + encodeParam (idField.getText ()) + "&" +
                            "licence=" + encodeParam (licenceField.getText ());
                        browse ("full_register.php", query);
                    }
                });

            addText (panel, gridy++,
                "Enter the full registration key below and then " +
                "click 'Register'.<br>"
                );

            keyField = addField ("Key", panel, gridy++, true, this);

            buttonPanel = addButtonPanel (panel, gridy++);
            applyButton = buttonPanel.addButton ("Register",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        String keyStr = keyField.getText ().trim ();
                        if (keyStr.equals (""))
                        {
                            JOptionPane.showMessageDialog (dialog,
                                "You must enter a registration key",
                                "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }

                        key = Key.create (keyStr);
                        if (key == null ||
                            !key.isValid (details, details.newUser,
                                details.newCompany))
                        {
                            JOptionPane.showMessageDialog (dialog,
                                "Invalid key",
                                "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                        if (!key.isFullLicence () && !key.isManual ())
                        {
                            // User is re-registering with another evaluation
                            // key. Don't allow it if old key has expired.

                            if (details.status != Details.EVAL)
                            {
                                JOptionPane.showMessageDialog (dialog,
                                    "Your evaluation period for\n" +
                                    "this product has expired.",
                                    "Error", JOptionPane.ERROR_MESSAGE);
                                return;
                            }

                            // The new key should have the same expiry date
                            // as the old key, if it was obtained from the
                            // web-site using the same email address. If the
                            // expiry dates are different, ignore the new key,
                            // but don't tell the user - if they are trying
                            // to cheat, the less they know the better.

                            if (key.expiry != details.key.expiry)
                            {
                                dialog.setVisible (false);
                                if (details.writeRequired)
                                    Registration.saveRegistration (details);
                                Registration.startChecker (details.product,
                                    details.majorVersion);
                                callback.registered (false);
                                return;
                            }

                            // Otherwise, allow the user to change the user &
                            // company names.
                        }

                        if (key.checkDate ())
                            saveKey ();
                        else
                            show ("date");
                    }
                });

            buttonPanel.addButton ("Cancel", cancelAction);
        }

        public void initialize ()
        {
            userField.setText (details.newUser);
            companyField.setText (details.newCompany);
            licenceField.setText (licence);
            idField.setText (details.getProductId (oldVersion));
            keyField.requestFocusInWindow ();
            rootPane.setDefaultButton (getKeyButton);
        }

        public void fieldChanged ()
        {
            boolean entered = !keyField.getText ().trim ().equals ("");
            applyButton.setEnabled (entered);
            rootPane.setDefaultButton (entered ? applyButton : getKeyButton);
        }

        public JPanel getPanel ()
        {
            return panel;
        }

        public String getTag ()
        {
            return "full2";
        }

        public boolean isEnabled ()
        {
            return true;
        }

        public boolean hasUnappliedChanges ()
        {
            return false;
        }

        public boolean hasError ()
        {
            return false;
        }
    }

    /*
    * Card displayed when the user has a full key for an earlier version of
    * the product and: the evaluation period has expired, or the
    * user has indicated in RemindCard that they want to purchase an upgrade,
    * or the user has selected Help->Register/Upgrade.
    * The card displays the Product ID and has a button for going to the
    * website upgrade page. There is a field for entering the upgraded
    * registration key.
    */
    private class UpgradeVCard
        implements Card, FieldListener
    {
        private JPanel panel;
        private JButton upgradeButton;
        private JButton applyButton;
        private JTextField idField;
        private JTextField keyField;

        UpgradeVCard ()
        {
            panel = new JPanel ();
            panel.setLayout (new GridBagLayout ());
            int gridy = 0;

            idField = addField ("Product ID", panel, gridy++, false, this);

            addText (panel, gridy++,
                "Please click 'Upgrade' to go to the upgrade page of the<br>" +
                "Wingpath web site (https://wingpath.co.uk/upgradev.php)<br>" +
                "and use the Product ID displayed above to obtain an<br>" +
                "upgraded registration key.<br>");

            ButtonPanel buttonPanel = addButtonPanel (panel, gridy++);
            upgradeButton = buttonPanel.addButton ("Upgrade",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        String query =
                            "code=" + encodeParam (idField.getText ());
                        browse ("upgradev.php", query);
                    }
                });

            addText (panel, gridy++,
                "Enter the upgraded registration key below and then " +
                "click 'Register'.<br>"
                );

            keyField = addField ("New key", panel, gridy++, true, this);

            buttonPanel = addButtonPanel (panel, gridy++);
            applyButton = buttonPanel.addButton ("Register",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        String keyStr = keyField.getText ().trim ();
                        if (keyStr.equals (""))
                        {
                            JOptionPane.showMessageDialog (dialog,
                                "You must enter a registration key",
                                "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }

                        key = Key.create (keyStr);
                        if (key == null ||
                            !key.isFullLicence () ||
                            !key.isValid (details, details.user,
                                details.company))
                        {
                            JOptionPane.showMessageDialog (dialog,
                                "Invalid key",
                                "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                        details.setKey (key);
                        Registration.saveRegistration (details);
                        dialog.setVisible (false);
                        callback.registered (true);
                    }
                });

            buttonPanel.addButton ("Cancel", cancelAction);
        }

        public void initialize ()
        {
            idField.setText (details.getProductId (oldVersion));
            keyField.requestFocusInWindow ();
            rootPane.setDefaultButton (upgradeButton);
        }

        public void fieldChanged ()
        {
            boolean entered = !keyField.getText ().trim ().equals ("");
            applyButton.setEnabled (entered);
            rootPane.setDefaultButton (entered ? applyButton : upgradeButton);
        }

        public JPanel getPanel ()
        {
            return panel;
        }

        public String getTag ()
        {
            return "upgradev";
        }

        public boolean isEnabled ()
        {
            return true;
        }

        public boolean hasUnappliedChanges ()
        {
            return false;
        }

        public boolean hasError ()
        {
            return false;
        }
    }

    /*
    * Card displayed when the user has a restricted full registration and
    * selects Help->Upgrade.
    * It displays the restricted registration key, and has a field for
    * entering an upgraded registration key.
    * It has an 'Upgrade' button, which opens the website upgrade page
    * in the browser, and a 'Register' button, which checks the registration
    * key and continues running of the program if OK.
    */
    private class UpgradeLCard
        implements Card, FieldListener
    {
        private JPanel panel;
        private JButton upgradeButton;
        private JButton applyButton;
        private JTextField oldKeyField;
        private JTextField newKeyField;

        UpgradeLCard ()
        {
            panel = new JPanel ();
            panel.setLayout (new GridBagLayout ());
            int gridy = 0;

            oldKeyField = addField ("Old key", panel, gridy++, false, this);

            addText (panel, gridy++,
                "If you want to upgrade to a later version, please download<br>" +
                "the later version, run it, and use its registration dialog<br>" +
                "to upgrade.<br><br>" +
                "If you want to upgrade to a less restrictive licence,<br>" +
                "please click 'Upgrade' to go to the upgrade page of the<br>" +
                "Wingpath web site (https://wingpath.co.uk/upgradel.php)<br>" +
                "and use the registration key displayed above to obtain an<br>" +
                "upgraded registration key.<br>");

            ButtonPanel buttonPanel = addButtonPanel (panel, gridy++);
            upgradeButton = buttonPanel.addButton ("Upgrade",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        String query =
                            "code=" + encodeParam (oldKeyField.getText ());
                        browse ("upgradel.php", query);
                    }
                });

            addText (panel, gridy++,
                "Enter the upgraded registration key below and then " +
                "click 'Register'.<br>"
                );

            newKeyField = addField ("New key", panel, gridy++, true, this);

            buttonPanel = addButtonPanel (panel, gridy++);
            applyButton = buttonPanel.addButton ("Register",
                new ActionListener ()
                {
                    public void actionPerformed (ActionEvent e)
                    {
                        String keyStr = newKeyField.getText ().trim ();
                        if (keyStr.equals (""))
                        {
                            JOptionPane.showMessageDialog (dialog,
                                "You must enter a registration key",
                                "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }

                        key = Key.create (keyStr);
                        if (key == null ||
                            !key.isFullLicence () ||
                            !key.isValid (details, details.user,
                                details.company))
                        {
                            JOptionPane.showMessageDialog (dialog,
                                "Invalid key",
                                "Error", JOptionPane.ERROR_MESSAGE);
                            return;
                        }
                        details.setKey (key);
                        Registration.saveRegistration (details);
                        dialog.setVisible (false);
                        callback.registered (true);
                    }
                });

            buttonPanel.addButton ("Cancel", cancelAction);
        }

        public void initialize ()
        {
            oldKeyField.setText (details.key.encodeStr ());
            newKeyField.requestFocusInWindow ();
            rootPane.setDefaultButton (upgradeButton);
        }

        public void fieldChanged ()
        {
            boolean entered = !newKeyField.getText ().trim ().equals ("");
            applyButton.setEnabled (entered);
            rootPane.setDefaultButton (entered ? applyButton : upgradeButton);
        }

        public JPanel getPanel ()
        {
            return panel;
        }

        public String getTag ()
        {
            return "upgradel";
        }

        public boolean isEnabled ()
        {
            return true;
        }

        public boolean hasUnappliedChanges ()
        {
            return false;
        }

        public boolean hasError ()
        {
            return false;
        }
    }
}

