/*   **********************************************************************  **
 **   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.statusline;

import net.sourceforge.rssowl.controller.GUI;
import net.sourceforge.rssowl.util.DateParser;
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.StringShop;
import net.sourceforge.rssowl.util.shop.WidgetShop;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

import java.util.Calendar;
import java.util.Vector;

/**
 * The status line at the bottom of RSSOwl that shows the status of loading /
 * reloading / searching of newsfeeds. A cancel button allows to cancel the
 * operation, whereas the skip button allows to skip the current operation.
 * 
 * @author <a href="mailto:bpasero@rssowl.org">Benjamin Pasero </a>
 * @version 1.2.3
 */
public class StatusLine {

  /** Status: Load */
  public static final int LOAD = 0;

  /** Status: Reload */
  public static final int RELOAD = 2;

  /** Status: Search */
  public static final int SEARCH = 1;

  /** Label for Loading */
  private static String LOAD_LABEL;

  /** Label for Reloading */
  private static String RELOAD_LABEL;

  /** Label for Searching */
  private static String SEARCH_LABEL;

  private Label dateLabel;
  private Calendar lastStatusDate;
  private Vector loadingJobQueue;
  private Composite parent;
  private Label rateStatusLabel;
  private Label sepA;
  private ToolItem skipFeedButton;
  private Thread statusLineAnimator;
  private Composite statusLineHolder;
  private Label statusTextLabel;
  private ToolItem stopOperationButton;
  private ToolBar stopOperationLoadBar;
  LoadJob currentLoadingJob;
  Display display;
  int entireLoad;
  int finishedLoad;
  GUI rssOwlGui;
  Label statusProgressBar;

  /**
   * Instantiate a new StatusLine
   * 
   * @param display The display
   * @param parent The parent Composite of the Status Line
   * @param rssOwlGui The Main controller
   */
  public StatusLine(Display display, Composite parent, GUI rssOwlGui) {
    this.display = display;
    this.parent = parent;
    this.rssOwlGui = rssOwlGui;
    loadingJobQueue = new Vector();
    setupDefaults();
    initJobLabel();
    initComponents();
  }

  /**
   * Add a number to the entireLoad value.
   * 
   * @param loadcount The number of jobs that will start. From an aggregation,
   * the number of all favorites in the aggregation will be passed as value.
   */
  public void addEntireLoad(int loadcount) {
    this.entireLoad += loadcount;
  }

  /**
   * The given Job has finished normally and was not interrupted by the user.
   * This method is running synchronized, because it is called from more than
   * one Thread but should run atomar.
   * 
   * @param rssOwlLoadJob The job that has finished
   */
  public synchronized void finishJob(LoadJob rssOwlLoadJob) {

    /** Remove the job from the queue */
    loadingJobQueue.remove(rssOwlLoadJob);

    /** Increase the counter of loaded feeds */
    finishedLoad++;

    /** Clear Statusline if RSSOwl has finished loading */
    if (!isBusyLoading()) {
      resetStatusLine();
    }

    /** RSSOwl is still loading newsfeeds, show the next from the queue */
    else {
      LoadJob nextJob = (LoadJob) loadingJobQueue.firstElement();
      displayJob(nextJob);

      /**
       * In case that this Job is the last element in the queue and not from an
       * aggregation, disable the Skip Button
       */
      if (loadingJobQueue.size() == 1 && !nextJob.isFromAggregation())
        setSkipFeedEnabled(false);
    }
  }

  /**
   * This tells the status line that a new Job has been started. The status line
   * will display it. This method is running synchronized, because it is called
   * from more than one Thread but should run atomar.
   * 
   * @param rssOwlLoadJob The job that has been started
   */
  public synchronized void insertJob(LoadJob rssOwlLoadJob) {

    /** Add Job into queue */
    loadingJobQueue.add(rssOwlLoadJob);

    /** Display the Job */
    displayJob(rssOwlLoadJob);

    /** Enable status line controlls */
    setStatusEnabled(true);

    /** If this is the first job in the queue, start the statusline animator */
    if (loadingJobQueue.size() == 1) {
      startStatusAnimatorThread(20);
      setSkipFeedEnabled(false);
    }

    /** Enable Skip Button if the queue has more than one element */
    else {
      setSkipFeedEnabled(true);
    }

    /** Use this event to update the date label if needed */
    updateDateLabel(false);
  }

  /**
   * Check if RSSOwl is currently busy loading feeds.
   * 
   * @return boolean TRUE if RSSOwl is loading feeds
   */
  public boolean isBusyLoading() {
    return loadingJobQueue.size() > 0;
  }

