/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * Author: Michael Dewsnip, NZDL Project, University of Waikato
 *
 * Copyright (C) 2006 New Zealand Digital Library Project
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */
package org.greenstone.gatherer.greenstone;

import java.io.*;
import java.util.*;
import javax.swing.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.cdm.Argument;
import org.greenstone.gatherer.cdm.Plugin;
import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
import org.greenstone.gatherer.util.SafeProcess;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.Utility;
import org.greenstone.gatherer.util.XMLTools;
import org.w3c.dom.*;
import org.xml.sax.*;


/** This class is for maintaining a list of known plug-ins, and importing new plugins using the parser. */
public class Plugins
{
    // A list of all the plugins in the core Greenstone "perllib/plugins" folder (arguments may not be loaded)
    static private ArrayList core_greenstone_plugins_list = null;
    // The name of the loaded collection
    static private String collection_name = null;
    // A list of all the plugins in the loaded collection's "perllib/plugins" folder (arguments may not be loaded)
    static private ArrayList collection_specific_plugins_list = new ArrayList();


    static public Plugin getPlugin(String plugin_name, boolean arguments_required)
    {
	Plugin plugin = null;
	boolean collection_specific = false;

	// Check the collection-specific plugins first
	for (int i = 0; i < collection_specific_plugins_list.size(); i++) {
	    Plugin collection_specific_plugin = (Plugin) collection_specific_plugins_list.get(i);
	    if (collection_specific_plugin.getName().equals(plugin_name)) {
		plugin = collection_specific_plugin;
		collection_specific = true;
		break;
	    }
	}

	// Try the core Greenstone plugins if necessary
	if (plugin == null) {
	    for (int i = 0; i < core_greenstone_plugins_list.size(); i++) {
		Plugin core_greenstone_plugin = (Plugin) core_greenstone_plugins_list.get(i);
		if (core_greenstone_plugin.getName().equals(plugin_name)) {
		    plugin = core_greenstone_plugin;
		    break;
		}
	    }
	}

	// If we've found the plugin, load its arguments now, if required
	if (plugin != null && arguments_required) {
	    if (!plugin.hasLoadedOptions()) {
		loadPluginInfo(plugin, collection_specific);
	    }
	    else {
		DebugStream.println("Already loaded arguments for " + plugin_name + "!");
	    }
	}

	return plugin;
    }


    /** Returns a new list from merging the collection-specific and the core Greenstone plugins. */
    static public ArrayList getPluginsList()
    {
	ArrayList plugins_list = new ArrayList();
	plugins_list.addAll(collection_specific_plugins_list);

	// Add in the core Greenstone plugins, taking care not to overwrite any collection-specific ones
	for (int i = 0; i < core_greenstone_plugins_list.size(); i++) {
	    Plugin core_greenstone_plugin = (Plugin) core_greenstone_plugins_list.get(i);

	    boolean found = false;
	    for (int j = 0; j < collection_specific_plugins_list.size(); j++) {
		Plugin collection_specific_plugin = (Plugin) collection_specific_plugins_list.get(j);
		if (core_greenstone_plugin.getName().equals(collection_specific_plugin.getName())) {
		    found = true;
		    break;
		}
	    }

	    if (!found) {
		plugins_list.add(core_greenstone_plugin);
	    }
	}

	return plugins_list;
    }


