package nxm.sys.osgi;

import java.net.URL;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.packageadmin.PackageAdmin;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.lib.OptionTree;

/** OSGi Bundle Activator for NeXtMidas to customize the starting and stopping
    of this bundle within an OSGi framework.

    @author Neon Ngo / Michael Ihde
    @since NeXtMidas 3.3.0
 */
public class NeXtMidasActivator implements BundleActivator, OptionTree.OptionTreeFinder {

  /** The shared instance (null if bundle is not running) */
  private static NeXtMidasActivator sharedInstance;

  /** The OSGi bundle context of this instance. */
  private BundleContext context;
  /** OSGi Package Admin service for framework (null if NOT available) */
  private PackageAdmin packageAdmin;
  /** The current debug level (zero for none) */
  private int debugLevel = 0;

  /** default constructor */
  public NeXtMidasActivator () {}

  @Override
  public void start (BundleContext context) throws Exception {
    if (debugLevel > 0) log("start()");
    sharedInstance = this;
    this.context = context;
    setDebug(getDebugProperty(context));

    // In the OSGi framework, native code is okay, so override property (if not explicitly set)
    // so behavior of nxm.sys.lib.NeXtMidas() constructor will run with native code enabled.
    // NOTE: this can be removed when NeXtMidas() no longer needs to disable native code by default
    if (System.getProperty("NONATIVE") == null) {
      System.setProperty("NONATIVE", "FALSE");
    }

    // Get package admin service for bundle lookup (e.g. finding option trees
    // deployed as bundles) in the OSGi framework
    ServiceReference ref = context.getServiceReference(
        org.osgi.service.packageadmin.PackageAdmin.class.getName());
    packageAdmin = (ref == null) ? null :
        (PackageAdmin) context.getService(ref);

    OptionTree.registerOptionTreeFinder(this);

    if (debugLevel > 1) listOptionTreeBundles(); // DEBUG
  }

  @Override
  public void stop (BundleContext context) throws Exception {
    if (debugLevel > 0) log("stop()");
    sharedInstance = null;
    this.context = null;
  }

  /** Get the shared instance (for interaction with the OSGi framework).
      @return the shared instance (null if bundle/plug-in is not running)
   */
  public static NeXtMidasActivator getDefault () {
    return sharedInstance;
  }

  /** Get the URL string of the specified option tree's root path if deployed as a bundle in the OSGi framework.
      Overrides OptionTree.OptionTreeFinder interface.
      @param opt name of option tree (should be lower case to be optimal)
      @return null if not found, otherwise URL string to option tree's root path
   */
  @Override
  public String findOptRoot (String opt) {
    opt = opt.toLowerCase(); // option tree root paths must be lower-case by convention
    Bundle bundle = findBundleForOpt(opt, null); // search for all versions of option tree
    if (bundle != null) {
      String resname = "nxm/"+opt+"/cfg/commands.cnf";
      // NOTE: Shell.class.getClassLoader().getResource(..) gives SYS's optRoot URL for
      //       other option trees in baseline (e.g. DSP, UCL, etc.) for some development env
      //       setup for NXM project setup under Eclipse IDE (aka the one I am using).
      //       Hence we need to get the resource URL from the bundle for a 100% solution.
      URL commands_cnf = bundle.getResource(resname);
      if (commands_cnf != null) { // must have commands.cnf to be an option tree
          String urlstr = commands_cnf.toString();
          int index = urlstr.lastIndexOf(resname);
          return urlstr.substring(0, index);
      } else if (debugLevel > 0) {
        log("Found bundle for ["+opt.toUpperCase()+"], but unable to find "+resname);
      }
     }
    return null; // not found
  }

  /** Find bundle in the OSGi framework for specified NeXtMidas option tree
      based on "nxm.&lt;optname&gt;" bundle symbolic naming convention.
      @param optName name of option tree (should be lower case to be optimal)
      @param versionRange The version range of the desired bundles, or
              <code>null</code> if all versions are desired.
      @return null if not found, otherwise the latest version of the bundle is returned
   */
  public Bundle findBundleForOpt (String optName, String versionRange) {
    if (packageAdmin == null) { // don't have package admin service, so cannot lookup
      return null;              // bundle from framework, so return null for not found
    }
    optName = optName.toLowerCase(); // option tree names MUST be lower-case by convention
    Bundle[] bundles = packageAdmin.getBundles("nxm."+optName, versionRange);
    if (bundles != null) {
      for (Bundle bundle : bundles) {
        return bundle; // first bundle is latest version for option tree, so use it
      }
    }
    return null; // not found
  }

  /** List all bundles in the OSGi framework that looks like a NeXtMidas option tree
      based on "nxm.&lt;optname&gt;" bundle symbolic naming convention (all lower-case).
      This is used for debugging (by listing all bundles that looks to be an option tree).
      This method is for internal usage only and is subject for removal at any time.
   */
  public void listOptionTreeBundles () {
    for (Bundle bundle : context.getBundles()) {
      if (bundle.getSymbolicName().startsWith("nxm.")) {
        log("bundle: "+bundle);
      }
    }
  }

  /** Get current debug level. This method is for internal usage only and is subject for removal at any time.
      @return current debug level
   */
  @InternalUseOnly("Since NeXtMidas 3.3.0")
  public int getDebug () {
    return debugLevel;
  }

  /** Set current debug level. This method is for internal usage only and is subject for removal at any time.
      @param level new debug level
   */
  @InternalUseOnly("Since NeXtMidas 3.3.0")
  public void setDebug (int level) {
    debugLevel = level;
  }

  /** Get debug level from "nxm.sys.osgi.NeXtMidasActivator.debug" bundle/system property. */
  private int getDebugProperty (BundleContext context) {
    String val = context.getProperty(getClass().getName()+".debug");
    if (val != null) {
      try {
        return Integer.parseInt(val);
      } catch (NumberFormatException nfe) {
        // ignore and use default below
      }
    }
    return debugLevel;
  }

  /** Print/log debug message to System.out for now, but could goto Logging service in the future.
      @param msg message print for debugging/logging
   */
  private void log (CharSequence msg) {
    System.out.println("NeXtMidasActivator: "+msg);
  }
}
