/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.core.client;

import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.client.impl.Impl;

/**
 * Supports core functionality that in some cases requires direct support from
 * the compiler and runtime systems such as runtime type information and
 * deferred binding.
 */
public final class GWT {
  /**
   * This interface is used to catch exceptions at the "top level" just before
   * they escape to the browser. This is used in places where the browser calls
   * into user code such as event callbacks, timers, and RPC.
   *
   * In Development Mode, the default handler prints a stack trace to the log
   * window. In Production Mode, the default handler is null and thus exceptions
   * are allowed to escape, which provides an opportunity to use a JavaScript
   * debugger.
   */
  public interface UncaughtExceptionHandler {
    void onUncaughtException(Throwable e);
  }

  /**
   * An {@link UncaughtExceptionHandler} that logs errors to
   * {@link GWT#log(String, Throwable)}. This is the default exception handler
   * in Development Mode. In Production Mode, the default exception handler is
   * <code>null</code>.
   */
  private static final class DefaultUncaughtExceptionHandler implements
      UncaughtExceptionHandler {
    @Override
    public void onUncaughtException(Throwable e) {
      log("Uncaught exception escaped", e);
    }
  }

  /**
   * This constant is used by {@link #getPermutationStrongName} when running in
   * Development Mode.
   */
  public static final String HOSTED_MODE_PERMUTATION_STRONG_NAME = "HostedMode";

  /**
   * Defaults to <code>null</code> in Production Mode and an instance of
   * {@link DefaultUncaughtExceptionHandler} in Development Mode.
   */
  private static UncaughtExceptionHandler uncaughtExceptionHandler = null;

  /**
   * Instantiates a class via deferred binding.
   *
   * <p>
   * The argument to {@link #create(Class)}&#160;<i>must</i> be a class literal
   * because the Production Mode compiler must be able to statically determine
   * the requested type at compile-time. This can be tricky because using a
   * {@link Class} variable may appear to work correctly in Development Mode.
   * </p>
   *
   * @param classLiteral a class literal specifying the base class to be
   *          instantiated
   * @return the new instance, which must be cast to the requested class
   */
  public static <T> T create(Class<?> classLiteral) {
    /*
     * In Production Mode, the compiler directly replaces calls to this method
     * with a new Object() type expression of the correct rebound type.
     */
    return com.google.gwt.core.shared.GWT.<T>createImpl(classLiteral);
  }

  /**
   * Gets the URL prefix of the hosting page, useful for prepending to relative
   * paths of resources which may be relative to the host page. Typically, you
   * should use {@link #getModuleBaseURL()} unless you have a specific reason to
   * load a resource relative to the host page.
   *
   * @return if non-empty, the base URL is guaranteed to end with a slash
   */
  public static String getHostPageBaseURL() {
    return Impl.getHostPageBaseURL();
  }

  /**
   * Gets the URL prefix that should be prepended to URLs that point to
   * static files generated by the GWT compiler, such as files in the
   * module's public path.
   *
   * <p>
   * Normally this will be the same value as {@link #getModuleBaseURL}, but
   * may be different when a GWT app is configured to get its static resources
   * from a different server.
   * </p>
   *
   * @return if non-empty, the base URL is guaranteed to end with a slash
   */
  public static String getModuleBaseForStaticFiles() {
    return Impl.getModuleBaseURLForStaticFiles();
  }

  /**
   * Gets the URL prefix that should be prepended to URLs that
   * are intended to be module-relative, such as RPC entry points.
   *
   * <p>If the URL points to an output file of the GWT compiler (such as
   * a file in the public path), use {@link #getModuleBaseForStaticFiles()}
   * instead.</p>
   *
   * @return if non-empty, the base URL is guaranteed to end with a slash
   */
  public static String getModuleBaseURL() {
    return Impl.getModuleBaseURL();
  }

  /**
   * Gets the name of the running module.
   */
  public static String getModuleName() {
    return Impl.getModuleName();
  }

  /**
   * Returns the permutation's strong name. This can be used to distinguish
   * between different permutations of the same module. In Development Mode,
   * this method will return {@value #HOSTED_MODE_PERMUTATION_STRONG_NAME}.
   */
  public static String getPermutationStrongName() {
    if (GWT.isScript()) {
      return Impl.getPermutationStrongName();
    } else {
      return HOSTED_MODE_PERMUTATION_STRONG_NAME;
    }
  }

  /**
   * @deprecated Use {@link Object#getClass()}, {@link Class#getName()}
   */
  @Deprecated
  public static String getTypeName(Object o) {
    return (o == null) ? null : o.getClass().getName();
  }