  /**
   * Set the AmphetaRate Statusline-Items visible or invisible based on the
   * given argument.
   * 
   * @param visible Set to TRUE if the Items are to be visible.
   */
  public void setAmphetaRateItemsVisible(boolean visible) {
    if (WidgetShop.isset(sepA))
      sepA.setVisible(visible);
    if (WidgetShop.isset(rateStatusLabel))
      rateStatusLabel.setVisible(visible);
  }

  /**
   * @see net.sourceforge.rssowl.controller.IFontChangeable#updateFonts()
   */
  public void updateFonts() {
    dateLabel.setFont(FontShop.dialogFont);
    statusTextLabel.setFont(FontShop.dialogFont);
    statusProgressBar.setFont(FontShop.dialogFont);
  }

  /**
   * @see net.sourceforge.rssowl.util.i18n.ITranslatable#updateI18N()
   */
  public void updateI18N() {
    initJobLabel();
    updateDateLabel(true);
    stopOperationButton.setToolTipText(GUI.i18n.getTranslation("BUTTON_CANCLE"));
    skipFeedButton.setToolTipText(GUI.i18n.getTranslation("TOOLTIP_SKIP"));

    /** Update status text if no feed is loading at the moment */
    if (!isBusyLoading()) {
      statusTextLabel.setText(GUI.i18n.getTranslation("LABEL_READY"));
      statusTextLabel.update();
    }
  }

  /**
   * Set text to the rate label
   * 
   * @param msg The text to set
   */
  public void updateRateLabel(String msg) {

    /** Return if there is no change */
    if (rateStatusLabel.getToolTipText().equals(msg))
      return;

    /** Only update image if a change is present */
    if (rateStatusLabel.getToolTipText().equals("AmphetaRate") || msg.equals(""))
      rateStatusLabel.setImage((!msg.equals("")) ? PaintShop.iconRateSuccess : PaintShop.iconNotRated);

    /** Update Tooltip */
    rateStatusLabel.setToolTipText((msg.equals("")) ? "AmphetaRate" : msg);
  }

  /**
   * Display the given Job in the status line
   * 
   * @param rssOwlLoadJob The job to display
   */
  private void displayJob(LoadJob rssOwlLoadJob) {

    /** Save this Job as current loaded one for the skip button */
    currentLoadingJob = rssOwlLoadJob;

    /** Show information about it */
    StringBuffer statusText = new StringBuffer();
    statusText.append(getStyleText(rssOwlLoadJob.getStyle()));

    /** Only show percentages if Job is from an aggregation */
    if (rssOwlLoadJob.isFromAggregation()) {
      statusText.append(" (");
      statusText.append((loadingJobQueue.size() > 1) ? String.valueOf(getLoadingPercentages()) : "100");
      statusText.append("%)");
    }
    statusText.append(": ");
    statusText.append(rssOwlLoadJob.getTitle());

    /** Set the text */
    if (!statusLineHolder.isDisposed())
      setStatusText(new String(statusText));
  }

  /**
   * Calculate the percentages of finishedLoad divided through entireLoad.
   * 
   * @return int The loading status in percents
   */
  private int getLoadingPercentages() {
    if (entireLoad == 0)
      return 0;
    return (finishedLoad * 100 / entireLoad);
  }

  /**
   * Format the given style as String
   * 
   * @param style The style to format
   * @return String The formatted style as String
   */
  private String getStyleText(int style) {
    switch (style) {

      /** Style: Search */
      case SEARCH:
        return SEARCH_LABEL;

        /** Style: Reload */
      case RELOAD:
        return RELOAD_LABEL;

        /** Default: Load */
      default:
        return LOAD_LABEL;
    }
  }

