/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.nutch.plugin;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.conf.Configuration;

/**
 * The <code>PluginDescriptor</code> provide access to all meta information of a
 * nutch-plugin, as well to the internationalizable resources and the plugin own
 * classloader. There are meta information about <code>Plugin</code>,
 * <code>ExtensionPoint</code> and <code>Extension</code>. To provide access to
 * the meta data of a plugin via a descriptor allow a lazy loading mechanism.
 */
public class PluginDescriptor {
  private String fPluginPath;
  private String fPluginClass = Plugin.class.getName();
  private String fPluginId;
  private String fVersion;
  private String fName;
  private String fProviderName;
  private HashMap<String, ResourceBundle> fMessages = new HashMap<String, ResourceBundle>();
  private ArrayList<ExtensionPoint> fExtensionPoints = new ArrayList<ExtensionPoint>();
  private ArrayList<String> fDependencies = new ArrayList<String>();
  private ArrayList<URL> fExportedLibs = new ArrayList<URL>();
  private ArrayList<URL> fNotExportedLibs = new ArrayList<URL>();
  private ArrayList<Extension> fExtensions = new ArrayList<Extension>();
  private PluginClassLoader fClassLoader;
  public static final Logger LOG = LoggerFactory
      .getLogger(PluginDescriptor.class);
  private Configuration fConf;

  /**
   * Constructor
   * 
   * @param pId
   * @param pVersion
   * @param pName
   * @param pProviderName
   * @param pPluginclazz
   * @param pPath
   */
  public PluginDescriptor(String pId, String pVersion, String pName,
      String pProviderName, String pPluginclazz, String pPath,
      Configuration conf) {
    setPath(pPath);
    setPluginId(pId);
    setVersion(pVersion);
    setName(pName);
    setProvidername(pProviderName);

    if (pPluginclazz != null)
      setPluginClass(pPluginclazz);

    this.fConf = conf;
  }

  /**
   * @param pPath
   */
  private void setPath(String pPath) {
    fPluginPath = pPath;
  }

  /**
   * Returns the name of the plugin.
   * 
   * @return String
   */
  public String getName() {
    return fName;
  }

  /**
   * @param providerName
   */
  private void setProvidername(String providerName) {
    fProviderName = providerName;
  }

  /**
   * @param name
   */
  private void setName(String name) {
    fName = name;
  }

  /**
   * @param version
   */
  private void setVersion(String version) {
    fVersion = version;
  }

  /**
   * Returns the fully qualified name of the class which implements the abstarct
   * <code>Plugin</code> class.
   * 
   * @return the name of this plug-in's runtime class or <code>null</code>.
   */
  public String getPluginClass() {
    return fPluginClass;
  }

  /**
   * Returns the unique identifier of the plug-in or <code>null</code>.
   * 
   * @return String
   */
  public String getPluginId() {
    return fPluginId;
  }

  /**
   * Returns an array of extensions.
   * 
   * @return Exception[]
   */
  public Extension[] getExtensions() {
    return fExtensions.toArray(new Extension[fExtensions.size()]);
  }

  /**
   * Adds a extension.
   * 
   * @param pExtension
   */
  public void addExtension(Extension pExtension) {
    fExtensions.add(pExtension);
  }

  /**
   * Sets the pluginClass.
   * 
   * @param pluginClass
   *          The pluginClass to set
   */
  private void setPluginClass(String pluginClass) {
    fPluginClass = pluginClass;
  }

  /**
   * Sets the plugin Id.
   * 
   * @param pluginId
   *          The pluginId to set
   */
  private void setPluginId(String pluginId) {
    fPluginId = pluginId;
  }

  /**
   * Adds a extension point.
   * 
   * @param extensionPoint
   */
  public void addExtensionPoint(ExtensionPoint extensionPoint) {
    fExtensionPoints.add(extensionPoint);
  }

  /**
   * Returns a array of extension points.
   * 
   * @return ExtensionPoint[]
   */
  public ExtensionPoint[] getExtenstionPoints() {
    return fExtensionPoints
        .toArray(new ExtensionPoint[fExtensionPoints.size()]);
  }

  /**
   * Returns a array of plugin ids.
   * 
   * @return String[]
   */
  public String[] getDependencies() {
    return fDependencies.toArray(new String[fDependencies.size()]);
  }