  /**
   * Returns the currently active uncaughtExceptionHandler.
   *
   * @return the currently active handler, or null if no handler is active.
   *
   * @see #reportUncaughtException(Throwable)
   */
  public static UncaughtExceptionHandler getUncaughtExceptionHandler() {
    return uncaughtExceptionHandler;
  }

  /**
   * Reports an exception caught at the "top level" to a handler set via
   * {@link #setUncaughtExceptionHandler(UncaughtExceptionHandler)}. This is
   * used in places where the browser calls into user code such as event
   * callbacks, timers, and RPC.
   * <p>
   * If no {@code UncaughtExceptionHandler} is set, the exception is reported
   * to browser. Browsers usually log these exceptions to the JavaScript
   * console.
   */
  public static void reportUncaughtException(Throwable e) {
    Impl.reportUncaughtException(e);
  }

  /**
   * Returns the empty string when running in Production Mode, but returns a
   * unique string for each thread in Development Mode (for example, different
   * windows accessing the dev mode server will each have a unique id, and
   * hitting refresh without restarting dev mode will result in a new unique id
   * for a particular window.
   *
   * TODO(unnurg): Remove this function once Dev Mode rewriting classes are in
   * gwt-dev.
   */
  public static String getUniqueThreadId() {
    return com.google.gwt.core.shared.GWT.getUniqueThreadId();
  }

  public static String getVersion() {
    String version = com.google.gwt.core.shared.GWT.getVersion();
    if (version == null) {
      version = getVersion0();
    }
    return version;
  }

  /**
   * Returns <code>true</code> when running inside the normal GWT environment,
   * either in Development Mode or Production Mode. Returns <code>false</code>
   * if this code is running in a plain JVM. This might happen when running
   * shared code on the server, or during the bootstrap sequence of a
   * GWTTestCase test.
   */
  public static boolean isClient() {
    return com.google.gwt.core.shared.GWT.isClient();
  }

  /**
   * Returns <code>true</code> when running in production mode. Returns
   * <code>false</code> when running either in development mode, or when running
   * in a plain JVM.
   */
  public static boolean isProdMode() {
    return com.google.gwt.core.shared.GWT.isProdMode();
  }

  /**
   * Determines whether or not the running program is script or bytecode.
   */
  public static boolean isScript() {
    return com.google.gwt.core.shared.GWT.isScript();
  }

  /**
   * Logs a message to the development shell logger in Development Mode, or to
   * the console in Super Dev Mode. Calls are optimized out in Production Mode.
   */
  public static void log(String message) {
    com.google.gwt.core.shared.GWT.log(message);
  }

  /**
   * Logs a message to the development shell logger in Development Mode, or to
   * the console in Super Dev Mode. Calls are optimized out in Production Mode.
   */
  public static void log(String message, Throwable e) {
    com.google.gwt.core.shared.GWT.log(message, e);
  }

  /**
   * Emits a JavaScript "debugger" statement on the line that called this method.
   * If the user has the browser's debugger open, the debugger will stop when the
   * GWT application executes that line. There is no effect in Dev Mode or in
   * server-side code.
   */
  public static void debugger() {
  }

  /**
   * The same as {@link #runAsync(RunAsyncCallback)}, except with an extra
   * parameter to provide a name for the call. The name parameter should be
   * supplied with a class literal. If two GWT.runAsync calls  use the same
   * class literal, the codesplitter will put the corresponding code in
   * the same code fragment.
   */
  @SuppressWarnings("unused") // parameter will be used following replacement
  public static void runAsync(Class<?> name, final RunAsyncCallback callback) {
    runAsyncImpl(callback);
  }


  /**
   * Run the specified callback once the necessary code for it has been loaded.
   */
  public static void runAsync(final RunAsyncCallback callback) {
    runAsyncImpl(callback);
  }

  private static void runAsyncImpl(final RunAsyncCallback callback) {
    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
      @Override public void execute() {
        callback.onSuccess();
      }
    });
  }

  /**
   * Sets a custom uncaught exception handler. See
   * {@link #getUncaughtExceptionHandler()} for details.
   *
   * @param handler the handler that should be called when an exception is
   *        about to escape to the browser, or <code>null</code> to clear the
   *        handler and allow exceptions to escape.
   */
  public static void setUncaughtExceptionHandler(
      UncaughtExceptionHandler handler) {
    uncaughtExceptionHandler = handler;
    // Dev mode does not do this
    if (GWT.isScript() && handler != null) {
      Impl.maybeInitializeWindowOnError();
    }
  }

  /**
   * Called via reflection in Development Mode; do not ever call this method in
   * Production Mode.
   */
  static void setBridge(GWTBridge bridge) {
    com.google.gwt.core.shared.GWT.setBridge(bridge);
    if (bridge != null) {
      setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler());
    }
  }

  private static native String getVersion0() /*-{
    return $gwt_version;
  }-*/;
}
