package nxm.sys.libg;

import java.awt.Color;
import java.awt.Desktop;
import java.io.FileNotFoundException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;

import javax.swing.event.HyperlinkEvent;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLFrameHyperlinkEvent;

import nxm.sys.lib.Shell;

/** Mini HTML browser/viewer using JEditorPane. Has history support (back, forward), refresh,
    browse in user's desktop web browser (Java6+), and location (URL) input text bar for
    user to enter in desired URL to view. This component is used by HELP/GUI.
    @author Neon Ngo
    @since NeXtMidas 2.5.0
 */
public class MJBrowser extends javax.swing.JPanel implements MJHelp.DocViewerIF {
  /** NOTE: this should be changed if the structure/fields of this class changes. */
  private static final long serialVersionUID = 2012102410330L; // for Serializable interface

  /** browsed URL history                             */ private ArrayList<URL> history       = new ArrayList<>();
  /** current position in history (-1 for none)       */ private int            currentPos    =                -1;
  /** back index position in history (-1 for none)    */ private int            backPos       =                -1;
  /** forward index position in history (-1 for none) */ private int            forwardPos    =                -1;
  /** toggle NeXtMidas shell debugging                */ private boolean        debug         =              true;

  /** java.awt.Desktop instance for Java6+ */ private static Desktop desktop = null;

  /** static initializer to get java.awt.Desktop reference */
  static {
    try {
      desktop = Desktop.getDesktop();
    } catch (Exception e) {
      Shell.warning("Unable to get java.awt.Desktop instance for using browse(URI) method: "+e);
    }
  }

  /** Creates new form MJBrowser */
  public MJBrowser () {
    initComponents();

    /** browse document in user's web browser is only enabled for java 6 and higher */
    inBrowser.setEnabled(desktop != null);
  }

  /** show's the document at the specified URL without changing the URL history. */
  private boolean displayURL (URL url) {
    boolean result = false;
    try {
      jURL.setText(url.toString());
      jEditorPane.setPage(url);
      jEditorPane.setBackground(Color.WHITE); // NXM-3965 - needed for JDK20+
      result = true;
    } catch (FileNotFoundException fne) {
      jEditorPane.setText("Specified file to display not found:\n" + url);
    } catch (Exception ex) {
      String msg = "Cannot render this document. Open this URL in your web browser to display the contents:\n" + url;
      jEditorPane.setText(msg);
    }
    return result;
  }

  public boolean showDocument (URL url) {
    boolean result = displayURL(url);
    if (result) {
      synchronized (history) {
        currentPos++;
        history.add(currentPos, url);
        if (currentPos>0) backPos++;
        back.setEnabled(backPos >= 0);
        int size = history.size();
        // remove all items from history after current position
        for (int i=size-1; i>currentPos; i--) history.remove(i);
        forwardPos = -1;
        forward.setEnabled(false);
      }
    }
    return result;
  }

  /** Launches the default browser to display the specified URL. This only works for Java6 or
      higher and uses reflection so that there is no compile time dependency on Java6.
      @param url URL to try to open in user's desktop web browser
      @return true if successful, otherwise false
      @since NeXtMidas 2.5.0
   */
  public static boolean browse (URL url) {
    boolean result = false;
    if (desktop!=null && url!=null) { // if we have java.awt.Desktop instance AND URL is not null
      try {
        desktop.browse(new URI(url.toString())); // Java6 API
        // if we got here without an Exceptions, then URL was probably opened in browser
        result = true;
      } catch (Exception e) {
        Shell.printStackTrace(e);
      }
    }
    return result;
  }

  /** This method is called from within the constructor to initialize the form.
      WARNING: Do NOT modify this code. The content of this method is
      always regenerated by the Form Editor.
   */
  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
  private void initComponents() {

    javax.swing.JPanel jPanel1 = new javax.swing.JPanel();
    javax.swing.JToolBar jToolBar = new javax.swing.JToolBar();
    back = new javax.swing.JButton();
    forward = new javax.swing.JButton();
    refresh = new javax.swing.JButton();
    inBrowser = new javax.swing.JButton();
    jURL = new javax.swing.JTextField();
    javax.swing.JScrollPane jScrollPane = new javax.swing.JScrollPane();
    jEditorPane = new javax.swing.JEditorPane();

    FormListener formListener = new FormListener();

    setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
    setPreferredSize(new java.awt.Dimension(640, 480));
    setLayout(new java.awt.BorderLayout());

    jPanel1.setLayout(new java.awt.BorderLayout());

    jToolBar.setFloatable(false);
    jToolBar.setRollover(true);

    back.setFont(new java.awt.Font("Dialog", 1, 10));
    back.setText("Back");
    back.setToolTipText("Go back one page");
    back.setEnabled(false);
    back.addActionListener(formListener);
    jToolBar.add(back);

    forward.setFont(new java.awt.Font("Dialog", 1, 10));
    forward.setText("Forward");
    forward.setToolTipText("Go forward one page");
    forward.setEnabled(false);
    forward.addActionListener(formListener);
    jToolBar.add(forward);

    refresh.setFont(new java.awt.Font("Dialog", 1, 10));
    refresh.setText("Refresh");
    refresh.setToolTipText("Reload current page");
    refresh.addActionListener(formListener);
    jToolBar.add(refresh);

    inBrowser.setFont(new java.awt.Font("Dialog", 1, 10));
    inBrowser.setText("Browser");
    inBrowser.setToolTipText("Display page in browser (Java6 only)");
    inBrowser.setDefaultCapable(false);
    inBrowser.setEnabled(false);
    inBrowser.setFocusable(false);
    inBrowser.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
    inBrowser.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
    inBrowser.addActionListener(formListener);
    jToolBar.add(inBrowser);

    jPanel1.add(jToolBar, java.awt.BorderLayout.LINE_START);

    jURL.setFont(new java.awt.Font("Dialog", 0, 10));
    jURL.setText("URL");
    jURL.setToolTipText("URL of content. Open this URL in a web browser for better formatting.");
    jURL.setDragEnabled(true);
    jURL.addActionListener(formListener);
    jPanel1.add(jURL, java.awt.BorderLayout.CENTER);

    add(jPanel1, java.awt.BorderLayout.NORTH);

    jEditorPane.setBorder(null);
    jEditorPane.setContentType("text/html");
    jEditorPane.setEditable(false);
    jEditorPane.setAlignmentX(0.0F);
    jEditorPane.setAlignmentY(0.0F);
    jEditorPane.setMargin(new java.awt.Insets(0, 0, 0, 0));
    jEditorPane.addHyperlinkListener(formListener);
    jScrollPane.setViewportView(jEditorPane);

    add(jScrollPane, java.awt.BorderLayout.CENTER);
  }

