/*
    GNU GENERAL PUBLIC LICENSE
    Copyright (C) 2006 The Lobo Project

    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
    verion 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 library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Contact info: lobochief@users.sourceforge.net
 */
package org.lobobrowser.primary.ext;

import java.awt.Component;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.lobobrowser.clientlet.ClientletException;
import org.lobobrowser.clientlet.ClientletResponse;
import org.lobobrowser.clientlet.ComponentContent;
import org.lobobrowser.clientlet.JavaVersionException;
import org.lobobrowser.clientlet.NavigatorVersionException;
import org.lobobrowser.html.HtmlRendererContext;
import org.lobobrowser.html.gui.HtmlPanel;
import org.lobobrowser.js.JavaScript;
import org.lobobrowser.main.PlatformInit;
import org.lobobrowser.primary.clientlets.PrimaryClientletSelector;
import org.lobobrowser.primary.clientlets.html.HtmlContent;
import org.lobobrowser.primary.clientlets.html.HtmlRendererContextImpl;
import org.lobobrowser.request.UserAgentImpl;
import org.lobobrowser.ua.NavigationEntry;
import org.lobobrowser.ua.NavigatorErrorListener;
import org.lobobrowser.ua.NavigatorExceptionEvent;
import org.lobobrowser.ua.NavigatorExtension;
import org.lobobrowser.ua.NavigatorExtensionContext;
import org.lobobrowser.ua.NavigatorFrame;
import org.lobobrowser.ua.NavigatorWindow;
import org.lobobrowser.util.Html;
import org.lobobrowser.util.Strings;
import org.w3c.dom.html.HTMLDocument;

public class ExtensionImpl implements NavigatorExtension {
  private static final Logger logger = Logger.getLogger(ExtensionImpl.class.getName());

  public void destroy() {
  }

  public void init(final NavigatorExtensionContext pcontext) {
    pcontext.addURLStreamHandlerFactory(new PrimaryStreamHandlerFactory());
    pcontext.addClientletSelector(new PrimaryClientletSelector());
    pcontext.addNavigatorErrorListener(new NavigatorErrorListener() {
      public void errorOcurred(final NavigatorExceptionEvent event) {
        showError(event.getNavigatorFrame(), event.getResponse(), event.getException());
      }
    });
    JavaScript.init();
  }

  public void windowClosing(final NavigatorWindow wcontext) {
    NavigationHistory.getInstance().save();
  }

  public void windowOpening(final NavigatorWindow wcontext) {
    final ComponentSource cs = new ComponentSource(wcontext);
    final Component[] abc = cs.getAddressBarComponents();
    for (final Component c : abc) {
      wcontext.addAddressBarComponent(c);
    }
    final Component[] sbc = cs.getStatusBarComponents();
    for (final Component c : sbc) {
      wcontext.addStatusBarComponent(c);
    }
    wcontext.addMenu("lobo.file", cs.getFileMenu());
    wcontext.addMenu("lobo.edit", cs.getEditMenu());
    wcontext.addMenu("lobo.view", cs.getViewMenu());
    wcontext.addMenu("lobo.navigation", cs.getNavigationMenu());
    wcontext.addMenu("lobo.bookmarks", cs.getBookmarksMenu());
    wcontext.addMenu("lobo.directory", cs.getDirectoryMenu());
    wcontext.addMenu("lobo.page.services", cs.getPageServicesMenu());
    wcontext.addMenu("lobo.tools", cs.getToolsMenu());
    // wcontext.addMenu("lobo.extensions", cs.getExtensionsMenu());
    wcontext.addMenu("lobo.help", cs.getHelpMenu());
    wcontext.addNavigatorWindowListener(cs);
    final NavigationEntry firstEntry = wcontext.getCurrentNavigationEntry();
    cs.setNavigationEntry(firstEntry);
  }

  public static void showError(final NavigatorFrame frame, final ClientletResponse response, final Throwable exception) {
    if (logger.isLoggable(Level.WARNING)) {
      logger.log(Level.WARNING,
          "showError(): An error occurred trying to process document " + (response == null ? "[null]" : response.getResponseURL()),
          exception);
    }
    final ComponentContent errorComponent = getErrorComponent(frame, response, exception);
    frame.replaceContent(response, errorComponent);
    // TODO: Get window or something, and ensure current URL is shown.
  }

  private static ComponentContent getErrorComponent(final NavigatorFrame frame, final ClientletResponse response, final Throwable exception) {
    final HtmlPanel panel = new HtmlPanel();
    final HtmlRendererContext rcontext = HtmlRendererContextImpl.getHtmlRendererContext(frame);
    panel.setHtml(getErrorHtml(response, exception), "about:error", rcontext);
    String sourceCode = "[NOT AVAILABLE]";
    if (exception instanceof ClientletException) {
      final ClientletException ce = (ClientletException) exception;
      final String sc = ce.getSourceCode();
      if (sc != null) {
        sourceCode = sc;
      }
    }
    return new HtmlContent((HTMLDocument) panel.getRootNode(), panel, sourceCode);
  }

  private static Throwable getRootCause(Throwable t) {
    while (t.getCause() != null) {
      t = t.getCause();
    }
    return t;
  }

