package org.greenstone.gatherer.collection;

import java.io.*;
import java.util.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.cdm.Argument;
import org.greenstone.gatherer.greenstone.LocalGreenstone;
import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
import org.greenstone.gatherer.util.ArrayTools;
import org.greenstone.gatherer.util.Codec;
import org.greenstone.gatherer.util.SafeProcess;
import org.greenstone.gatherer.util.Utility;
import org.greenstone.gatherer.util.XMLTools;
import org.w3c.dom.*;


/** This class parses options from Perl scripts: import.pl, buildcol.pl and explode_metadata_database.pl in particular. */
public class ScriptOptions
{
    /** The root element of the argument tree. */
    private Element arguments_element;
    /** The root element of the values tree. */
    private Element values_element;
    /** The name of an argument element. */
    static final private String ARGUMENT = "Argument";
    /** The name of the enabled attribute. */
    static final private String ENABLED = "enabled";
    /** The name of the 'false' value. */
    static final private String FALSE = "false";
    /** The name of a name attribute. */
    static final private String NAME = "name";
    /** The name of an option element. */
    static final private String OPTION = "Option";
    /** The name of the 'true' value. */
    static final private String TRUE = "true";


    /** Parse the arguments from running "script_name -xml" */
    public ScriptOptions(Element values_element, String script_name)
    {
	this.values_element = values_element;
	this.arguments_element = loadArguments(script_name, Configuration.getLanguage());
    }


    /** Retrieve the indexth argument */
    public Argument getArgument(int index)
    {
	Argument argument = null;

	NodeList option_list = arguments_element.getElementsByTagName(OPTION);
	if (index >= 0 && index < option_list.getLength()) {
	    argument = new Argument();
	    argument.parseXML((Element) option_list.item(index));
	}

	return argument;
    }


    /** Retrieve the number of arguments.
     */
    public int getArgumentCount() {
	// Determining the total count is easy.
	NodeList argument_elements = arguments_element.getElementsByTagName(OPTION);
	return argument_elements.getLength();
    }


    /** Retrieve the value of a certain argument. */
    public String getValue(String name) {
	return getValue(name, false);
    }

    /** Determine if the named argument value is enabled or disabled. */
    public boolean getValueEnabled(String name) {
	boolean result = false;
	String value = getValue(name, true);
	if(value != null && value.length() > 0) {
	    result = (value.equalsIgnoreCase(TRUE));
	}
	return result;
    }

