/**
 * 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.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.TrayIcon.MessageType;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;


/**
 * Main class for ThisTime program.
 * Simple clock and timer program.
 */
public class ThisTime
{
    /** program name */
    static final String NAME = "ThisTime";

    /** program version */
    static final String VERSION = "2.0";

    /** program date */
    static final String DATE = "2008-03-30";

    /** URL for homepage */
    static final String HOMEPAGE = "http://thistime.sourceforge.net/";

    /** program description text, to be shown in "about" dialog */
    static final String DESCRIPTION =
            ThisTime.NAME + " " +
            ThisTime.VERSION + " (" +
            ThisTime.DATE + ")\n" +
            "Copyright 2008 Zach Scrivena\n" +
            "zachscrivena@gmail.com\n" +
            ThisTime.HOMEPAGE;

    /** URL for latest release information */
    static final String LATEST_RELEASE_URL =
            "http://thistime.sourceforge.net/latest.txt";

    /** URL for bug reporting */
    static final String BUG_REPORT =
            "http://sourceforge.net/tracker/?func=add&group_id=193349&atid=945057";

    /** clocks */
    private final List<ClockUnit> clocks = new ArrayList<ClockUnit>();

    /** clock unique ID */
    private int clockUid = 0;

    /** timers */
    private final List<TimerUnit> timers = new ArrayList<TimerUnit>();

    /** timer unique ID */
    private int timerUid = 0;

    /** "About" form */
    private About about = null;


