/**
 * ThisTime 2.0 (2008-03-30)
 * Copyright 2007 Zach Scrivena
 * zachscrivena@gmail.com
 * http://thistime.sourceforge.net/
 *
 * Simple clock and timer program.
 *
 * TERMS AND CONDITIONS:
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package thistime;

import java.awt.Desktop;
import java.awt.Font;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URI;
import java.net.URL;
import java.util.Observable;
import java.util.Observer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;


/**
 * Represent an "About" form.
 */
class About
        extends JFrame
        implements Observer
{
    /** is the new release checker running? */
    private boolean checkerRunning;

    /** downloader for "latest release" information */
    private final WebGet webget;


    /**
    * Constructor.
    *
    * @param iconImage
    *      program icon image to be used by this "About" form
    */
    About(
            final Image iconImage)
    {
        /*********************
        * INITIALIZE FIELDS *
        *********************/

        checkerRunning = false;
        webget = new WebGet();
        webget.addObserver(this);

        /******************************
        * INITIALIZE FORM COMPONENTS *
        ******************************/

        initComponents();

        /***************************
        * CONFIGURE FORM SETTINGS *
        ***************************/

        setTitle("About - " + ThisTime.NAME);
        setIconImage(iconImage);

        try
        {
            aboutText.setText(ThisTime.DESCRIPTION + "\n\n" +
                    ResourceManipulator.resourceAsString("/thistime/resources/about_text.txt"));
        }
        catch (Exception e)
        {
            aboutText.setText(ThisTime.DESCRIPTION);

            SwingManipulator.showErrorDialog(
                    null,
                    getTitle(),
                    "(INTERNAL) Failed to read \"About\" text from JAR file.");
        }

        aboutText.setToolTipText("About " + ThisTime.NAME);
        aboutText.setCaretPosition(0);
        aboutText.setFont(new Font(
                Font.DIALOG,
                Font.PLAIN,
                aboutText.getFont().getSize() - 2));

        addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing(WindowEvent e)
            {
                closeForm();
            }
        });

        /* button: "Check for New Release" */
        checkButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                checkForNewRelease();
            }
        });

        /* button: "Visit Homepage" */
        homeButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                if (Desktop.isDesktopSupported())
                {
                    try
                    {
                        Desktop.getDesktop().browse(new URI(ThisTime.HOMEPAGE));
                    }
                    catch (Exception ex)
                    {
                        /* ignore */
                    }
                }
            }
        });

        /* button: "Report Bug" */
        bugButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                if (Desktop.isDesktopSupported())
                {
                    try
                    {
                        Desktop.getDesktop().browse(new URI(ThisTime.BUG_REPORT));
                    }
                    catch (Exception ex)
                    {
                        /* ignore */
                    }
                }
            }
        });

        /* button: "OK" */
        closeButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                closeForm();
            }
        });

        /* progress bar */
        progress.setStringPainted(true);
        progress.setVisible(false);

        /* key binding: ESCAPE key */
        aboutPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
                .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "ESCAPE_CLOSE_BUTTON");

        aboutPane.getActionMap().put("ESCAPE_CLOSE_BUTTON", new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                closeButton.doClick();
            }
        });

        /* center form on screen */
        setLocationRelativeTo(null);
    }


    /**
    * Close the "About" form.
    */
    private void closeForm()
    {
        if (checkerRunning)
        {
            webget.cancel();
        }

        setVisible(false);
    }


    /**
    * Check for a new release of the program.
    * This method is synchronized.
    */
    private synchronized void checkForNewRelease()
    {
        /* check if the checker is already running */
        if (checkerRunning)
        {
            return;
        }

        checkerRunning = true;

        /* disable "Check for New Release" button */
        checkButton.setEnabled(false);
        progress.setIndeterminate(true);
        progress.setString("Waiting");
        progress.setVisible(true);

        /* start worker thread to check for new release */
        final Thread t = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                /* URL for "latest release" information */
                URL url = null;

                try
                {
                    url = new URL(ThisTime.LATEST_RELEASE_URL);
                }
                catch (Exception e)
                {
                    SwingManipulator.showWarningDialog(About.this, getTitle(),
                            "(INTERNAL) Invalid URL for retrieval.");

                    restoreInterface();
                    return;
                }

                final StringBuilder contents = new StringBuilder();
                final boolean success = webget.get(url, "latest release information", contents);

                if (!success)
                {
                    restoreInterface();
                    return;
                }

                final Matcher m = Pattern.compile("(?s)" +
                        Pattern.quote("<version>") + "(.*)" + Pattern.quote("</version>") + ".*" + /* 1: version    */
                        Pattern.quote("<date>") + "(.*)" + Pattern.quote("</date>") + ".*" +       /* 2: date       */
                        Pattern.quote("<link>") + "(.*)" + Pattern.quote("</link>") + ".*" +       /* 3: link       */
                        Pattern.quote("<about>") + "(.*)" + Pattern.quote("</about>"))             /* 4: about text */
                        .matcher(contents);

                if (m.find())
                {
                    final String version = m.group(1);
                    final String date = m.group(2);
                    final String link = m.group(3);
                    final String about = m.group(4);

                    if (ThisTime.DATE.compareTo(date) < 0)
                    {
                        /* new release is available */
                        final int choice = SwingManipulator.showOptionTextDialog(About.this,
                                "A new release of " + ThisTime.NAME + " is available",
                                ThisTime.NAME + " " + version + " (" + date +
                                ") is available for download. " +
                                ((Double.parseDouble(ThisTime.VERSION) < Double.parseDouble(version)) ?
                                    "This is a major update and is strongly recommended." :
                                    "This is a minor update and is recommended if you are currently experiencing difficulties.") +
                                    "\n\n" + about,
                                8,
                                "Check for New Release - " + getTitle(),
                                JOptionPane.DEFAULT_OPTION,
                                JOptionPane.INFORMATION_MESSAGE,
                                null,
                                new String[] {"Download Now", "Cancel"},
                                0);

                        /* download now */
                        if ((choice == 0) && Desktop.isDesktopSupported())
                        {
                            try
                            {
                                Desktop.getDesktop().browse(new URI(link));
                            }
                            catch (Exception ex)
                            {
                                /* ignore */
                            }
                        }
                    }
                    else
                    {
                        /* this release is up-to-date */
                        SwingManipulator.showInfoDialog(About.this,
                                "Check for New Release - " + getTitle(),
                                "No new release",
                                "This release of " + ThisTime.NAME + " " + ThisTime.VERSION + " (" +
                                ThisTime.DATE + ") is already up-to-date.", 5);
                    }
                }
                else
                {
                    /* unable to parse */
                    SwingManipulator.showWarningDialog(About.this,
                            "Check for New Release - " + getTitle(),
                            ThisTime.NAME + " is unable to parse the information retrieved from the " +
                            ThisTime.NAME + " website; please try again later. If the problem persists, please visit the homepage manually.");
                }

                restoreInterface();
            }


            /**
            * Restore interface before returning.
            */
            private void restoreInterface()
            {
                SwingUtilities.invokeLater(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        checkButton.setEnabled(true);
                        progress.setVisible(false);
                        checkerRunning = false;
                    }
                });
            }
        });

        t.start();
    }


    /**
    * Respond to an observation event created by webget.
    *
    * @param o
    *      observable object generating the event
    * @param arg
    *      description of the observation event
    */
    @Override
    public void update(
            Observable o,
            Object arg)
    {
        final WebGet.Observation obs = (WebGet.Observation) arg;

        switch (obs.observationType)
        {
            case PROGRESS_TEXT:
                /* progress update with text only */
                SwingManipulator.updateProgressBar(progress, (String) obs.value, -1);
                break;

            case PROGRESS_TEXT_PERCENT:
                /* progress update with text and percentage */
                SwingManipulator.updateProgressBar(progress, (String) ((Object[]) obs.value)[0], (Integer) ((Object[]) obs.value)[1]);
                break;

            case COMPLETED:
                /* operation completed */
                SwingUtilities.invokeLater(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        progress.setVisible(false);
                    }
                });
                break;

            case SIZE:
                /* obtained size of remote resource */
                /* ignore */
                break;

            case ERROR:
                /* fatal error has occurred; operation aborted */
                SwingManipulator.showErrorDialog(About.this, "Check for New Release - " + getTitle(),
                        "\"" + ((String) obs.value) + "\"\n" +
                        ThisTime.NAME + " is currently unable to download information about the latest release from the " +
                        ThisTime.NAME + " website; please try again later. If the problem persists, please visit the homepage manually.");

                break;

            case WARNING:
                /* warning issued; can still proceed with operation */
                SwingManipulator.showWarningDialog(About.this, "Check for New Release - " + getTitle(),
                        "\"" + ((String) obs.value) + "\"\n" +
                        ThisTime.NAME + " will try to continue checking for a new release.");
                break;
        }
    }

    /***************************
    * NETBEANS-GENERATED CODE *
    ***************************/

    /** This method is called from within the constructor to
    * initialize the form.
    * WARNING: Do NOT modify this code. The content of this method is
    * always regenerated by the Form Editor.
    */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents()
    {

        title = new javax.swing.JLabel();
        aboutPane = new javax.swing.JScrollPane();
        aboutText = new javax.swing.JTextArea();
        closeButton = new javax.swing.JButton();
        buttonsPanel = new javax.swing.JPanel();
        checkButton = new javax.swing.JButton();
        homeButton = new javax.swing.JButton();
        bugButton = new javax.swing.JButton();
        progress = new javax.swing.JProgressBar();

        setResizable(false);

        title.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        title.setIcon(new javax.swing.ImageIcon(getClass().getResource("/thistime/resources/splashscreen.png"))); // NOI18N

        aboutText.setColumns(20);
        aboutText.setEditable(false);
        aboutText.setFont(aboutText.getFont());
        aboutText.setLineWrap(true);
        aboutText.setRows(5);
        aboutText.setTabSize(4);
        aboutText.setWrapStyleWord(true);
        aboutPane.setViewportView(aboutText);

        closeButton.setMnemonic('C');
        closeButton.setText("Close");
        closeButton.setNextFocusableComponent(closeButton);

        buttonsPanel.setLayout(new java.awt.GridLayout(1, 0));

        checkButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/thistime/resources/lightning.png"))); // NOI18N
        checkButton.setMnemonic('N');
        checkButton.setText("<html>Check for<br /><u>N</u>ew Release</html>");
        checkButton.setIconTextGap(8);
        checkButton.setNextFocusableComponent(checkButton);
        buttonsPanel.add(checkButton);

        homeButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/thistime/resources/house.png"))); // NOI18N
        homeButton.setMnemonic('H');
        homeButton.setText("<html>Visit<br /><u>H</u>omepage</html>");
        homeButton.setIconTextGap(8);
        homeButton.setNextFocusableComponent(homeButton);
        buttonsPanel.add(homeButton);

        bugButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/thistime/resources/bug.png"))); // NOI18N
        bugButton.setMnemonic('B');
        bugButton.setText("<html>Report<br /><u>B</u>ug</hmtl>");
        bugButton.setIconTextGap(8);
        bugButton.setNextFocusableComponent(bugButton);
        buttonsPanel.add(bugButton);

        progress.setFont(progress.getFont().deriveFont(progress.getFont().getSize()-2f));

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(buttonsPanel, javax.swing.GroupLayout.Alignment.CENTER, javax.swing.GroupLayout.DEFAULT_SIZE, 369, Short.MAX_VALUE)
                    .addComponent(aboutPane, javax.swing.GroupLayout.Alignment.CENTER, javax.swing.GroupLayout.DEFAULT_SIZE, 369, Short.MAX_VALUE)
                    .addComponent(title, javax.swing.GroupLayout.Alignment.CENTER, javax.swing.GroupLayout.DEFAULT_SIZE, 369, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(progress, javax.swing.GroupLayout.PREFERRED_SIZE, 247, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE)
                        .addComponent(closeButton, javax.swing.GroupLayout.PREFERRED_SIZE, 74, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(title)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(aboutPane, javax.swing.GroupLayout.DEFAULT_SIZE, 237, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(buttonsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(progress, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(closeButton, javax.swing.GroupLayout.PREFERRED_SIZE, 27, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JScrollPane aboutPane;
    private javax.swing.JTextArea aboutText;
    private javax.swing.JButton bugButton;
    private javax.swing.JPanel buttonsPanel;
    private javax.swing.JButton checkButton;
    private javax.swing.JButton closeButton;
    private javax.swing.JButton homeButton;
    private javax.swing.JProgressBar progress;
    private javax.swing.JLabel title;
    // End of variables declaration//GEN-END:variables
}