/**
 * 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.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;


/**
 * Clock unit.
 */
class ClockUnit
        extends JFrame
{
    /** timer refresh interval */
    private static final long TIMER_REFRESH_INTERVAL_MILLISECONDS = 200L;

    /** default "always on top" mode */
    private static final boolean DEFAULT_ALWAYS_ON_TOP_MODE = true;

    /** tooltip for clock display */
    private static final String DISPLAY_TOOLTIP = "Right-click for clock options";

    /** parent ThisTime object */
    private final ThisTime parent;

    /** clock display for this clock unit */
    private final ClockDisplay display;

    /** clock value to be displayed */
    private final ClockValue value;

    /** timer for refreshing clock value */
    private final Timer timer;

    /** true for 24-hour time format; false for 12-hour time format */
    private volatile boolean hour24;

    /** time zone for this clock */
    private volatile TimeZone timeZone = TimeZone.getDefault();

    /** "minimize to tray" mode (default is true) */
    private boolean minimizeToTray = true;

    /** tray icon for this clock unit */
    private TrayIcon trayIcon;

    /** "Time Zone Selector" form */
    private TimeZoneSelector timeZoneSelector = null;


    /**
    * Constructor.
    * This method must run on the EDT.
    *
    * @param parent
    *      parent ThisTime object
    * @param title
    *      title of this clock unit
    * @param hour24
    *      true for 24-hour time format; false for 12-hour time format
    */
    ClockUnit(
            final ThisTime parent,
            final String title,
            final boolean hour24)
    {
        /*********************
        * INITIALIZE FIELDS *
        *********************/

        this.parent = parent;
        this.hour24 = hour24;

        value = new ClockValue();
        display = new ClockDisplay(value);

        /*****************************
        * CONFIGURE FORM COMPONENTS *
        *****************************/

        setTitle(title);

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

            @Override
            public void windowDeiconified(WindowEvent e)
            {
                restoreClock();
            }

            @Override
            public void windowIconified(WindowEvent e)
            {
                minimizeClock();
            }
        });

        /* set "always on top" mode */
        try
        {
            setAlwaysOnTop(DEFAULT_ALWAYS_ON_TOP_MODE);
        }
        catch (Exception e)
        {
            /* ignore */
        }

        /* set frame icon */
        Image iconImage = null;

        try
        {
            iconImage = ImageIO.read(ClockUnit.class.getResource("/thistime/resources/clock_red.png"));
            setIconImage(iconImage);
        }
        catch (Exception e)
        {
            SwingManipulator.showErrorDialog(
                    null,
                    "Initialization Error - " + getTitle(),
                    "Failed to load clock icon (" + e + ").\n" +
                    "This clock will proceed to run without displaying the clock icon.");
        }

        /* prepare tray icon */
        try
        {
            trayIcon = new TrayIcon(iconImage, getTitle());
            trayIcon.setImageAutoSize(true);

            trayIcon.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent e)
                {
                    restoreClock();
                }
            });
        }
        catch (Exception e)
        {
            trayIcon = null;

            SwingManipulator.showErrorDialog(
                    null,
                    "Initialization Error - " + getTitle(),
                    "Failed to load clock tray icon (" + e + ").\n" +
                    "This clock will proceed to run without displaying the clock tray icon.");
        }

        /* popup menu (Set Title, Enable Alarm, Show Buttons, Always on Top, Minimize to Tray) */
        final JPopupMenu popupMenu = new JPopupMenu();

        /* popup menu: "Set Title..." */
        final JMenuItem titleMenuItem = new JMenuItem("Set Title...", 't');
        titleMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                String newTitle = JOptionPane.showInputDialog(
                        ClockUnit.this,
                        "Clock title:",
                        "Set Title - " + getTitle(),
                        JOptionPane.QUESTION_MESSAGE);

                if (newTitle == null)
                {
                    return;
                }

                newTitle = newTitle.trim();

                if (newTitle.isEmpty())
                {
                    newTitle = title;
                }

                setTitle(newTitle);

                if (trayIcon != null)
                {
                    trayIcon.setToolTip(newTitle);
                }
            }
        });
        popupMenu.add(titleMenuItem);

        /* popup menu: "24-hour Format" */
        final JMenuItem hour24MenuItem = new JCheckBoxMenuItem("24-hour Format", this.hour24);
        hour24MenuItem.setMnemonic('f');
        hour24MenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                ClockUnit.this.hour24 = hour24MenuItem.isSelected();
            }
        });
        popupMenu.add(hour24MenuItem);

        /* popup menu: "Select Time Zone..." */
        final JMenuItem zoneMenuItem = new JMenuItem("Select Time Zone...");
        zoneMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                if (timeZoneSelector == null)
                {
                    timeZoneSelector = new TimeZoneSelector(ClockUnit.this);
                }

                timeZoneSelector.setVisible(true);
                timeZoneSelector.setExtendedState(JFrame.NORMAL);
                timeZoneSelector.toFront();
            }
        });
        popupMenu.add(zoneMenuItem);
        popupMenu.addSeparator();

        /* popup menu: "Always on Top" */
        final JMenuItem topMenuItem = new JCheckBoxMenuItem("Always on Top", isAlwaysOnTop());
        topMenuItem.setMnemonic('t');
        topMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                final boolean alwaysOnTop = topMenuItem.isSelected();

                setAlwaysOnTop(alwaysOnTop);

                if (timeZoneSelector != null)
                {
                    timeZoneSelector.setAlwaysOnTop(alwaysOnTop);
                }
            }
        });
        popupMenu.add(topMenuItem);

        /* popup menu: "Minimize to Tray" */
        final JMenuItem trayMenuItem = new JCheckBoxMenuItem("Minimize to Tray", minimizeToTray);
        trayMenuItem.setMnemonic('t');
        trayMenuItem.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                minimizeToTray = trayMenuItem.isSelected();
            }
        });
        popupMenu.add(trayMenuItem);

        /* add popup menu to clock display */
        display.addMouseListener(new MouseAdapter()
        {
            @Override
            public void mousePressed(MouseEvent e)
            {
                processMouseEvent(e);
            }

            @Override
            public void mouseReleased(MouseEvent e)
            {
                processMouseEvent(e);
            }

            private void processMouseEvent(
                    final MouseEvent e)
            {
                if (e.isPopupTrigger())
                {
                    popupMenu.show(e.getComponent(), e.getX(), e.getY());
                }
            }
        });

        /* display tooltip */
        display.setToolTipText(DISPLAY_TOOLTIP);

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

        display.getActionMap().put("ESCAPE_MINIMIZE", new AbstractAction()
        {
            public void actionPerformed(ActionEvent e)
            {
                minimizeClock();
            }
        });

        /* layout frame components */
        setLayout(new BorderLayout());
        final Container c = getContentPane();
        c.add(display, BorderLayout.CENTER);
        setPreferredSize(new Dimension(350, 200));
        pack();

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

        /***************************
        * INITIALIZE TIMER THREAD *
        ***************************/

        timer = new Timer("Clock-Unit-Timer", true);

        timer.schedule(new TimerTask()
        {
            /** new clock value computed during refreshing */
            private final ClockValue newValue = new ClockValue();


            /**
            * Refresh clock value.
            */
            @Override
            public void run()
            {
                /* compute new clock value */
                final Calendar calendar = Calendar.getInstance(timeZone, Locale.ENGLISH);

                newValue.hour24 = ClockUnit.this.hour24;
                newValue.h = calendar.get(Calendar.HOUR_OF_DAY);
                newValue.m = calendar.get(Calendar.MINUTE);
                newValue.s = calendar.get(Calendar.SECOND);
                newValue.am = (newValue.h < 12);

                if (!newValue.hour24)
                {
                    if (newValue.h == 0)
                    {
                        newValue.h = 12;
                    }
                    else if (newValue.h > 12)
                    {
                        newValue.h -= 12;
                    }
                }

                /* compare against displayed clock value */
                synchronized (value)
                {
                    if (!value.equals(newValue))
                    {
                        value.hour24 = newValue.hour24;
                        value.am = newValue.am;
                        value.h = newValue.h;
                        value.m = newValue.m;
                        value.s = newValue.s;

                        display.repaint();
                    }
                }
            }
        }, 0L, TIMER_REFRESH_INTERVAL_MILLISECONDS);
    }


    /**
    * Set the time zone for this clock unit.
    */
    void setTimeZone(
            final TimeZone tz)
    {
        timeZone = tz;
    }


    /**
    * Close this clock unit.
    */
    private void closeClock()
    {
        if (timeZoneSelector != null)
        {
            timeZoneSelector.setVisible(false);
            timeZoneSelector.dispose();
        }

        parent.removeClock(this);
        setVisible(false);
        timer.cancel();
        dispose();
    }


    /**
    * Minimize this clock unit.
    */
    void minimizeClock()
    {
        setExtendedState(JFrame.ICONIFIED);

        if (minimizeToTray && (trayIcon != null))
        {
            try
            {
                final SystemTray systemTray = SystemTray.getSystemTray();
                boolean alreadyInTray = false;

                for (TrayIcon t : systemTray.getTrayIcons())
                {
                    if (t.equals(trayIcon))
                    {
                        alreadyInTray = true;
                        break;
                    }
                }

                if (!alreadyInTray)
                {
                    systemTray.add(trayIcon);
                }

                setVisible(false);
            }
            catch (Exception e)
            {
                SwingManipulator.showErrorDialog(
                        this,
                        "Error - " + getTitle(),
                        "Failed to minimize clock to tray (" + e + ").\n" +
                        "This clock will continue to stay on the desktop.");
            }
        }
    }


    /**
    * Restore this clock unit.
    */
    void restoreClock()
    {
        setVisible(true);
        setExtendedState(JFrame.NORMAL);
        toFront();

        if (trayIcon != null)
        {
            try
            {
                SystemTray.getSystemTray().remove(trayIcon);
            }
            catch (Exception e)
            {
                /* ignore */
            }
        }
    }
}