/**
 * Copyright (c) 2002-2007 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.common;


import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.jar.Manifest;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.Status;

import org.eclipse.emf.common.util.DelegatingResourceLocator;
import org.eclipse.emf.common.util.Logger;
import org.eclipse.emf.common.util.ResourceLocator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;


/**
 * EMF must run 
 * within an Eclipse workbench,
 * within a headless Eclipse workspace,
 * or just stand-alone as part of some other application.
 * To support this, all resource access (e.g., NL strings, images, and so on) is directed to the resource locator methods,
 * which can redirect the service as appropriate to the runtime.
 * During Eclipse invocation, the implementation delegates to a plugin implementation.
 * During stand-alone invocation, no plugin initialization takes place,
 * so the implementation delegates to a resource JAR on the CLASSPATH.
 * The resource jar will typically <b>not</b> be on the CLASSPATH during Eclipse invocation.
 * It will contain things like the icons and the .properties,  
 * which are available in a different way during Eclipse invocation.
 * @see DelegatingResourceLocator
 * @see ResourceLocator
 * @see Logger
 */
public abstract class EMFPlugin extends DelegatingResourceLocator implements ResourceLocator, Logger
{
  public static final boolean IS_ECLIPSE_RUNNING;
  static
  {
    boolean result = false;
    try
    {
      result = Platform.isRunning();
    }
    catch (Throwable exception)
    {
      // Assume that we aren't running.
    }
    IS_ECLIPSE_RUNNING = result;
  }

  public static final boolean IS_RESOURCES_BUNDLE_AVAILABLE;
  static
  {
    boolean result = false;
    if (IS_ECLIPSE_RUNNING)
    {
      try
      {
        Bundle resourcesBundle = Platform.getBundle("org.eclipse.core.resources");
        result = resourcesBundle != null && (resourcesBundle.getState() & (Bundle.ACTIVE | Bundle.STARTING | Bundle.RESOLVED)) != 0;
      }
      catch (Throwable exception)
      {
        // Assume that it's not available.
      }
    }
    IS_RESOURCES_BUNDLE_AVAILABLE = result;
  }

  protected ResourceLocator [] delegateResourceLocators;

  public EMFPlugin(ResourceLocator [] delegateResourceLocators)
  {
    this.delegateResourceLocators = delegateResourceLocators;
  }

  /**
   * Returns an Eclipse plugin implementation of a resource locator.
   * @return an Eclipse plugin implementation of a resource locator.
   */
  public abstract ResourceLocator getPluginResourceLocator();
  
  @Override
  final protected ResourceLocator getPrimaryResourceLocator()
  {
    return getPluginResourceLocator();
  }
  
  @Override
  protected ResourceLocator[] getDelegateResourceLocators()
  {
    return delegateResourceLocators;
  }

  /**
   * Returns an Eclipse plugin implementation of a logger.
   * @return an Eclipse plugin implementation of a logger.
   */
  public Logger getPluginLogger()
  {
    return (Logger)getPluginResourceLocator();
  }

  public String getSymbolicName()
  {
    ResourceLocator resourceLocator = getPluginResourceLocator();
    if (resourceLocator instanceof InternalEclipsePlugin)
    {
      return ((InternalEclipsePlugin)resourceLocator).getSymbolicName();
    }
    else
    {
      String result = getClass().getName();
      return result.substring(0, result.lastIndexOf('.'));
    }
  }

  /*
   * Javadoc copied from interface.
   */
  public void log(Object logEntry)
  {
    Logger logger = getPluginLogger();
    if (logger == null)
    {
      if (logEntry instanceof Throwable)
      {
        ((Throwable)logEntry).printStackTrace(System.err);
      }
      else
      {
        System.err.println(logEntry);
      }
    }
    else
    {
      logger.log(logEntry);
    }
  }

  /**
   * This is just a bundle activator wrapper for delegating to another bundle activator.
   * It provides a {@link #createBundle() create} method for creating the delegate.
   * Any exception thrown during creation of the delegate is ignored,
   * in which case this activator does nothing for {@link #start(BundleContext) start} and {@link #stop(BundleContext) stop}.
   * The idea is to provide a bundle activator that can delegate to an Equinox-dependent bundle activator,
   * but behaves gracefully in a non-Equinox OSGi implementation.
   * 
   * @since 2.10
   */
  public static abstract class OSGiDelegatingBundleActivator implements BundleActivator
  {
    private final BundleActivator bundle;