    /**
    * Constructor.
    * This method must run on the EDT.
    */
    ThisTime()
    {
        /**********************
        * PROGRAM ICON IMAGE *
        **********************/

        final Image iconImage;

        try
        {
            iconImage = ImageIO.read(TimerUnit.class.getResource("/thistime/resources/icon.png"));
        }
        catch (Exception e)
        {
            throw new TerminatingException("Failed to load program icon image (" + e.getMessage() + ").");
        }

        /***********************************
        * POPUP MENU FOR SYSTEM TRAY ICON *
        ***********************************/

        final PopupMenu popupMenu = new PopupMenu(ThisTime.NAME);

        /* popup menu: "New Clock" */
        final MenuItem clockItem = new MenuItem("New Clock");
        clockItem.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                final ClockUnit c;

                synchronized (clocks)
                {
                    clockUid++;

                    c = new ClockUnit(
                            ThisTime.this,
                            "Clock #" + clockUid,
                            false);

                    clocks.add(c);
                }

                c.setVisible(true);
                c.setExtendedState(JFrame.NORMAL);
                c.toFront();
            }
        });
        popupMenu.add(clockItem);

        /* popup menu: "New Count Up Timer" */
        final MenuItem countUpItem = new MenuItem("New Count Up Timer");
        countUpItem.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                final TimerUnit t;

                synchronized (timers)
                {
                    timerUid++;

                    t = new TimerUnit(
                            ThisTime.this,
                            "Timer #" + timerUid,
                            true);

                    timers.add(t);
                }

                t.setVisible(true);
                t.setExtendedState(JFrame.NORMAL);
                t.toFront();
            }
        });
        popupMenu.add(countUpItem);

        /* popup menu: "New Count Down Timer" */
        final MenuItem countDownItem = new MenuItem("New Count Down Timer");
        countDownItem.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                final TimerUnit t;

                synchronized (timers)
                {
                    timerUid++;

                    t = new TimerUnit(
                            ThisTime.this,
                            "Timer #" + timerUid,
                            false);

                    timers.add(t);
                }

                t.setVisible(true);
                t.setExtendedState(JFrame.NORMAL);
                t.toFront();
            }
        });
        popupMenu.add(countDownItem);
        popupMenu.addSeparator();

        /* popup menu: "Restore All" */
        final MenuItem restoreAllItem = new MenuItem("Restore All");
        restoreAllItem.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                synchronized (clocks)
                {
                    for (ClockUnit c : clocks)
                    {
                        c.restoreClock();
                    }
                }

                synchronized (timers)
                {
                    for (TimerUnit t : timers)
                    {
                        t.restoreTimer();
                    }
                }
            }
        });
        popupMenu.add(restoreAllItem);

        /* popup menu: "Minimize All" */
        final MenuItem minimizeAllItem = new MenuItem("Minimize All");
        minimizeAllItem.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
            synchronized (clocks)
                {
                    for (ClockUnit c : clocks)
                    {
                        c.minimizeClock();
                    }
                }

                synchronized (timers)
                {
                    for (TimerUnit t : timers)
                    {
                        t.minimizeTimer();
                    }
                }
            }
        });
        popupMenu.add(minimizeAllItem);
        popupMenu.addSeparator();

        /* popup menu: "About..." */
        final MenuItem aboutItem = new MenuItem("About...");
        aboutItem.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                if (about == null)
                {
                    about = new About(iconImage);
                }

                about.setVisible(true);
                about.setExtendedState(JFrame.NORMAL);
                about.toFront();
            }
        });
        popupMenu.add(aboutItem);
        popupMenu.addSeparator();

        /* popup menu: "Exit" */
        final MenuItem exitItem = new MenuItem("Exit");
        exitItem.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                /* confirm exit, if necessary */
                int numClocksTimers = 0;

                synchronized (clocks)
                {
                    numClocksTimers += clocks.size();
                }

                synchronized (timers)
                {
                    numClocksTimers += timers.size();
                }

                if (numClocksTimers > 0)
                {
                    final int choice = JOptionPane.showConfirmDialog(
                            null,
                            "Exit now and close all clocks and timers?",
                            "Confirm Exit - " + ThisTime.NAME,
                            JOptionPane.YES_NO_OPTION,
                            JOptionPane.WARNING_MESSAGE);

                    if (choice != JOptionPane.YES_OPTION)
                    {
                        return;
                    }
                }

                System.exit(0);
            }
        });
        popupMenu.add(exitItem);

        /********************
        * SYSTEM TRAY ICON *
        ********************/

        final TrayIcon trayIcon = new TrayIcon(iconImage, ThisTime.NAME, popupMenu);
        trayIcon.setImageAutoSize(true);

        try
        {
            SystemTray.getSystemTray().add(trayIcon);
        }
        catch (Exception e)
        {
            throw new TerminatingException("Failed to create system tray icon (" + e.getMessage() + ").");
        }

        trayIcon.displayMessage(
                ThisTime.NAME,
                "Right-click tray icon for program menu",
                MessageType.INFO);

        /* create a frame to get rid of splashscreen */
        final JFrame f = new JFrame();
        f.setVisible(true);
        f.setVisible(false);
        f.dispose();
    }


    /**
    * Remove the specified clock unit.
    */
    void removeClock(
            final ClockUnit c)
    {
        synchronized (clocks)
        {
            clocks.remove(c);
        }
    }


    /**
    * Remove the specified timer unit.
    */
    void removeTimer(
            final TimerUnit t)
    {
        synchronized (timers)
        {
            timers.remove(t);
        }
    }


    /**
    * Provide a string description for the stack trace of the specified exception.
    *
    * @param e
    *      exception
    * @return
    *      string description for the stack trace
    */
    static String stackTraceString(
            final Exception e)
    {
        final StringBuilder sb = new StringBuilder("Exception stack trace:");

        for (StackTraceElement t : e.getStackTrace())
        {
            sb.append("\n  ");
            sb.append(t);
        }

        return sb.toString();
    }


    /**
    * Main entry point for program.
    *
    * @param args
    *     command-line argument strings
    */
    public static void main(
            final String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {

            @Override
            public void run()
            {
                try
                {
                    /* use system look and feel if possible */
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                }
                catch (Exception e)
                {
                    /* ignore */
                }

                /* make tooltips appear instantaneously */
                ToolTipManager.sharedInstance().setInitialDelay(0);

                /* create program GUI */
                try
                {
                    new ThisTime();
                }
                catch (TerminatingException e)
                {
                    SwingManipulator.showErrorDialog(
                            null,
                            "Initialization Error - " + ThisTime.NAME,
                            e.getMessage());

                    System.exit(e.getExitCode());
                }
                catch (Exception e)
                {
                    SwingManipulator.showErrorDialog(
                            null,
                            "Initialization Error - " + ThisTime.NAME,
                            "Failed to initialize " + ThisTime.NAME + " because of an unexpected error: " + e +
                            "\nPlease help to improve " + ThisTime.NAME + " by filing a bug report at " +
                            ThisTime.HOMEPAGE + ".\n\n" +
                            stackTraceString(e));

                    System.exit(1);
                }
            }
        });
    }
}