/*
 * TV-Browser
 * Copyright (C) 04-2003 Martin Oberhauser (darras@users.sourceforge.net)
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * CVS information:
 *  $RCSfile$
 *   $Source$
 *     $Date: 2011-03-26 17:33:19 +0100 (Sa, 26 Mär 2011) $
 *   $Author: bananeweizen $
 * $Revision: 6970 $
 */

package util.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.Stack;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLFrameHyperlinkEvent;

import util.exc.ErrorHandler;
import util.io.IOUtilities;

/**
 * A help dialog which is able to show HTML pages that are located in a jar
 * file.
 *
 * @author Til Schneider, www.murfman.de
 */
public class HelpDialog implements ActionListener, HyperlinkListener {

  /** The localizer for this class. */
  private static final util.ui.Localizer mLocalizer
    = util.ui.Localizer.getLocalizerFor(HelpDialog.class);
  
  /** The minimum size of the help dialog. */
  public static final Dimension MIN_HELP_DIALOG_SIZE = new Dimension(350, 400);
  
  /** The maximum size of the help dialog. */
  public static final Dimension MAX_HELP_DIALOG_SIZE = new Dimension(500, 10000);

  /**
   * Dialog-Singleton: Es wird immer nur ein Dialog auf einmal angezeigt.
   * Wenn eine neue Seite angezeigt werden soll, so passiert das im selben
   * Dialog.
   */
  private static HelpDialog mHelpDialogSingleton;

  /** The dialog. */
  private JDialog mDialog;

  /** The history. */
  private Stack<HistoryStackElement> mHistoryStack;

  /**
   * Quellpfad der aktuellen Seite. Kann <CODE>null</CODE> sein, wenn der Text
   * nicht aus einer Datei stammt.
   */
  private String mSourcePath;
  
  /** A class of the jar where the currently shown site is located. */
  private Class mSourceClass;

  private JEditorPane mEditorPane;
  private JScrollPane mScrollPane;
  private JButton mBackButton, mCloseButton;



  private HelpDialog(Component parent, String filename) {
    mDialog = UiUtilities.createDialog(UiUtilities.getBestDialogParent(parent),
        false);

    initUi();
  }



  /**
   * Shows the specified site in the help dialog. If there is currently no help
   * dialog shown, one is created.
   *
   * @param parent If there is currently no help dialog shown, this parent is
   *        used for creating a new one.
   * @param filename The name of the HTML file to show.
   * @param clazz A class in the jar where the HTML file is in.
   */
  public static void showHelpPage(Component parent, String filename, Class clazz) {
    if ((mHelpDialogSingleton == null)
      || (! mHelpDialogSingleton.mDialog.isShowing()))
    {
      mHelpDialogSingleton = new HelpDialog(parent, filename);
    } else {
      mHelpDialogSingleton.addThisSiteToHistory();
    }
    
    mHelpDialogSingleton.openSite(filename, clazz);
    mHelpDialogSingleton.show();
  }



  private void initUi() {
    mHistoryStack = new Stack<HistoryStackElement>();

    mDialog.setTitle(Localizer.getLocalization(Localizer.I18N_HELP));

    JPanel main = new JPanel(new BorderLayout());
    mDialog.setContentPane(main);
    main.setBorder(UiUtilities.DIALOG_BORDER);

    mEditorPane = new JEditorPane();
    mEditorPane.setContentType("text/html");
    mEditorPane.addHyperlinkListener(this);
    mEditorPane.setEditable(false);
    
    main.add(mScrollPane = new JScrollPane(mEditorPane));

    // buttons
    JPanel buttonPn = new JPanel(new FlowLayout(FlowLayout.TRAILING));
    main.add(buttonPn, BorderLayout.SOUTH);
    
    buttonPn.add(mBackButton = new JButton(Localizer.getLocalization(Localizer.I18N_BACK)));
    mBackButton.addActionListener(this);
    mBackButton.setEnabled(false);
    
    buttonPn.add(mCloseButton = new JButton(Localizer.getLocalization(Localizer.I18N_CLOSE)));
    mCloseButton.addActionListener(this);
    mDialog.getRootPane().setDefaultButton(mCloseButton);

    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    Window vaterWindow = mDialog.getOwner();
    Point vaterLocation = vaterWindow.getLocation();
    Dimension vaterSize = vaterWindow.getSize();

    // Preferred
    Point hilfeLocation = new Point(vaterLocation.x + vaterSize.width, vaterLocation.y);
    Dimension hilfeSize = new Dimension(screenSize.width - hilfeLocation.x, vaterSize.height);

    // check if size is OK
    if (hilfeSize.width < MIN_HELP_DIALOG_SIZE.width) {
      hilfeSize.width = MIN_HELP_DIALOG_SIZE.width;
      hilfeLocation.x = screenSize.width - hilfeSize.width;

      // does not fit beside -> move parent window to the left
      vaterLocation.x = screenSize.width - MIN_HELP_DIALOG_SIZE.width - vaterSize.width;
      if (vaterLocation.x < 10) {
        vaterLocation.x = 10;
      }
      vaterWindow.setLocation(vaterLocation);
    }
    if (hilfeSize.width > MAX_HELP_DIALOG_SIZE.width) {
      hilfeSize.width = MAX_HELP_DIALOG_SIZE.width;
    }
    if (hilfeSize.height < MIN_HELP_DIALOG_SIZE.height) {
      hilfeSize.height = MIN_HELP_DIALOG_SIZE.height;
      hilfeLocation.y = (screenSize.height - hilfeSize.height) / 2;
    }
    if (hilfeSize.height > MAX_HELP_DIALOG_SIZE.height) {
      hilfeSize.height = MAX_HELP_DIALOG_SIZE.height;
    }

    mDialog.setSize(hilfeSize);
    mDialog.setLocation(hilfeLocation);
  }



