// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0

package com.google.appinventor.client.output;

import com.google.appinventor.client.BugReport;
import com.google.appinventor.client.Ode;
import static com.google.appinventor.client.Ode.MESSAGES;
import com.google.appinventor.common.utils.StringUtils;
import com.google.appinventor.common.version.AppInventorFeatures;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.UmbrellaException;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.VerticalPanel;

import java.util.Set;

/**
 * Output panel for displaying ODE internal logging messages.
 *
 * <p>Note that logging is only active in hosted mode.
 *
 */
// TODO(user): Make this mesh with the new Logger interface.
public final class OdeLog extends Composite {

  // Singleton logging instance
  private static class SingletonHolder {
    private static final OdeLog INSTANCE = new OdeLog();
  }

  // Message style
  private static final String messageStyle = "style=\"font-size:small\"";

  // UI elements
  private final HTML text;

  /**
   * Returns singleton ODE logging instance.
   *
   * @return  ODE logging instance
   */
  public static OdeLog getOdeLog() {
    if (isLogAvailable()) {
      return SingletonHolder.INSTANCE;
    }

    throw new UnsupportedOperationException("logging not available");
  }

  /**
   * Creates a new output panel for displaying internal messages.
   */
  private OdeLog() {
    // Initialize UI
    Button clearButton = new Button(MESSAGES.clearButton());
    clearButton.addClickHandler(new ClickHandler() {
      @Override
      public void onClick(ClickEvent event) {
        clear();
      }
    });

    text = new HTML();
    text.setWidth("100%");

    VerticalPanel panel = new VerticalPanel();
    panel.add(clearButton);
    panel.add(text);
    panel.setSize("100%", "100%");
    panel.setCellHeight(text, "100%");
    panel.setCellWidth(text, "100%");

    initWidget(panel);
  }

  /**
   * Indicates whether logging is available.
   *
   * @return  logging availability
   */
  public static final boolean isLogAvailable() {
    return AppInventorFeatures.hasDebuggingView();
  }

  /**
   * Prints a log message.
   *
   * @param message  message to print
   */
  public static void log(String message) {
    if (isLogAvailable() && !Ode.isWindowClosing()) {
      getOdeLog().println(StringUtils.escape(message));
    }
  }

  /**
   * Prints a log warning message.
   *
   * @param message  message to print
   */
  public static void wlog(String message) {
    if (isLogAvailable() && !Ode.isWindowClosing()) {
      getOdeLog().wprintln(StringUtils.escape(message));
    }
  }

  /**
   * Prints a log error message.
   *
   * @param message  message to print
   */
  public static void elog(String message) {
    if (isLogAvailable() && !Ode.isWindowClosing()) {
      getOdeLog().eprintln(StringUtils.escape(message));
    }
  }

  /**
   * Prints a log message for an exception.
   *
   * @param throwable  exception thrown
   */
  public static void xlog(Throwable throwable) {
    if (isLogAvailable() && !Ode.isWindowClosing()) {
      getOdeLog().eprintln(StringUtils.escape(throwable.toString()) + prepareStackTrace(throwable));
      if (AppInventorFeatures.sendBugReports()) {
        // For this message, we don't escape. We want the bug report link to show as a link.
        getOdeLog().eprintln("File a <a href=\"" + BugReport.getBugReportLink(throwable)
            + "\" target=\"_blank\">bug report</a> for this error.");
      }
    }
  }

  private static String prepareStackTrace(Throwable throwable) {
    StringBuilder html = new StringBuilder();
    html.append("<ul>");
    for (StackTraceElement element : throwable.getStackTrace()) {
      html.append("<li>").append(StringUtils.escape(element.toString())).append("</li>");
    }

    html.append(prepareCause(throwable.getCause()));

    if (throwable instanceof UmbrellaException) {
      Set<Throwable> causes = ((UmbrellaException) throwable).getCauses();
      if (causes != null && !causes.isEmpty()) {
        for (Throwable cause : causes) {
          html.append(prepareCause(cause));
        }
      }
    }

    html.append("</ul>");
    return html.toString();
  }

  private static String prepareCause(Throwable cause) {
    StringBuilder html = new StringBuilder();
    if (cause != null) {
      html.append("<li>Caused by ").append(StringUtils.escape(cause.toString())).append("</li>")
          .append(prepareStackTrace(cause));
    }
    return html.toString();
  }

  /*
   * Prints a log message.
   */
  private void println(String message) {
    text.setHTML(text.getHTML() + "<div " + messageStyle + ">" +
      "<span style=\"color:green\">[INFO] </span>" + message + "</div>");
  }

  /*
   * Prints a log warning message.
   */
  private void wprintln(String message) {
    text.setHTML(text.getHTML() + "<div " + messageStyle + ">" +
        "<span style=\"color:orange\">[WARNING] </span>" + message +
        "</div>");
  }

  /*
   * Prints a log error message.
   */
  private void eprintln(String message) {
    text.setHTML(text.getHTML() + "<div " + messageStyle + ">" +
        "<span style=\"color:red\">[ERROR] </span>" + message + "</div>");
  }

  /*
   * Clears all messages.
   */
  private void clear() {
    if (!Ode.isWindowClosing()) {
      text.setText("");
    }
  }
}