    public OSGiDelegatingBundleActivator()
    {
      bundle = createBundleHelper();
    }

    private BundleActivator createBundleHelper()
    {
      try
      {
        return createBundle();
      }
      catch (Throwable throwable)
      {
        return null;
      }
    }

    protected abstract BundleActivator createBundle();

    public final void start(BundleContext context) throws Exception
    {
      if (bundle != null)
      {
        bundle.start(context);
      }
    }

    public final void stop(BundleContext context) throws Exception
    {
      if (bundle != null)
      {
        bundle.stop(context);
      }
    }
  }

  /**
   * The actual implementation of an Eclipse <b>Plugin</b>.
   */
  public static abstract class EclipsePlugin extends Plugin implements ResourceLocator, Logger, InternalEclipsePlugin
  {
    /**
     * The EMF plug-in APIs are all delegated to this helper, so that code can be shared by plug-in
     * implementations with a different platform base class (e.g. AbstractUIPlugin).
     */
    protected InternalHelper helper;
    
    /**
     * Creates an instance.
     */
    public EclipsePlugin()
    {
      super();
      helper = new InternalHelper(this);
    }

    /**
     * Return the plugin ID.
     */
    public String getSymbolicName()
    {
      return helper.getSymbolicName();
    }

    /*
     * Javadoc copied from interface.
     */
    public URL getBaseURL()
    {
      return helper.getBaseURL();
    }

    /*
     * Javadoc copied from interface.
     */
    public Object getImage(String key)
    {
      try
      {
        return doGetImage(key);
      }
      catch (MalformedURLException exception)
      {
        throw new WrappedException(exception);
      }
      catch (IOException exception)
      {
        throw 
          new MissingResourceException
            (CommonPlugin.INSTANCE.getString("_UI_StringResourceNotFound_exception", new Object [] { key }),
             getClass().getName(), 
             key);
      }
    }

    /**
     * Does the work of fetching the image associated with the key.
     * It ensures that the image exists.
     * @param key the key of the image to fetch.
     * @exception IOException if an image doesn't exist.
     * @return the description of the image associated with the key.
     */
    protected Object doGetImage(String key) throws IOException
    {
      return helper.getImage(key);
    }

    public String getString(String key)
    {
      return helper.getString(key, true);
    }
    
    public String getString(String key, boolean translate)
    {
      return helper.getString(key, translate);
    }

    public String getString(String key, Object [] substitutions)
    {
      return helper.getString(key, substitutions, true);
    }

    public String getString(String key, Object [] substitutions, boolean translate)
    {
      return helper.getString(key, substitutions, translate);
    }

    public void log(Object logEntry)
    {
      helper.log(logEntry);
    }
  }

  /**
   * This just provides a common interface for the Eclipse plugins supported by EMF.
   * It is not considered API and should not be used by clients.
   */
  public static interface InternalEclipsePlugin
  {
    String getSymbolicName();
  }
  
  /**
   * This just provides a common delegate for non-UI and UI plug-in classes.
   * It is not considered API and should not be used by clients.
   */
  public static class InternalHelper
  {
    protected Plugin plugin;
    protected ResourceBundle resourceBundle;
    protected ResourceBundle untranslatedResourceBundle;

    public InternalHelper(Plugin plugin)
    {
      this.plugin = plugin;
    }

    protected Bundle getBundle()
    {
      return plugin.getBundle();
    }

    protected ILog getLog()
    {
      return plugin.getLog();
    }
    
    /**
     * Return the plugin ID.
     */
    public String getSymbolicName()
    {
      return getBundle().getSymbolicName();
    }

    public URL getBaseURL()
    {
      return getBundle().getEntry("/");
    }

    /**
     * Fetches the image associated with the given key. It ensures that the image exists.
     * @param key the key of the image to fetch.
     * @exception IOException if an image doesn't exist.
     * @return the description of the image associated with the key.
     */
    public Object getImage(String key) throws IOException
    {
      URL url = new URL(getBaseURL() + "icons/" + key + extensionFor(key));
      InputStream inputStream = url.openStream(); 
      inputStream.close();
      return url;
    }

