package org.greenstone.gsdl3.action;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

import org.apache.log4j.Logger;
import org.greenstone.gsdl3.core.ModuleInterface;
import org.greenstone.gsdl3.util.Dictionary;
import org.greenstone.gsdl3.util.GSConstants;
import org.greenstone.gsdl3.util.GSParams;
import org.greenstone.gsdl3.util.GSPath;
import org.greenstone.gsdl3.util.GSXML;
import org.greenstone.gsdl3.util.GSXSLT;
import org.greenstone.gsdl3.util.UserContext;
import org.greenstone.gsdl3.util.XMLConverter;
import org.greenstone.gsdl3.util.XMLTransformer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/** base class for Actions */
abstract public class Action
{

	/** the system set up variables */
	protected HashMap<String, Object> config_params = null;
	
  /** the xml element languageList */
  protected Element language_list = null;
	/** a converter class to parse XML and create Docs */
	protected XMLConverter converter = null;
  /** a transformer class in case the action wants to run XSLT itself */
  protected XMLTransformer transformer = null;
	/**
	 * a reference to the message router that it must talk to to get info. it
	 * may be a communicator acting as a proxy, but it doesn't care about that
	 */
	protected ModuleInterface mr = null;

	static Logger logger = Logger.getLogger(org.greenstone.gsdl3.action.Action.class.getName());

	public Action()
	{
		this.converter = new XMLConverter();
		this.transformer = new XMLTransformer();
	}

	/** the config variables must be set before configure is called */
	public void setConfigParams(HashMap<String, Object> params)
	{
		this.config_params = params;
	}

	/** sets the message router */
	public void setMessageRouter(ModuleInterface m)
	{
		this.mr = m;
	}

  /** give the action access to the list of languages */
  public void setLanguageList(Element lang_list) {
    this.language_list = lang_list;
  }
	public boolean configure()
	{
		// does nothing yet
		return true;
	}

	/**
	 * process takes an xml representation of cgi args and returns the page of
	 * results - may be in html/xml/other depending on the output att of the
	 * request
	 */
	public String process(String xml_in)
	{

		Document message_doc = this.converter.getDOM(xml_in);
		if (message_doc == null)
		{
			logger.error("Couldn't parse request");
			logger.error(xml_in);
			return null;
		}
		Node result = process(message_doc);
		return this.converter.getString(result);
	}

	/** the main process method - must be implemented in subclass */
	abstract public Node process(Node xml_in);

	/**
	 * tell the param class what its arguments are.
	 * if an action has its own arguments, this should add them to 
	 * the params object - particularly
	 * important for args that need to be saved to the session
	 */
	public boolean addActionParameters(GSParams params)
	{
		return true;
	}

  protected void getRequiredMetadataNames(HashSet<String> meta_names, Element format, Element request) {
    if (format != null) {
      GSXSLT.findExtraMetadataNames(format, meta_names);
    }
    if (request != null) {
      // look for extraMetadata
      Element extraMetaListElem = (Element) GSXML.getChildByTagName(request, GSXML.EXTRA_METADATA + GSXML.LIST_MODIFIER);
      if (extraMetaListElem != null)
      {
        NodeList extraMetaList = extraMetaListElem.getElementsByTagName(GSXML.EXTRA_METADATA);
        for (int i = 0; i < extraMetaList.getLength(); i++)
        {
          meta_names.add(((Element) extraMetaList.item(i)).getAttribute(GSXML.NAME_ATT));
        }
      }
      
    }
  }


	protected Element createMetadataParamList(Document doc, HashSet<String> metadata_names)
	{
		Element param_list = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);