    /** Retrieve all of the values as a String array ready to added to the script call. */
    public String[] getValues() {
	ArrayList values = new ArrayList();
	try {
	    NodeList arguments = values_element.getElementsByTagName(ARGUMENT);
	    for(int i = 0; i < arguments.getLength(); i++) {
		Element argument_element = (Element) arguments.item(i);
		// Determine if this argument is enabled.
		if(argument_element.getAttribute(ENABLED).equalsIgnoreCase(TRUE)) {
		    // First store the name of the argument prefixed with a '-'
		    values.add("-" + argument_element.getAttribute(NAME));
		    // Now retrieve the value.
		    String argument_value = Codec.transform(XMLTools.getValue(argument_element), Codec.DOM_TO_TEXT);
		    // If there is a value, tokenize it by commas only.
		    if(argument_value != null && argument_value.length() > 0) {
			values.add(argument_value);
		    }
		    argument_value = null;
		}
		argument_element = null;
	    }
	    arguments = null;
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
	return ArrayTools.arrayListToStringArray(values);
    }

   /** Remove the named value from the arguments */
    public void removeValue(String name) {
	try {
	    NodeList arguments = values_element.getElementsByTagName(ARGUMENT);
	    boolean found = false;
	    for(int i = 0; !found && i < arguments.getLength(); i++) {
		Element argument_element = (Element) arguments.item(i);
		// Is this the argument we want.
		if(argument_element.getAttribute(NAME).equalsIgnoreCase(name)) {
		    values_element.removeChild(argument_element);
		    found = true;
		}
		argument_element = null;
	    }
	    arguments = null;
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
    }

    /** Set the state of some argument. Note that value may be either a single String, an ArrayList of Strings or null. If enable is false then any existing argument for the named argument is disabled. */
    public void setValue(String name, boolean enable, String value) {
	///ystem.err.println("Set value: " + (arguments_element == build_values_element ? "Build" : "Import") + ", " + name + ", " + enable + ", " + value);
	try {
	    Document document = values_element.getOwnerDocument();
	    NodeList arguments = values_element.getElementsByTagName(ARGUMENT);
	    boolean found = false;
	    for(int i = 0; i < arguments.getLength(); i++) {
		Element argument_element = (Element) arguments.item(i);
		// If this the argument named.
		if(argument_element.getAttribute(NAME).equalsIgnoreCase(name)) {
		    found = true;
		    // Set whether this argument is enabled
		    argument_element.setAttribute(ENABLED, (enable ? TRUE : FALSE));
		    // Now we set the value, depending or what it is.
		    if(value == null) {
			// Nothing to do.
		    }
		    else {
			// Remove existing text nodes.
			while(argument_element.hasChildNodes()) {
			    argument_element.removeChild(argument_element.getFirstChild());
			}
			argument_element.appendChild(document.createTextNode((String)value));
		    }
		}
		argument_element = null;
	    }
	    // If we haven't found an instance of this argument, but should have, then add it.
	    if(!found && (enable || value != null)) {
		Element argument_element = document.createElement(ARGUMENT);
		argument_element.setAttribute(NAME, name);
		argument_element.setAttribute(ENABLED, (enable ? TRUE : FALSE));
		// Now we set the value, depending or what it is.
		if(value == null) {
		    // Nothing to do.
		}
		else {
		    argument_element.appendChild(document.createTextNode((String)value));
		}
		values_element.appendChild(argument_element);
	    }
	    arguments = null;
	    document = null;
	    // Make sure the collection knows to save.
	    Gatherer.c_man.getCollection().setSaved(false);
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
    }

 
    private String getValue(String name, boolean is_enabled) {
	String result = null;
	try {
	    NodeList arguments = values_element.getElementsByTagName(ARGUMENT);
	    for(int i = 0; result == null && i < arguments.getLength(); i++) {
		Element argument_element = (Element) arguments.item(i);
		// Is this the argument we want.
		if(argument_element.getAttribute(NAME).equalsIgnoreCase(name)) {
		    // Are we simply determining if this argument is enabled
		    if(is_enabled) {
			result = argument_element.getAttribute(ENABLED);
		    }
		    else {
			String argument_value = XMLTools.getValue(argument_element);
			if(argument_value != null) {
			    result = argument_value;
			}
			argument_value = null;
		    }
		}
		argument_element = null;
	    }
	    arguments = null;
	}
	catch (Exception error) {
	    DebugStream.printStackTrace(error);
	}
	return result;
    }


    private Element loadArguments(String filename, String lang)
    {
	// Run the required program.
	try {
	    Document document;

	    if (Gatherer.isGsdlRemote) {
		String script_output = Gatherer.remoteGreenstoneServer.getScriptOptions(filename, "");
		document = XMLTools.parseXML(new StringReader(script_output));
	    }
	    else {
		String args[];
		args = new String[6];
		args[0] = Configuration.perl_path;
		args[1] = "-S";
		args[2] = LocalGreenstone.getBinScriptDirectoryPath() + filename;
		args[3] = "-xml";
		args[4] = "-language";
		args[5] = lang;

		// Create the process.
		SafeProcess process = new SafeProcess(args);

		//for (int i=0; i<args.length; i++) { 
		//   System.err.print(args[i] + " ");
		//}
		//System.err.println("");
		
		// run the SafeProcess
		int exitVal = process.runProcess();
		if(exitVal != 0) {
		    throw new Exception("*** Error running ScriptOptions process, process exited with: "
					+ exitVal);
		}		
		
		// get the result and process it.
		// We expect XML to have come out of the process std error stream.
		String errStreamOutput = process.getStdError();
		///System.err.println("*********\nScriptOptions data, got:\n" + errStreamOutput + "\n**********\n");
		StringReader xmlStrReader = new StringReader(errStreamOutput);
		document = XMLTools.parseXML(xmlStrReader);
		xmlStrReader.close();


		if (document == null) {
		    // command has not generated XML, script has probably failed in some way
		    for (int i=0; i<args.length; i++) { 
			DebugStream.print(args[i] + " ");
		    }
		    DebugStream.println("");
		}
	    }

	    return document.getDocumentElement();
	}
	catch (Exception error) {
	    DebugStream.println("Error in ScriptOptions.loadArguments(): " + error);
	    DebugStream.printStackTrace(error);
	}

	return null;
    }
}