  /**
   * Init the status lines controls
   */
  private void initComponents() {

    /** Composite holding the status line */
    statusLineHolder = new Composite(parent, SWT.NONE);
    statusLineHolder.setLayoutData(LayoutDataShop.createGridData(GridData.VERTICAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL, 3));
    statusLineHolder.setLayout(LayoutShop.createGridLayout(4, 3, 0, 0, 5, false));

    /** Composite holding the cancel button and the progress bar */
    Composite statusControlsHolder = new Composite(statusLineHolder, SWT.NONE);
    statusControlsHolder.setLayoutData(LayoutDataShop.createGridData(GridData.HORIZONTAL_ALIGN_BEGINNING, 1));
    statusControlsHolder.setLayout(LayoutShop.createGridLayout(2, 0, 0, 0, 5, false));

    /** Label displaying the progress bar */
    statusProgressBar = new Label(statusControlsHolder, SWT.NO_BACKGROUND);
    statusProgressBar.setFont(FontShop.dialogFont);
    statusProgressBar.setImage(PaintShop.getProgressIcon(0));
    statusProgressBar.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_CENTER));

    /** ToolBar holding the stop operation button */
    stopOperationLoadBar = new ToolBar(statusControlsHolder, SWT.FLAT);
    stopOperationLoadBar.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_CENTER));

    /**
     * Bug in SWT: With some Themes the Toolbar is not using the default Widget
     * Background. The Fix is to explicitly set the Background.
     */
    if (GlobalSettings.isWindows())
      stopOperationLoadBar.setBackground(statusControlsHolder.getBackground());

    /** ToolItem to stop the operation */
    stopOperationButton = new ToolItem(stopOperationLoadBar, SWT.PUSH);
    stopOperationButton.setImage(PaintShop.iconCancelOperation);
    stopOperationButton.setDisabledImage(PaintShop.iconCancelOperationDisabled);
    stopOperationButton.setToolTipText(GUI.i18n.getTranslation("BUTTON_CANCLE"));
    stopOperationButton.addSelectionListener(new SelectionAdapter() {

      /** Cancel operation on selection */
      public void widgetSelected(SelectionEvent e) {

        /** Cancel current running operation */
        operationCanceld();
      }
    });

    /** ToolItem to skip the loading of a feed (only if aggregating) */
    skipFeedButton = new ToolItem(stopOperationLoadBar, SWT.PUSH);
    skipFeedButton.setImage(PaintShop.iconSkip);
    skipFeedButton.setDisabledImage(PaintShop.iconSkipDisabled);
    skipFeedButton.setToolTipText(GUI.i18n.getTranslation("TOOLTIP_SKIP"));
    skipFeedButton.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        if (currentLoadingJob != null) {
          currentLoadingJob.cancelJob(false);
          finishJob(currentLoadingJob);
        }
      }
    });

    /** Label displaying the status text */
    statusTextLabel = new Label(statusLineHolder, SWT.NONE);
    statusTextLabel.setLayoutData(LayoutDataShop.createGridData(GridData.VERTICAL_ALIGN_CENTER | GridData.FILL_HORIZONTAL, 1));
    statusTextLabel.setFont(FontShop.dialogFont);

    /** Disable status line controls */
    setStatusEnabled(false);
    setSkipFeedEnabled(false);

    /** Composite holding the AmphetaRate status label */
    Composite rateStatusLabelHolder = new Composite(statusLineHolder, SWT.NONE);
    rateStatusLabelHolder.setLayoutData(LayoutDataShop.createGridData(GridData.HORIZONTAL_ALIGN_END, 1));
    rateStatusLabelHolder.setLayout(LayoutShop.createGridLayout(2, 0, 0, 0, 7, false));

    /** Separator with height set to text height */
    sepA = new Label(rateStatusLabelHolder, SWT.SEPARATOR);
    GridData sepGridDataA = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
    sepGridDataA.heightHint = statusTextLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
    sepA.setLayoutData(sepGridDataA);

    /** Label showing the rate status as text */
    rateStatusLabel = new Label(rateStatusLabelHolder, SWT.NONE);
    rateStatusLabel.setLayoutData(LayoutDataShop.createGridData(GridData.HORIZONTAL_ALIGN_END, 1));
    rateStatusLabel.setFont(FontShop.dialogFont);
    rateStatusLabel.setToolTipText("AmphetaRate");
    rateStatusLabel.setImage(PaintShop.iconNotRated);

    /** Set default rate label message */
    updateRateLabel("");

    /** Composite holding the date label */
    Composite dateHolder = new Composite(statusLineHolder, SWT.NONE);
    dateHolder.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
    dateHolder.setLayout(LayoutShop.createGridLayout(2, 3, 0, 0, 5, false));

    /** On Mac, add margin to the right to make room for the size grip */
    ((GridLayout) dateHolder.getLayout()).marginRight = GlobalSettings.isMac() ? 16 : 0;

    /** Separator with height set to text height */
    Label sepB = new Label(dateHolder, SWT.SEPARATOR);
    GridData sepGridDataB = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
    sepGridDataB.heightHint = statusTextLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
    sepB.setLayoutData(sepGridDataB);

    /** Label showing the date as text */
    dateLabel = new Label(dateHolder, SWT.NONE);
    dateLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
    dateLabel.setFont(FontShop.dialogFont);

    updateDateLabel(true);
  }

  /**
   * Update the Job Label from the Translation.
   */
  private void initJobLabel() {
    SEARCH_LABEL = GUI.i18n.getTranslation("SEARCH_FEED");
    RELOAD_LABEL = GUI.i18n.getTranslation("RELOAD_FEED");
    LOAD_LABEL = GUI.i18n.getTranslation("LOAD_FEED");
  }

  /**
   * Resets the status line.
   */
  private void resetStatusLine() {

    /** Clear the queue */
    loadingJobQueue.clear();

    /** Interrupt the animator thread */
    if (statusLineAnimator != null)
      statusLineAnimator.interrupt();

    /** Reset some values */
    setupDefaults();

    /** Reset status line controls */
    if (GUI.isAlive() && !statusLineHolder.isDisposed()) {
      setStatusEnabled(false);
      setSkipFeedEnabled(false);
    }
  }

  /**
   * Set the skip feed button enabled / disabled
   * 
   * @param enabled TRUE for enabled
   */
  private void setSkipFeedEnabled(boolean enabled) {

    /** State skip button */
    if (!stopOperationLoadBar.isDisposed() && !skipFeedButton.isDisposed()) {
      skipFeedButton.setEnabled(enabled);
      skipFeedButton.setToolTipText((enabled == true) ? GUI.i18n.getTranslation("TOOLTIP_SKIP") : null);
    }
  }

  /**
   * Set the status controlls enabled / disabled
   * 
   * @param enabled TRUE for enabled
   */
  private void setStatusEnabled(boolean enabled) {

    /** State stop button */
    if (!stopOperationLoadBar.isDisposed() && !stopOperationButton.isDisposed()) {
      stopOperationButton.setEnabled(enabled);
      stopOperationButton.setToolTipText((enabled == true) ? GUI.i18n.getTranslation("BUTTON_CANCLE") : null);
    }

    /** Reset progress bar */
    if (!statusProgressBar.isDisposed() && !enabled) {
      statusProgressBar.setImage(PaintShop.getProgressIcon(0));
      statusProgressBar.update();
    }

    /** Reset status text */
    if (!statusTextLabel.isDisposed() && !enabled)
      statusTextLabel.setText(GUI.i18n.getTranslation("LABEL_READY"));

    /** Update layout */
    if (!statusLineHolder.isDisposed())
      statusLineHolder.layout(true);
  }

  /**
   * Set the status text and avoid display of accelerators with replacing all
   * amps with double amps.
   * 
   * @param text The text to display
   */
  private void setStatusText(String text) {
    if (!statusTextLabel.isDisposed()) {
      statusTextLabel.setText(StringShop.escapeAmpersands(text));
      statusTextLabel.update();
    }
  }

  /**
   * Reset some values to default.
   */
  private void setupDefaults() {
    finishedLoad = 0;
    entireLoad = 0;
    currentLoadingJob = null;
  }

  /**
   * Start / Resume the status animator thread
   * 
   * @param initCount The initial progress of the bar as value (0-100)
   */
  private void startStatusAnimatorThread(int initCount) {
    statusLineAnimator = new StatusLineAnimator(display, rssOwlGui, statusProgressBar, initCount);
    statusLineAnimator.start();
  }

  /**
   * Update the date of the label
   * 
   * @param force TRUE forces the update to occur, even if the day has not
   * changed.
   */
  private void updateDateLabel(boolean force) {

    /** Only update if the day has changed or for init */
    if (force || Calendar.getInstance().get(Calendar.DAY_OF_MONTH) != lastStatusDate.get(Calendar.DAY_OF_MONTH)) {
      lastStatusDate = Calendar.getInstance();
      dateLabel.setText(DateParser.formatLongDate(lastStatusDate.getTime()));

      /** Update */
      dateLabel.update();
    }
  }

  /**
   * The current running operation was canceld by the user.
   */
  void operationCanceld() {

    /** Cancel all running jobs */
    for (int a = 0; a < loadingJobQueue.size(); a++)
      ((LoadJob) loadingJobQueue.get(a)).cancelJob(true);

    /** In case the FeedQueueLoader is currently running */
    if (rssOwlGui.getRSSOwlFeedQueueLoader().isRunning())
      rssOwlGui.getRSSOwlFeedQueueLoader().stopThread();

    /** Reset Statusline */
    resetStatusLine();
  }
}