  public void show() {
    mDialog.setVisible(true);
  }


  // Hilfsmethoden


  /**
   * Sets the text currently show.
   *
   * @param text The HTML text.
   * @param updateScrollBarTo The relative position to scroll to. (Between 0 and 1)
   */
  protected void setEditorText(String text, double updateScrollBarTo) {
    mEditorPane.setText(text);

    SwingUtilities.invokeLater(new ScrollBarUpdater(updateScrollBarTo));
  }


  /**
   * helper method for {@link #hyperlinkUpdate(HyperlinkEvent) hyperlinkUpdate}.
   * <p>
   * adds the current page to the mHistoryStack.
   */
  protected void addThisSiteToHistory() {
    // Neues HistoryStackElement erstellen
    HistoryStackElement newSite = new HistoryStackElement();
    newSite.mText = mEditorPane.getText();
    newSite.mSourcePath = mSourcePath;
    newSite.mSourceClass = mSourceClass;

    JScrollBar scrollBar = mScrollPane.getVerticalScrollBar();
    newSite.mVerticalScrollBarRelValue = ((double) scrollBar.getValue()) / ((double) scrollBar.getMaximum());

    mHistoryStack.push(newSite);

    mBackButton.setEnabled(true);
  }



  /**
   * helper method for {@link #actionPerformed(ActionEvent)}.
   * <P>
   * return most current page of mHistoryStack.
   */
  protected void popFromHistory() {
    HistoryStackElement lastSite = mHistoryStack.pop();

    double updateScrollBarTo = lastSite.mVerticalScrollBarRelValue;
    setEditorText(lastSite.mText, updateScrollBarTo);
    mSourcePath = lastSite.mSourcePath;
    mSourceClass = lastSite.mSourceClass;

    mScrollPane.validate();

    if (mHistoryStack.empty()) {
      mBackButton.setEnabled(false);
    }
  }



  /**
   * return content of a HTML page as string.
   * <p>
   * Die Seite kann auch in der Jar-Datei sein, in dem diese Klasse ist.
   *
   * @param clazz A class in the jar where the HTML site is located.
   * @param filename Der Dateiname der HTML-Seite.
   */
  protected void openSite(String filename, Class clazz) {
    String text;

    try {
      text = new String(IOUtilities.loadFileFromJar(filename, clazz));
    }
    catch (IOException exc) {
      String msg = mLocalizer.msg("error.1", "Can't open site \"{0}\"!",
        filename);
      ErrorHandler.handle(msg, exc);

      text = "";
    }

    setEditorText(text, 0);
    mSourcePath = extractPath(filename);
    mSourceClass = clazz;
  }



  /**
   * Extrahiert aus einem gegebenen Dateinamen den Pfad. Ist kein Pfad
   * vorhanden, so wird null zurückgegeben.
   *
   * @param dateiname Der Dateiname.
   */
  protected static String extractPath(String dateiname) {
    int lastSlashPos = Math.max(dateiname.lastIndexOf('/'), dateiname.lastIndexOf('\\'));
    if (lastSlashPos == -1) {
      return null;
    } else {
      return dateiname.substring(0, lastSlashPos);
    }
  }


  // implements ActionListener


  public void actionPerformed(ActionEvent evt) {
    if (evt.getSource() == mBackButton) {
      popFromHistory();
    }

    else if (evt.getSource() == mCloseButton) {
      mDialog.dispose();
    }
  }


  // implements HyperlinkListener


  public void hyperlinkUpdate(HyperlinkEvent evt) {
    if (evt.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
      if (evt instanceof HTMLFrameHyperlinkEvent) {
        HTMLFrameHyperlinkEvent frameEvt = (HTMLFrameHyperlinkEvent) evt;
        HTMLDocument doc = (HTMLDocument) mEditorPane.getDocument();
        doc.processHTMLFrameHyperlinkEvent(frameEvt);
      } else {
        addThisSiteToHistory();
        String filename = evt.getDescription();

        // Falls in Link ein Anker vorkommt, diesen ignorieren
        int anchorPos = filename.indexOf('#');
        if (anchorPos != -1) {
          filename = filename.substring(0, anchorPos);
        }

        // Quellpfad der aktuellen Seite voranstellen
        if (mSourcePath != null) {
          filename = mSourcePath + "/" + filename;
        }
        this.mSourcePath = extractPath(filename);

        // Datei laden
        openSite(filename, mSourceClass);
      }
    }
  }
  
  
  // inner class ScrollBarUpdater
  
  
  protected class ScrollBarUpdater implements Runnable {
    
    private double mUpdateScrollBarTo;
    
    public ScrollBarUpdater (double updateScrollBarTo) {
      mUpdateScrollBarTo = updateScrollBarTo;
    }
    
    public void run() {
      JScrollBar bar = mScrollPane.getVerticalScrollBar();
      bar.setValue((int) (mUpdateScrollBarTo * bar.getMaximum()));
    }
    
  }


  // inner class HistoryStackElement
  

  protected static class HistoryStackElement {
    /**
     * Quellpfad der zuletzt besuchten Seite. Kann <CODE>null</CODE> sein, wenn
     * der Text nicht aus einer Datei stammt.
     */
    public String mSourcePath;
    
    /** The class of the jar, where the page is in. */
    public Class mSourceClass;

    /** Der Text der zuletzt besuchten Seite. */
    public String mText;

    /** Die letzte relative Position der vertikalen ScrollBar. (value / maximum) */
    public double mVerticalScrollBarRelValue;
  }

}
