/**
 *#########################################################################
 *
 * 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.Classifier;
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.*;


public class Classifiers
{
    // A list of all the classifiers in the core Greenstone "perllib/classify" folder (arguments may not be loaded)
    static private ArrayList core_greenstone_classifiers_list = null;
    // The name of the loaded collection
    static private String collection_name = null;
    // A list of all the classifiers in the loaded collection's "perllib/classify" folder (arguments may not be loaded)
    static private ArrayList collection_specific_classifiers_list = new ArrayList();


    static public Classifier getClassifier(String classifier_name, boolean arguments_required)
    {
	Classifier classifier = null;
	boolean collection_specific = false;

	// Check the collection-specific classifiers first
	for (int i = 0; i < collection_specific_classifiers_list.size(); i++) {
	    Classifier collection_specific_classifier = (Classifier) collection_specific_classifiers_list.get(i);
	    if (collection_specific_classifier.getName().equals(classifier_name)) {
		classifier = collection_specific_classifier;
		collection_specific = true;
		break;
	    }
	}

	// Try the core Greenstone classifiers if necessary
	if (classifier == null) {
	    for (int i = 0; i < core_greenstone_classifiers_list.size(); i++) {
		Classifier core_greenstone_classifier = (Classifier) core_greenstone_classifiers_list.get(i);
		if (core_greenstone_classifier.getName().equals(classifier_name)) {
		    classifier = core_greenstone_classifier;
		    break;
		}
	    }
	}

	// If we've found the classifier, load its arguments now, if required
	if (classifier != null && arguments_required) {
	    if (classifier.getArguments().size() == 0 && classifier.didLoadingOptionsFail() == false) {
		loadClassifierInfo(classifier, collection_specific);
	    }
	    else {
		DebugStream.println("Already loaded arguments for " + classifier_name + "!");
	    }
	}

	return classifier;
    }


    /** Returns a new list from merging the collection-specific and the core Greenstone classifiers. */
    static public ArrayList getClassifiersList()
    {
	ArrayList classifiers_list = new ArrayList();
	classifiers_list.addAll(collection_specific_classifiers_list);

	// Add in the core Greenstone classifiers, taking care not to overwrite any collection-specific ones
	for (int i = 0; i < core_greenstone_classifiers_list.size(); i++) {
	    Classifier core_greenstone_classifier = (Classifier) core_greenstone_classifiers_list.get(i);

	    boolean found = false;
	    for (int j = 0; j < collection_specific_classifiers_list.size(); j++) {
		Classifier collection_specific_classifier = (Classifier) collection_specific_classifiers_list.get(j);
		if (core_greenstone_classifier.getName().equals(collection_specific_classifier.getName())) {
		    found = true;
		    break;
		}
	    }

	    if (!found) {
		classifiers_list.add(core_greenstone_classifier);
	    }
	}

	return classifiers_list;
    }


    static private void loadClassifierInfo(Classifier classifier, boolean collection_specific)
    {
	DebugStream.println("Loading arguments for " + classifier.getName() + "...");

	// Run classifierfo.pl to get the list of classifiers
	try {
	    String classinfo_xml = null;
	    if (Gatherer.isGsdlRemote) {
		String classinfo_options = "&classifier=" + classifier;
		if (collection_specific) {
		    classinfo_options += "&collection=" + collection_name;
		}
		classinfo_xml = Gatherer.remoteGreenstoneServer.getScriptOptions("classinfo.pl", classinfo_options);
	    }
	    else {
		ArrayList args = new ArrayList();
		args.add(Configuration.perl_path);
		args.add("-S");		
		args.add(LocalGreenstone.getBinScriptDirectoryPath() + "classinfo.pl");
		if (collection_specific) {
		    args.add("-collection");
		    args.add(collection_name);
		}
		args.add("-xml");
		args.add("-language");
		args.add(Configuration.getLanguage());
		args.add(classifier.getName());


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

		// run the SafeProcess
		int exitVal = process.runProcess();
		if(exitVal != 0) {
		    throw new Exception("*** Error running classinfo.pl loadClassifierInfo, ("+args.toString()+") process exited with: "
					+ exitVal);
		}
		// get the result: We expect XML to have come out of the process std error stream.
		classinfo_xml = process.getStdError();
		///System.err.println("*********\nClassifierInfo, got:\n" + classinfo_xml + "\n**********\n");
	    }

	    // Check the XML output was obtained successfully
	    if (classinfo_xml == null || classinfo_xml.length() == 0) {
		classifier.setLoadingOptionsFailed();
		JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CDM.ClassifierManager.Classifier_XML_Parse_Failed", classifier.getName()), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
		return;
	    }

	    parseClassifierInfoXML(classifier, classinfo_xml);
	}
	catch (Exception exception) {
	    DebugStream.printStackTrace(exception);
	}
    }


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

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

	// Run classifierfo.pl to get the list of classifiers
	try {
	    StringBuffer xml = null;
	    if (Gatherer.isGsdlRemote) {
		String classinfo_options = "&listall";
		if (collection_name != null) {
		    classinfo_options += "&collection=" + collection_name;
		}
		String classinfo_output = Gatherer.remoteGreenstoneServer.getScriptOptions("classinfo.pl", classinfo_options);
		xml = new StringBuffer(classinfo_output);
	    }
	    else {
		ArrayList args = new ArrayList();
		args.add(Configuration.perl_path);
		args.add("-S");
		args.add(LocalGreenstone.getBinScriptDirectoryPath() + "classinfo.pl");
		if (collection_name != null) {
		    args.add("-collection");
		    args.add(collection_name);
		}
		args.add("-listall");
		args.add("-xml");

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

		// run the SafeProcess
		int exitVal = process.runProcess();
		if(exitVal != 0) {
		    throw new Exception("*** Error running classinfo.pl loadClassifiersList ("+args.toString()+"), process exited with: "
					+ exitVal);
		}
		// get the result: We expect XML to have come out of the process std error stream.
		xml = new StringBuffer(process.getStdError());
		///System.err.println("*********\nClassifierList, 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.ClassifierManager.Classifier_List_XML_Parse_Failed"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
		return;
	    }

	    if (collection_name != null) {
		collection_specific_classifiers_list = parseClassifiersListXML(xml.toString());
	    }
	    else {
		core_greenstone_classifiers_list = parseClassifiersListXML(xml.toString());
	    }
	}
	catch (Exception exception) {
	    DebugStream.printStackTrace(exception);
	}
    }


    static private void parseClassifierInfoXML(Classifier classifier, String xml)
    {
	Document document = XMLTools.parseXML(new StringReader(xml));
	if (document == null) {
	    classifier.setLoadingOptionsFailed();
	    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CDM.ClassifierManager.Classifier_XML_Parse_Failed", classifier.getName()), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
	    return;
	}

	parseClassifierInfoXMLNode(classifier, document.getDocumentElement());
    }


    static private void parseClassifierInfoXMLNode(Classifier classifier, Node root_node)
    {
	for (Node node = root_node.getFirstChild(); node != null; node = node.getNextSibling()) {
	    String node_name = node.getNodeName();

	    if (node_name.equalsIgnoreCase("Name")) {
		classifier.setName(XMLTools.getValue(node));
	    }
	    else if (node_name.equals("Desc")) {
		classifier.setDescription(XMLTools.getValue(node));
	    }
	    else if (node_name.equals("Abstract")) {
		classifier.setIsAbstract(XMLTools.getValue(node).equalsIgnoreCase(StaticStrings.YES_STR));
	    }
	    // Parse the classifier 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);
			classifier.addArgument(argument);
		    }
		}
	    }
	    // A super classifier class
	    else if (node_name.equalsIgnoreCase("ClassInfo")) {
		Classifier super_classifier = new Classifier();
		parseClassifierInfoXMLNode(super_classifier, node);
		classifier.setSuper(super_classifier);
	    }
	}
    }


    static private ArrayList parseClassifiersListXML(String xml)
    {
	ArrayList classifiers_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("ClassInfo")) {
		Classifier classifier = new Classifier();
		parseClassifierInfoXMLNode(classifier, node);
		classifiers_list.add(classifier);
	    }
	}

	return classifiers_list;
    }
}