    static private void loadPluginInfo(Plugin plugin, boolean collection_specific)
    {
	DebugStream.println("Loading arguments for " + plugin.getName() + "...");

	// Run pluginfo.pl to get the list of plugins
	try {
	    String pluginfo_xml = null;
	    if (Gatherer.isGsdlRemote) {
		String pluginfo_options = "&plugin=" + plugin;
		if (collection_specific) {
		    pluginfo_options += "&collection=" + collection_name;
		}
		pluginfo_xml = Gatherer.remoteGreenstoneServer.getScriptOptions("pluginfo.pl", pluginfo_options);
	    }
	    else {
		ArrayList args = new ArrayList();
		args.add(Configuration.perl_path);
		args.add("-S");
		args.add(LocalGreenstone.getBinScriptDirectoryPath() + "pluginfo.pl");
		args.add("-gs_version");
		if (Gatherer.GS3) {
		    args.add("3");
		} else {
		    args.add("2");
		}
		if (collection_specific) {
		    args.add("-collection");
		    args.add(collection_name);
		    if (Gatherer.GS3) {
		      args.add("-site");
		      args.add(Configuration.site_name);
		    }		    
		}
		args.add("-xml");
		args.add("-language");
		args.add(Configuration.getLanguage());
		args.add(plugin.getName());

		// Run the pluginfo.pl process:
		// Create the process.
		SafeProcess process = new SafeProcess((String[]) args.toArray(new String[] { }));
		process.setSplitStdErrorNewLines(true);

		// run the SafeProcess
		int exitVal = process.runProcess();
		if(exitVal != 0) {
		    throw new Exception("*** Error running pluginfo.pl loadPlugInfo, process exited with: "
					+ exitVal);
		}
		// get the result: We expect XML to have come out of the process std error stream.
		pluginfo_xml = process.getStdError();
		// make sure to have parsed out any lines preceding the XML content
		pluginfo_xml = XMLTools.readXMLStream(pluginfo_xml).toString();
		///System.err.println("*********\nPluginInfo, got:\n" + pluginfo_xml + "\n**********\n");
	    }

	    // Check the XML output was obtained successfully
	    if (pluginfo_xml == null || pluginfo_xml.length() == 0) {
		plugin.setHasLoadedOptions(false); // failure to load options
		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CDM.PlugInManager.PlugIn_XML_Parse_Failed", plugin.getName()), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
		return;
	    } else {
		plugin.setHasLoadedOptions(true);
	    }

	    parsePluginInfoXML(plugin, pluginfo_xml);
	}
	catch (Exception exception) {
	    DebugStream.printStackTrace(exception);
	}
    }


    static public void loadPluginsList(String collection_name_arg)
    {
	DebugStream.println("In loadPluginsList()...");

	// If we're getting the collection-specific plugins, clear the old list no matter what
	if (collection_name_arg != null) {
	    collection_name = collection_name_arg;
	    collection_specific_plugins_list = new ArrayList();
	}

	// Run pluginfo.pl to get the list of plugins
	try {
	    String xml = null;
	    if (Gatherer.isGsdlRemote) {
		String pluginfo_options = "&listall";
		if (collection_name != null) {
		    pluginfo_options += "&collection=" + collection_name;
		}
		String pluginfo_output = Gatherer.remoteGreenstoneServer.getScriptOptions("pluginfo.pl", pluginfo_options);
		xml = pluginfo_output;
	    }
	    else {
		ArrayList args = new ArrayList();
		args.add(Configuration.perl_path);
		args.add("-S");		
		args.add(LocalGreenstone.getBinScriptDirectoryPath() + "pluginfo.pl");
		args.add("-gs_version");
		if (Gatherer.GS3) {
		    args.add("3");
		} else {
		    args.add("2");
		}
		if (collection_name != null) {
		    args.add("-collection");
		    args.add(collection_name);
		    if (Gatherer.GS3) {
		      args.add("-site");
		      args.add(Configuration.site_name);
		    }
		}
		args.add("-listall");
		args.add("-xml");
		
		// Run the pluginfo.pl process:
		// Create the process.
		SafeProcess process = new SafeProcess((String[]) args.toArray(new String[] { }));
		process.setSplitStdErrorNewLines(true);

		// run the SafeProcess
		int exitVal = process.runProcess();
		if(exitVal != 0) {
		    throw new Exception("*** Error running pluginfo.pl loadPluginsList, process exited with: "
					+ exitVal);
		}
		// get the result: We expect XML to have come out of the process std error stream.	
		xml = process.getStdError();
		// make sure to parse out any lines before the XML content, else running "gli -debug" results in an XML error:
		// for pluginfo.pl -listall, we see a "AutoloadConverters" (PDFBox) message
		// before actual XML output, which breaks XML parsing. 
		// This gets rid of output before "<?xml" in the way that the code did before the change to SafeProcess
		// Then we can call the same from method RemoteGreenstoneServer.java, so that running "client-gli -debug"
		// will work too.
		xml = XMLTools.readXMLStream(xml).toString();
		
		///System.err.println("*********\nPluginsList, got:\n" + xml + "\n**********\n");
	    }

	    // Check the XML output was obtained successfully
	    if (xml == null || xml.length() == 0) {
		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CDM.PluginManager.Plugin_List_XML_Parse_Failed"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
		return;
	    }

	    if (collection_name != null) {
		collection_specific_plugins_list = parsePluginsListXML(xml);
	    }
	    else {
		core_greenstone_plugins_list = parsePluginsListXML(xml);
	    }
	}
	catch (Exception exception) {
	    DebugStream.printStackTrace(exception);
	}
    }