  static String getErrorHtml(final ClientletResponse response, final Throwable exception) {
    final java.net.URL url = response == null ? null : response.getResponseURL();
    final String method = response == null ? null : response.getLastRequestMethod();
    final Writer swriter = new StringWriter();
    final PrintWriter writer = new PrintWriter(swriter);
    writer.println("<html><body>");
    writer.println("<dl style='background-color: #FFE0E0; border: 1px solid red; padding: 1em;'>");
    writer.println("  <big>An <strong>error</strong> occurred trying to process a request.</big>");
    writer.println("  <br>");
    if (url != null) {
      writer.println("  <h3>URL:</h3>");
      writer.println("  <dd>" + getErrorUrlText(url, method) + "</dd>");
    }
    writer.println("  <h3>Exception:</h3>");
    writer.println("  <p>" + exception.getClass().getName() + "</p>");
    writer.println("  <h3>Meaning</h3>");
    writer.println("  <div>" + getExceptionMeaning(url, exception) + "</div>");
    writer.println("  <h3>Message:</h3>");
    writer.println("  <div>" + Html.textToHTML(exception.getMessage()) + "</div>");
    writer.println("</dl>");
    writer.println("<p></p>");

    if (PlatformInit.getInstance().debugOn) {
      writer.println("<table border='1' width='100%' style='background-color: #E0E0FF; bolder: solid red 2px;'>");
      writer.println("  <tr><th style='padding:.2em'>Details</th></tr>");
      writer.println("  <tr><td style='padding:.2em'>");

      final StringWriter sw = new StringWriter();
      final PrintWriter pw = new PrintWriter(sw);
      exception.printStackTrace(pw);
      pw.flush();

      writer.println(Html.textToHTML(sw.toString()));

      if (exception.getCause() != null) {
        final Throwable rootCause = getRootCause(exception);
        final StringWriter sw2 = new StringWriter();
        final PrintWriter pw2 = new PrintWriter(sw2);
        rootCause.printStackTrace(pw2);
        pw2.flush();
        writer.println("<p><strong>Root Cause</strong></p>");
        writer.println(Html.textToHTML(sw2.toString()));
      }

      writer.println("  </td></tr>");
      writer.println("</table>");
    }

    writer.println("</body><html>");
    writer.flush();
    return swriter.toString();
  }

  private static String getErrorUrlText(final java.net.URL url, final String method) {
    final StringBuffer buf = new StringBuffer();
    final boolean isGet = "GET".equals(method);
    if (isGet) {
      buf.append("<a href=\"" + url.toExternalForm() + "\">");
    }
    buf.append(Strings.truncate(url.toExternalForm(), 80));
    if (isGet) {
      buf.append("</a>");
    }
    return buf.toString();
  }

  private static String getExceptionMeaning(final java.net.URL url, final Throwable exception) {
    if (exception instanceof org.lobobrowser.clientlet.JavaVersionException) {
      final JavaVersionException jve = (JavaVersionException) exception;
      return "This exception is thrown when the content expects the user's Java Virtual Machine "
          + "to be more up to date than it currently is. In this case the content is " + "expecting version " + jve.getExpectingVersion()
          + " whereas the version running " + "the browser is " + System.getProperty("java.version") + ".";
    } else if (exception instanceof NavigatorVersionException) {
      final NavigatorVersionException nve = (NavigatorVersionException) exception;
      return "This exception is thrown when the content expects the browser version "
          + "to be more up to date than it currently is. In this case the content is " + "expecting version " + nve.getExpectingVersion()
          + " whereas the user agent " + "version is " + UserAgentImpl.getInstance().getVersion() + ".";
    } else {
      Throwable cause = exception;
      if (exception instanceof ClientletException) {
        cause = ((ClientletException) exception).getCause();
        if (cause == null) {
          // oops
          cause = exception;
        }
      } else if (exception instanceof java.lang.reflect.InvocationTargetException) {
        cause = ((java.lang.reflect.InvocationTargetException) exception).getCause();
      }
      if (cause instanceof java.net.MalformedURLException) {
        return "A URL or URI was not formatted correctly.";
      } else if (cause instanceof javax.net.ssl.SSLHandshakeException) {
        return "<p>This is most likely caused due to a JVM with crippled cipher suites.</p>" +
               "<p>We are actively working on this. Please see https://github.com/gngrOrg/gngr/wiki/SSL-Handshake-Failures</p>";
      } else if (cause instanceof java.net.UnknownHostException) {
        return "The host named '" + ((java.net.UnknownHostException) cause).getMessage()
            + "' could not be found by the Domain Name Service (DNS).";
      } else if (cause instanceof java.lang.SecurityException) {
        return "An attempted security violation has been detected and stopped.";
      } else if (cause instanceof java.net.ProtocolException) {
        return "Indicates there is an error in the underlying communications protocol.";
      } else if (cause instanceof java.net.SocketTimeoutException) {
        return "It means the server accepted the connection, but failed to respond after a long period of time. This is usually indicative of a server problem.";
      } else if (cause instanceof java.net.ConnectException) {
        return "It means a connection to the server could not be obtained. Typically, the server has refused the connection, i.e. it's not accepting connections on a given port.";
      } else if (cause instanceof java.net.SocketException) {
        return "Indicates there was an error in the underlying protocol, e.g. TCP/IP.";
      } else if (cause instanceof java.io.IOException) {
        return "Indicates an Input/Output error has occurred. This is typically due "
            + "to a network connection that cannot be establised or one that has failed, "
            + "but it can also mean that a file could not be accessed or found.";
      } else if ((cause instanceof java.lang.NullPointerException) || (cause instanceof java.lang.ClassCastException)) {
        return "This is a common Java exception that generally occurs due to a programming error. "
            + "The stack trace will show if the error is in browser code, an extension or the document itself.";
      } else if (cause instanceof ClientletException) {
        return "A " + ClientletException.class.getSimpleName()
            + " is thrown by extensions or documents typically to indicate an unexpected state has been encountered.";
      } else {
        return "Unknown.";
      }
    }
  }
}
