/*   **********************************************************************  **
 **   Copyright notice                                                       **
 **                                                                          **
 **   (c) 2003-2006 RSSOwl Development Team                                  **
 **   http://www.rssowl.org/                                                 **
 **                                                                          **
 **   All rights reserved                                                    **
 **                                                                          **
 **   This program and the accompanying materials are made available under   **
 **   the terms of the Eclipse Public License 1.0 which accompanies this     **
 **   distribution, and is available at:                                     **
 **   http://www.rssowl.org/legal/epl-v10.html                               **
 **                                                                          **
 **   A copy is found in the file epl-v10.html and important notices to the  **
 **   license from the team is found in the textfile LICENSE.txt distributed **
 **   in this package.                                                       **
 **                                                                          **
 **   This copyright notice MUST APPEAR in all copies of the file!           **
 **                                                                          **
 **   Contributors:                                                          **
 **     RSSOwl - initial API and implementation (bpasero@rssowl.org)         **
 **                                                                          **
 **  **********************************************************************  */

package net.sourceforge.rssowl.controller.tray;

import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.controller.thread.ExtendedThread;
import net.sourceforge.rssowl.util.GlobalSettings;
import net.sourceforge.rssowl.util.shop.FontShop;
import net.sourceforge.rssowl.util.shop.LayoutDataShop;
import net.sourceforge.rssowl.util.shop.LayoutShop;
import net.sourceforge.rssowl.util.shop.PaintShop;
import net.sourceforge.rssowl.util.shop.WidgetShop;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;