  // Code for dispatching events from components to event handlers.

  private class FormListener implements java.awt.event.ActionListener, javax.swing.event.HyperlinkListener {
    FormListener() {}
    public void actionPerformed(java.awt.event.ActionEvent evt) {
      if (evt.getSource() == back) {
        MJBrowser.this.back(evt);
      }
      else if (evt.getSource() == forward) {
        MJBrowser.this.forward(evt);
      }
      else if (evt.getSource() == refresh) {
        MJBrowser.this.refresh(evt);
      }
      else if (evt.getSource() == inBrowser) {
        MJBrowser.this.browser(evt);
      }
      else if (evt.getSource() == jURL) {
        MJBrowser.this.jURLActionPerformed(evt);
      }
    }

    public void hyperlinkUpdate(javax.swing.event.HyperlinkEvent evt) {
      if (evt.getSource() == jEditorPane) {
        MJBrowser.this.jEditorPaneHyperlinkUpdate(evt);
      }
    }
  }// </editor-fold>//GEN-END:initComponents

  private void browser(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browser
    if (currentPos<0) return;
    try {
      URL url = history.get(currentPos);
      browse(url);
    } catch (Exception ex) {
      // cannot display in browser, nothing else we can do
      if (debug) ex.printStackTrace();
    }
  }//GEN-LAST:event_browser

  private void jEditorPaneHyperlinkUpdate(javax.swing.event.HyperlinkEvent evt) {//GEN-FIRST:event_jEditorPaneHyperlinkUpdate
    if (evt.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
      if (evt instanceof HTMLFrameHyperlinkEvent) {
        ((HTMLDocument)jEditorPane.getDocument()).processHTMLFrameHyperlinkEvent(
            (HTMLFrameHyperlinkEvent)evt);
      } else {
        showDocument(evt.getURL());
      }
    }
  }//GEN-LAST:event_jEditorPaneHyperlinkUpdate

  private void jURLActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jURLActionPerformed
    try {
      URL url = (new URI(jURL.getText())).toURL();
      showDocument(url);
    } catch (Exception ex) {
      // cannot display URL
      if (debug) ex.printStackTrace();
    }
  }//GEN-LAST:event_jURLActionPerformed

  private void refresh(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refresh
    if (currentPos<0) return;
    URL url = history.get(currentPos);
    try {
      jEditorPane.read(url.openStream(), url);
    } catch (Exception e) {
      jEditorPane.setText("Unable to refresh contents of URL: " + url + "\n" + e);
    }
    //displayURL(url);
  }//GEN-LAST:event_refresh

  private void forward(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_forward
    synchronized (history) {
      int size = history.size();
      if (forwardPos < size) {
        URL url = history.get(forwardPos);
        backPos    = currentPos;
        currentPos = forwardPos;
        forwardPos++;
        back.setEnabled(true);
        displayURL(url);
      } else forwardPos = -1;
      if (forwardPos < 0 || forwardPos >= size) forward.setEnabled(false);
    }
  }//GEN-LAST:event_forward

  private void back(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_back
    synchronized (history) {
      if (backPos >= 0) {
        URL url = history.get(backPos);
        forwardPos = currentPos;
        currentPos = backPos;
        backPos--;
        forward.setEnabled(true);
        displayURL(url);
      }

      if (backPos<0) back.setEnabled(false);
    }
  }//GEN-LAST:event_back

  // Variables declaration - do not modify//GEN-BEGIN:variables
  /** back button */       javax.swing.JButton back;
  /** forward button */    javax.swing.JButton forward;
  /** in browser button */ javax.swing.JButton inBrowser;
  /** editor pane */       javax.swing.JEditorPane jEditorPane;
  /** URL */               javax.swing.JTextField jURL;
  /** refresh button */    javax.swing.JButton refresh;
  // End of variables declaration//GEN-END:variables

}