    static private void parsePluginInfoXML(Plugin plugin, String xml)
    {
	Document document = XMLTools.parseXML(new StringReader(xml));
	if (document == null) {
	    plugin.setHasLoadedOptions(false); // failure to load the options/failed plugin
	    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CDM.PlugInManager.PlugIn_XML_Parse_Failed", plugin.getName()), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
	    return;
	} else {
	    plugin.setHasLoadedOptions(true);
	}

	parsePluginInfoXMLNode(plugin, document.getDocumentElement());
    }


    static private void parsePluginInfoXMLNode(Plugin plugin, Node root_node)
    {
	for (Node node = root_node.getFirstChild(); node != null; node = node.getNextSibling()) {
	    String node_name = node.getNodeName();

	    if (node_name.equalsIgnoreCase("Name")) {
		plugin.setName(XMLTools.getValue(node));
	    }
	    else if (node_name.equals("Desc")) {
		plugin.setDescription(XMLTools.getValue(node));
	    }
	    else if (node_name.equals("Abstract")) {
		plugin.setIsAbstract(XMLTools.getValue(node).equalsIgnoreCase(StaticStrings.YES_STR));
	    }
	    else if (node_name.equalsIgnoreCase("Explodes")) {
		plugin.setDoesExplodeMetadataDatabases(XMLTools.getValue(node).equalsIgnoreCase(StaticStrings.YES_STR));
	    }
	    else if (node_name.equalsIgnoreCase("SourceReplaceable")) { // looking for <SourceReplaceable> tag
		plugin.setDoesReplaceSrcDocsWithHtml(XMLTools.getValue(node).equalsIgnoreCase(StaticStrings.YES_STR));
	    }
	    else if (node_name.equalsIgnoreCase("Processes")) {
		plugin.setDefaultProcessExpression(XMLTools.getValue(node));
	    }
	    else if (node_name.equalsIgnoreCase("Blocks")) {
		plugin.setDefaultBlockExpression(XMLTools.getValue(node));
	    }
	    // Parse the plugin arguments
	    else if (node_name.equalsIgnoreCase("Arguments")) {
		for (Node argument_node = node.getFirstChild(); argument_node != null; argument_node = argument_node.getNextSibling()) {
		    // An option
		    if (argument_node.getNodeName().equalsIgnoreCase("Option")) {
			Argument argument = new Argument();
			argument.parseXML((Element) argument_node);
			plugin.addArgument(argument);
		    }
		}
	    }
	    // A super plugin class
	    else if (node_name.equalsIgnoreCase("PlugInfo")) {
		Plugin super_plugin = new Plugin();
		parsePluginInfoXMLNode(super_plugin, node);
		plugin.setSuper(super_plugin);
	    }
	}
    }


    static private ArrayList parsePluginsListXML(String xml)
    {
	ArrayList plugins_list = new ArrayList();
	
	Document document = XMLTools.parseXML(new StringReader(xml));
	Node root = document.getDocumentElement();
	for (Node node = root.getFirstChild(); node != null; node = node.getNextSibling()) {
	    String node_name = node.getNodeName();

	    if (node_name.equals("PlugInfo")) {
		Plugin plugin = new Plugin();
		parsePluginInfoXMLNode(plugin, node);
		plugins_list.add(plugin);
	    }
	}

	return plugins_list;
    }
}