		Element param = null;
		Iterator<String> i = metadata_names.iterator();
		while (i.hasNext())
		{
			String name = i.next();
			param = doc.createElement(GSXML.PARAM_ELEM);
			param_list.appendChild(param);
			param.setAttribute(GSXML.NAME_ATT, "metadata");
			param.setAttribute(GSXML.VALUE_ATT, name);

		}
		return param_list;
	}

	protected boolean processErrorElements(Element message, Element page)
	{
		NodeList error_nodes = message.getElementsByTagName(GSXML.ERROR_ELEM);
		if (error_nodes.getLength() == 0)
		{
			return false;
		}
		Document owner = page.getOwnerDocument();
		for (int i = 0; i < error_nodes.getLength(); i++)
		{
			page.appendChild(owner.importNode(error_nodes.item(i), true));
		}
		return true;
	}

	/**
	 * Takes an XML element and adds the metadata of the current site to it.
	 * Useful for adding the current site's metadata to a response before
	 * sending it
	 * 
	 * @param element
	 *            the element to add site metadata to
	 * @param lang
	 *            the current language
	 * @param uid
	 *            the current user id
	 */
	protected void addSiteMetadata(Element element, UserContext userContext)
	{

		Document doc = XMLConverter.newDOM();
		//ADD SITE METADATA
		Element metadata_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_DESCRIBE, "", userContext);
		//create the element to put the params in
		Element param_list = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
		// want to get metadataList and displayItemList
		GSXML.addParameterToList(param_list, GSXML.SUBSET_PARAM , GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER);
		GSXML.addParameterToList(param_list, GSXML.SUBSET_PARAM , GSXML.DISPLAY_TEXT_ELEM  + GSXML.LIST_MODIFIER);
		
		metadata_request.appendChild(param_list);
		//create the message
		Element metadata_message = doc.createElement(GSXML.MESSAGE_ELEM);
		metadata_message.appendChild(metadata_request);
	
		//get response
		Element metadata_response_message = (Element) this.mr.process(metadata_message);
		//drill down to response
		Element metadata_response = (Element) GSXML.getChildByTagName(metadata_response_message, GSXML.RESPONSE_ELEM);
	
		//merge in metadata
		// *************** need to merge the displayItem lists too
		GSXML.mergeMetadataLists(element, metadata_response);
		GSXML.mergeSpecifiedLists(element, metadata_response, GSXML.DISPLAY_TEXT_ELEM);
	
	}

	protected void addInterfaceOptions(Element elem)
	{
		Document doc = elem.getOwnerDocument();
		
		Element documentOptionList = doc.createElement("interfaceOptions");
		for (Object key : this.config_params.keySet())
		{
			Element option = doc.createElement("option");
			option.setAttribute(GSXML.NAME_ATT, (String) key);
			option.setAttribute(GSXML.VALUE_ATT, this.config_params.get(key).toString());
			documentOptionList.appendChild(option);
		}
		elem.appendChild(elem.getOwnerDocument().importNode(documentOptionList, true));
	}

  protected Element getServiceDescription(String to, UserContext userContext)
  {
    Document doc = XMLConverter.newDOM();
    Element mr_info_message = doc.createElement(GSXML.MESSAGE_ELEM);
    Element mr_info_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_DESCRIBE, to, userContext);
    mr_info_message.appendChild(mr_info_request);
    Element mr_info_response = (Element) this.mr.process(mr_info_message);

    String path = GSXML.RESPONSE_ELEM;
    path = GSPath.appendLink(path, GSXML.SERVICE_ELEM);

    Element description = (Element)GSXML.getNodeByPath(mr_info_response, path);
    return description;
 
  }
	protected Element getFormatInfo(String to, UserContext userContext)
	{
		// Eclipse call hierarchy shows the element returned from this method is 
		// subsequently used in a 'importNode'.  For this reason it is safe here
		// for call up our own document DOM.  
		//
		// If this pattern changes for any reason, then the DOM will need to be
		// passed in as a parameter
		
		Document doc = XMLConverter.newDOM();
		
		Element mr_format_message = doc.createElement(GSXML.MESSAGE_ELEM);
		Element mr_format_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_FORMAT, to, userContext);
		mr_format_message.appendChild(mr_format_request);

		// process the message
		Element mr_response_message = (Element) this.mr.process(mr_format_message);
		// the response

		Element format_response = (Element) GSXML.getChildByTagName(mr_response_message, GSXML.RESPONSE_ELEM);

		Element format_elem = (Element) GSXML.getChildByTagName(format_response, GSXML.FORMAT_ELEM);
		if (format_elem != null)
		{
			Element global_format_elem = (Element) GSXML.getChildByTagName(format_response, GSXML.GLOBAL_FORMAT_ELEM);
			if (global_format_elem != null)
			{
				GSXSLT.mergeFormatElements(format_elem, global_format_elem, false);
			}
		}
		return format_elem;
	}

	protected String getTextString(String key, String lang, String[] args)
	{
	    return Dictionary.getTextString(key, lang, args, null, null, null, null);
	}

}