  /**
   * Adds a dependency
   * 
   * @param pId
   *          id of the dependent plugin
   */
  public void addDependency(String pId) {
    fDependencies.add(pId);
  }

  /**
   * Adds a exported library with a relative path to the plugin directory.
   * 
   * @param pLibPath
   */
  public void addExportedLibRelative(String pLibPath)
      throws MalformedURLException {
    URL url = new File(getPluginPath() + File.separator + pLibPath).toURI()
        .toURL();
    fExportedLibs.add(url);
  }

  /**
   * Returns the directory path of the plugin.
   * 
   * @return String
   */
  public String getPluginPath() {
    return fPluginPath;
  }

  /**
   * Returns a array exported librareis as URLs
   * 
   * @return URL[]
   */
  public URL[] getExportedLibUrls() {
    return fExportedLibs.toArray(new URL[0]);
  }

  /**
   * Adds a not exported library with a plugin directory relative path.
   * 
   * @param pLibPath
   */
  public void addNotExportedLibRelative(String pLibPath)
      throws MalformedURLException {
    URL url = new File(getPluginPath() + File.separator + pLibPath).toURI()
        .toURL();
    fNotExportedLibs.add(url);
  }

  /**
   * Returns a array of libraries as URLs that are not exported by the plugin.
   * 
   * @return URL[]
   */
  public URL[] getNotExportedLibUrls() {
    return fNotExportedLibs.toArray(new URL[fNotExportedLibs.size()]);
  }

  /**
   * Returns a cached classloader for a plugin. Until classloader creation all
   * needed libraries are collected. A classloader use as first the plugins own
   * libraries and add then all exported libraries of dependend plugins.
   * 
   * @return PluginClassLoader the classloader for the plugin
   */
  public PluginClassLoader getClassLoader() {
    if (fClassLoader != null)
      return fClassLoader;
    ArrayList<URL> arrayList = new ArrayList<URL>();
    arrayList.addAll(fExportedLibs);
    arrayList.addAll(fNotExportedLibs);
    arrayList.addAll(getDependencyLibs());
    File file = new File(getPluginPath());
    try {
      for (File file2 : file.listFiles()) {
        if (file2.getAbsolutePath().endsWith("properties"))
          arrayList.add(file2.getParentFile().toURI().toURL());
      }
    } catch (MalformedURLException e) {
      LOG.debug(getPluginId() + " " + e.toString());
    }
    URL[] urls = arrayList.toArray(new URL[arrayList.size()]);
    fClassLoader = new PluginClassLoader(urls,
        PluginDescriptor.class.getClassLoader());
    return fClassLoader;
  }

  /**
   * @return Collection
   */
  private ArrayList<URL> getDependencyLibs() {
    ArrayList<URL> list = new ArrayList<URL>();
    collectLibs(list, this);
    return list;
  }

  /**
   * @param pLibs
   * @param pDescriptor
   */
  private void collectLibs(ArrayList<URL> pLibs, PluginDescriptor pDescriptor) {

    for (String id : pDescriptor.getDependencies()) {
      PluginDescriptor descriptor = PluginRepository.get(fConf)
          .getPluginDescriptor(id);
      for (URL url : descriptor.getExportedLibUrls()) {
        pLibs.add(url);
      }
      collectLibs(pLibs, descriptor);
    }
  }

  /**
   * Returns a I18N'd resource string. The resource bundles could be stored in
   * root directory of a plugin in the well know i18n file name conventions.
   * 
   * @param pKey
   * @param pLocale
   * @return String
   * @throws IOException
   */
  public String getResourceString(String pKey, Locale pLocale)
      throws IOException {
    if (fMessages.containsKey(pLocale.toString())) {
      ResourceBundle bundle = fMessages.get(pLocale.toString());
      try {
        return bundle.getString(pKey);
      } catch (MissingResourceException e) {
        return '!' + pKey + '!';
      }
    }
    try {
      ResourceBundle res = ResourceBundle.getBundle("messages", pLocale,
          getClassLoader());
      return res.getString(pKey);
    } catch (MissingResourceException x) {
      return '!' + pKey + '!';
    }
  }

  public String getProviderName() {
    return fProviderName;
  }

  public String getVersion() {
    return fVersion;
  }
}