/**
 * This class creates a small Shell which places itself to the bottom right
 * corner and informs the user that unread news are available. A click on the
 * content text will restore RSSOwl from the System Tray.
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class SystemTrayAlert {

  /** Delay animation for performance reasons */
  private static final int ANIMATION_DELAY = 500;

  /** Delay between Steps of Animation */
  private static final int ANIMATION_SLEEP = 10;

  /** Number of Steps the Animation has */
  private static final int ANIMATION_STEPS = 35;

  /** Time after the popup is closed automatically */
  private static final int AUTO_CLOSE_TIME = 6000;

  /** Keep a single instance of the popup */
  private static SystemTrayAlert instance = null;

  private ExtendedThread autoClosePopup;
  Image closeImageHot;
  Image closeImageNormal;
  Display display;
  boolean mouseInPopup;
  Font popupBoldFont;
  Color popupBorderColor;
  boolean popupClosed;
  Color popupInnerCircleColor;
  Color popupOuterCircleColor;
  Shell popupShell;
  SystemTray rssOwlSystemTray;
  Image trayOwlTease;

  /**
   * Instantiate a new SystemTrayAlert
   * 
   * @param display The display of RSSOwl
   * @param rssOwlSystemTray The calling System Tray class
   */
  private SystemTrayAlert(Display display, SystemTray rssOwlSystemTray) {
    this.display = display;
    this.rssOwlSystemTray = rssOwlSystemTray;
    mouseInPopup = false;
    popupClosed = false;
    initResources();
    initComponents();
  }

  /**
   * Factory method to create or return the single instance of the popup.
   * 
   * @param display The display
   * @param rssOwlSystemTray The System Tray of RSSOwl
   * @return SystemTrayAlert An instance of this class
   */
  public static SystemTrayAlert getInstance(Display display, SystemTray rssOwlSystemTray) {
    if (instance == null)
      instance = new SystemTrayAlert(display, rssOwlSystemTray);

    return instance;
  }

  /**
   * Get if the popup was closed already
   * 
   * @return boolean TRUE if the popup was closed
   */
  public boolean isPopupClosed() {
    return popupClosed;
  }

  /**
   * Show the popup shell. Move it in from the right side or just display it, if
   * the user has set so.
   */
  public void show() {

    /** Update Flag */
    popupClosed = false;

    /** Animate Popup */
    if (GlobalSettings.animateNewsPopup) {
      moveIn();
    }

    /** Just show Popup on the primary monitor */
    else {
      Rectangle clArea = getPrimaryClientArea();
      popupShell.setLocation(clArea.width + clArea.x - popupShell.getSize().x, clArea.height + clArea.y - popupShell.getSize().y);
      popupShell.setVisible(true);
    }

    /** Start auto close Thread if set so */
    if (GlobalSettings.autoCloseNewsPopup)
      startAutoCloseThread();
  }

  /**
   * Get the Client Area of the primary Monitor.
   * 
   * @return Rectangle Client Area of the primary Monitor.
   */
  private Rectangle getPrimaryClientArea() {
    Monitor primaryMonitor = display.getPrimaryMonitor();
    return (primaryMonitor != null) ? primaryMonitor.getClientArea() : display.getClientArea();
  }

  /**
   * Init the popup components
   */
  private void initComponents() {

    /** Create a new ON_TOP shell that is not trimmable */
    popupShell = new Shell(display, GlobalSettings.isWindows() ? SWT.ON_TOP | SWT.NO_FOCUS : SWT.NO_TRIM | SWT.ON_TOP | SWT.NO_FOCUS);
    popupShell.setBackground(popupBorderColor);
    popupShell.setLayout(LayoutShop.createGridLayout(1, 1, 1));

    /** Clean up on disposal */
    popupShell.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        onDispose();
      }
    });

    /** Outer Compositing holding the controlls */
    Composite outerCircle = new Composite(popupShell, SWT.NO_FOCUS);
    outerCircle.setBackground(popupOuterCircleColor);
    outerCircle.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_BOTH, 1));
    outerCircle.setLayout(LayoutShop.createGridLayout(1, 0, 3, 0));

    /** Title area containing label and close button */
    Composite titleCircle = new Composite(outerCircle, SWT.NO_FOCUS);
    titleCircle.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
    titleCircle.setBackground(outerCircle.getBackground());
    titleCircle.setLayout(LayoutShop.createGridLayout(2, 0, 0));

    /** Title Label displaying RSSOwl */
    CLabel titleCircleLabel = new CLabel(titleCircle, SWT.NO_FOCUS);
    titleCircleLabel.setText("RSSOwl");
    titleCircleLabel.setFont(popupBoldFont);
    titleCircleLabel.setBackground(titleCircle.getBackground());
    titleCircleLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    titleCircleLabel.setForeground(display.getSystemColor(SWT.COLOR_WHITE));

    /** CLabel to display a cross to close the popup */
    final CLabel closeButton = new CLabel(titleCircle, SWT.NO_FOCUS);
    closeButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
    closeButton.setBackground(titleCircle.getBackground());
    closeButton.setImage(closeImageNormal);
    closeButton.setCursor(display.getSystemCursor(SWT.CURSOR_HAND));
    closeButton.addMouseListener(new MouseAdapter() {
      public void mouseUp(MouseEvent e) {
        close();
      }
    });

    closeButton.addMouseTrackListener(new MouseTrackAdapter() {

      /** Show hot image on mouse-enter */
      public void mouseEnter(MouseEvent e) {
        closeButton.setImage(closeImageHot);
        mouseInPopup = true;
      }

      /** Show normal image on mouse-exit */
      public void mouseExit(MouseEvent e) {
        closeButton.setImage(closeImageNormal);
        mouseInPopup = false;
      }
    });

    /** Outer composite to hold content controlls */
    Composite outerContentCircle = new Composite(outerCircle, SWT.NO_FOCUS);
    outerContentCircle.setLayout(LayoutShop.createGridLayout(1, 3, 0));
    outerContentCircle.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
    outerContentCircle.setBackground(outerCircle.getBackground());

    /** Middle composite to show a 1px black line around the content controlls */
    Composite middleContentCircle = new Composite(outerContentCircle, SWT.NO_FOCUS);
    middleContentCircle.setLayout(LayoutShop.createGridLayout(1, 1, 1));
    middleContentCircle.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
    middleContentCircle.setBackground(display.getSystemColor(SWT.COLOR_BLACK));

    /** Inner composite containing the content controlls */
    Composite innerContentCircle = new Composite(middleContentCircle, SWT.NO_FOCUS);
    innerContentCircle.setLayoutData(LayoutDataShop.createGridData(GridData.FILL_HORIZONTAL, 1));
    innerContentCircle.setLayout(LayoutShop.createGridLayout(1, 5, 5));
    innerContentCircle.setBackground(popupInnerCircleColor);

    /** Content label showing an image and text */
    final CLabel contentLabel = new CLabel(innerContentCircle, SWT.NO_FOCUS);
    contentLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    contentLabel.setBackground(titleCircle.getBackground());
    contentLabel.setImage(trayOwlTease);
    contentLabel.setBackground(popupInnerCircleColor);
    contentLabel.setText(GUI.i18n.getTranslation("TOOLTIP_UNREAD_AVAILABLE"));
    contentLabel.setFont(popupBoldFont);
    contentLabel.setCursor(display.getSystemCursor(SWT.CURSOR_HAND));
    contentLabel.addMouseTrackListener(new MouseTrackAdapter() {

      /** Paint text blue on mouse-enter */
      public void mouseEnter(MouseEvent e) {
        contentLabel.setForeground(display.getSystemColor(SWT.COLOR_BLUE));
        mouseInPopup = true;
      }

      /** Paint text normal on mouse-exit */
      public void mouseExit(MouseEvent e) {
        contentLabel.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
        mouseInPopup = false;
      }
    });

    contentLabel.addMouseListener(new MouseAdapter() {

      /** Restore RSSOwl when content-text is clicked */
      public void mouseUp(MouseEvent e) {
        close();
        rssOwlSystemTray.restoreWindow();
      }
    });

    /** Pack size and place into bottom right corner */
    popupShell.pack();
  }

  /**
   * Init Fonts, Colors and Images
   */
  private void initResources() {
    popupBorderColor = new Color(display, 125, 177, 251);
    popupOuterCircleColor = new Color(display, 73, 135, 234);
    popupInnerCircleColor = new Color(display, 241, 240, 234);
    popupBoldFont = FontShop.createFont(display.getSystemFont().getFontData()[0].getHeight(), SWT.BOLD);
    closeImageNormal = PaintShop.loadImage("/img/popupclose.gif");
    closeImageHot = PaintShop.loadImage("/img/popupclose_hot.gif");
    trayOwlTease = PaintShop.loadImage("/img/trayowl_tease.gif");
  }

  /**
   * Move the Popup in, from right to left.
   */
  private void moveIn() {

    /** Get Client Area */
    final Rectangle clArea = getPrimaryClientArea();

    /** Cache Popup Size */
    final Point shellSize = popupShell.getSize();

    /** Get Max and Min Coordnites */
    final int maxX = clArea.width + clArea.x;
    final int minX = maxX - shellSize.x;
    final int yPos = clArea.height + clArea.y - shellSize.y;

    /** Value of pixel to move per Animation Step */
    final int pixelPerStep = shellSize.x / ANIMATION_STEPS;

    /** Reset location if not yet done */
    popupShell.setLocation(maxX, yPos);

    /** Restore visibility if required */
    if (!popupShell.isVisible())
      popupShell.setVisible(true);

    /** Create Thread to animate the Popup */
    Thread animator = new Thread() {
      public void run() {

        /** Delay Animation for performance reasons */
        try {
          sleep(ANIMATION_DELAY);
        } catch (InterruptedException e) {
          return;
        }

        /** Start Animation */
        for (int a = maxX; a > minX; a -= pixelPerStep) {

          /** Move the Shell */
          move(a, yPos);

          /** Sleep some Time */
          try {
            sleep(ANIMATION_SLEEP);
          } catch (InterruptedException e) {
            break;
          }
        }

        /** Final Step */
        move(clArea.width + clArea.x - shellSize.x, yPos);
      }
    };

    /** Run the Animator */
    animator.setName("Popup Animator Thread");
    animator.setDaemon(true);
    animator.start();
  }

  /**
   * This thread will close the popup automatically after a certain amount of
   * time. The popup will not close in the case the user has the mouse set into
   * the content or close button.
   */
  private void startAutoCloseThread() {

    /** In case an old Auto Close Thread is still existing and not yet stopped */
    if (autoClosePopup != null && !autoClosePopup.isStopped())
      autoClosePopup.stopThread();

    /** New auto close thread */
    autoClosePopup = new ExtendedThread() {
      public void run() {
        while (!isStopped() && !popupClosed) {
          try {

            /** Slee some seconds */
            sleep(AUTO_CLOSE_TIME);

            /** Close the popup */
            if (GUI.isAlive() && !isStopped()) {
              display.asyncExec(new Runnable() {
                public void run() {

                  /** Only close if still open and not blocked by the user */
                  if (!mouseInPopup && !popupClosed && !isStopped())
                    close();
                }
              });
            }
          }

          /** Return on any interruption */
          catch (InterruptedException e) {
            break;
          }
        }
      }
    };
    /** Start Thread */
    autoClosePopup.setName("Auto Close Tray Popup Thread");
    autoClosePopup.setDaemon(true);
    autoClosePopup.start();
  }

  /**
   * Close the popup shell
   */
  void close() {

    /** Update Flag */
    popupClosed = true;

    /** Stop Autoclose Thread if running */
    if (autoClosePopup != null)
      autoClosePopup.stopThread();

    /** Hide the Popup */
    hide();
  }

  /**
   * Hide the Popup and set location outside client area.
   */
  void hide() {
    if (GUI.isAlive() && WidgetShop.isset(popupShell)) {
      Rectangle clArea = getPrimaryClientArea();
      popupShell.setVisible(false);
      popupShell.setLocation(clArea.width + clArea.x, clArea.height + clArea.y);
    }
  }

  /**
   * Move the Popup in a timed exec.
   * 
   * @param x The X location
   * @param y The Y location
   */
  void move(final int x, final int y) {
    if (GUI.isAlive()) {
      display.syncExec(new Runnable() {
        public void run() {
          if (WidgetShop.isset(popupShell))
            popupShell.setLocation(x, y);
        }
      });
    }
  }

  /**
   * Dispose Resources
   */
  void onDispose() {
    popupBorderColor.dispose();
    popupOuterCircleColor.dispose();
    popupInnerCircleColor.dispose();
    popupBoldFont.dispose();
    closeImageNormal.dispose();
    closeImageHot.dispose();
    trayOwlTease.dispose();
  }
}