    public String getString(String key, boolean translate)
    {
      ResourceBundle bundle = translate ? resourceBundle : untranslatedResourceBundle;
      if (bundle == null)
      {
        if (translate)
        {
           bundle = resourceBundle = Platform.getResourceBundle(getBundle());
        }
        else
        {
          String bundleLocalization = (String)getBundle().getHeaders().get(Constants.BUNDLE_LOCALIZATION);
          String propertiesPath = bundleLocalization != null ? bundleLocalization + ".properties" : "plugin.properties";
          String resourceName = getBaseURL().toString() + propertiesPath;
          try
          {
            InputStream inputStream =  new URL(resourceName).openStream();
            bundle = untranslatedResourceBundle = new PropertyResourceBundle(inputStream);
            inputStream.close();
          }
          catch (IOException ioException)
          {
            throw new MissingResourceException("Missing properties: " + resourceName, getClass().getName(), propertiesPath);
          }
        }
      }
      return bundle.getString(key);
    }

    public String getString(String key, Object [] substitutions, boolean translate)
    {
      return MessageFormat.format(getString(key, translate), substitutions);
    }

    public void log(Object logEntry)
    {
      IStatus status;
      if (logEntry instanceof IStatus)
      {
        status = (IStatus)logEntry;
        getLog().log(status);
      }
      else 
      {
        if (logEntry == null)
        {
          logEntry = new RuntimeException(getString("_UI_NullLogEntry_exception", true)).fillInStackTrace();
        }

        if (logEntry instanceof Throwable)
        {
          Throwable throwable = (Throwable)logEntry;

          // System.err.println("Logged throwable: --------------------");
          // throwable.printStackTrace();

          String message = throwable.getLocalizedMessage();
          if (message == null)
          {
            Throwable cause = throwable.getCause();
            if (cause != null)
            {
              message = cause.getLocalizedMessage();
            }
            if (message == null)
            {
              message = "";
            }
          }

          getLog().log(new Status(IStatus.WARNING, getBundle().getSymbolicName(), 0, message, throwable));
        }
        else
        {
          // System.err.println("Logged throwable: --------------------");
          // throwable.printStackTrace();

          getLog().log (new Status (IStatus.WARNING, getBundle().getSymbolicName(), 0, logEntry.toString(), null));
        }
      }
    } 
  }

  public static void main(String[] args)
  {
    try
    {
      String [] relativePath = { "META-INF", "MANIFEST.MF" };
      Class<?> theClass =  args.length > 0 ? Class.forName(args[0]) : EMFPlugin.class;

      String className = theClass.getName();
      int index = className.lastIndexOf(".");
      URL classURL = theClass.getResource((index == -1 ? className : className.substring(index + 1)) + ".class");
      URI uri = URI.createURI(classURL.toString());

      // Trim off the segments corresponding to the package nesting.
      //
      int count = 1;
      for (int i = 0; (i = className.indexOf('.', i)) != -1; ++i)
      {
        ++count;
      }
      uri = uri.trimSegments(count);

      URL manifestURL = null;
  
      // For an archive URI, check for the path in the archive.
      //
      if (URI.isArchiveScheme(uri.scheme()))
      {
        try
        {
          // If we can open  an input stream, then the path is there, and we have a good URL.
          //
          String manifestURI = uri.appendSegments(relativePath).toString();
          InputStream inputStream =  new URL(manifestURI).openStream();
          inputStream.close();
          manifestURL = new URL(manifestURI);
        }
        catch (IOException exception)
        {
          // If the path isn't within the root of the archive, 
          // create a new URI for the folder location of the archive, 
          // so we can look in the folder that contains it.
          //
          uri = URI.createURI(uri.authority()).trimSegments(1);
        }
      }
              
      // If we didn't find the path in the usual place nor in the archive...
      //
      if (manifestURL == null)
      {
        // Trim off the "bin" or "runtime" segment.
        //
        String lastSegment = uri.lastSegment();
        if ("bin".equals(lastSegment) || "runtime".equals(lastSegment))
        {
          uri = uri.trimSegments(1);
        }
        uri = uri.appendSegments(relativePath);
        manifestURL = new URL(uri.toString());
      }
              
      Manifest manifest = new Manifest(manifestURL.openStream());
      String symbolicName =  manifest.getMainAttributes().getValue("Bundle-SymbolicName");
      if (symbolicName != null)
      {
        int end = symbolicName.indexOf(";");
        if (end != -1)
        {
          symbolicName = symbolicName.substring(0, end);
        }
        System.out.println("Bundle-SymbolicName=" + symbolicName + " Bundle-Version=" + manifest.getMainAttributes().getValue("Bundle-Version"));
        return;
      }
    }
    catch (Exception exception)
    {
      // Just print an error message.
    }
    
    System.err.println("No Bundle information found");
  }
}
