package org.greenstone.gsdl3.action;

import java.io.Serializable;
import java.util.HashMap;

import org.apache.log4j.Logger;
import org.greenstone.gsdl3.core.MessageRouter;
import org.greenstone.gsdl3.service.CollectionGroups;
import org.greenstone.gsdl3.util.GSParams;
import org.greenstone.gsdl3.util.GSPath;
import org.greenstone.gsdl3.util.GSXML;
import org.greenstone.gsdl3.util.UserContext;
import org.greenstone.gsdl3.util.XMLConverter;
import org.greenstone.util.GlobalProperties;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

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

  public static final String HOME_PAGE = "home";
  public static final String ABOUT_PAGE = "about";
  public static final String PREFS_PAGE = "pref";
  public static final String GLI4GS3_PAGE = "gli4gs3";
  public static final String VERIFY_PAGE = "verify";

  // this gets set to the groupInfo service name if there is one.
  protected String groupInfoServiceName = null;

  public boolean configure() {

    // work out if we have a collection group service or not
      if (this.mr instanceof MessageRouter && ((MessageRouter)this.mr).pingModule(CollectionGroups.GROUP_CONTENT_SERVICE)) {
        this.groupInfoServiceName = CollectionGroups.GROUP_CONTENT_SERVICE;
      } else {
        this.groupInfoServiceName = "NO_GROUPS";
      }
      

    return true;
  }
  
  public Node process(Node message_node)
	
  {
    Element message = GSXML.nodeToElement(message_node);
    Document doc = XMLConverter.newDOM();
	    
    Element request = (Element) GSXML.getChildByTagName(message, GSXML.REQUEST_ELEM);
    Element paramList = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
    String collection = "";
    if (paramList != null)
      {
	HashMap<String, Serializable> params = GSXML.extractParams(paramList, false);
	if (params != null && params.get(GSParams.COLLECTION) != null)
	  {
	    collection = (String) params.get(GSParams.COLLECTION);
	  }
      }

    // the page name is the subaction
    String page_name = request.getAttribute(GSXML.SUBACTION_ATT);
    if (page_name.equals("")) {
	if (collection.equals("")) {
	    // if no page or collection specified, assume home page
	    page_name = HOME_PAGE;
	} else {
	    // we have specified a collection, assume about page  
	    page_name = ABOUT_PAGE;
	}
	// we need to save this to the request for other places to know whats going on, eg transforming receptionist
	request.setAttribute(GSXML.SUBACTION_ATT, page_name); 
    }
      
    Element result = doc.createElement(GSXML.MESSAGE_ELEM);
    Element response;
    if (page_name.equals(HOME_PAGE)) {
      
      response = homePage(request);
      
    }
    else if (page_name.equals(GLI4GS3_PAGE)) {
      
      response = gli4gs3Page(request);
      
    } else if (collection.equals("")) {
      // we are not in a collection. eg could be library prefs or other page
      response = generalLibraryPage(request, page_name);
    } else {
      // we are in a collection
      response = collectionPage(request, page_name, collection);
    }
    
    
    result.appendChild(doc.importNode(response, true));
    ///ogger.debug("page action result: " + this.converter.getPrettyString(result));

    return result;
  }

  // A general library page just adds site metadata and interface options.
  // page specific customisation: prefs: add language list
  protected Element generalLibraryPage(Element request, String page_name) {

    Document doc = XMLConverter.newDOM();
    UserContext userContext = new UserContext(request);
    Element response = doc.createElement(GSXML.RESPONSE_ELEM);
    addSiteMetadata(response, userContext);
    addInterfaceOptions(response);
    if (page_name.equals(PREFS_PAGE) && this.language_list != null) {
      response.appendChild(doc.importNode(this.language_list, true));
    }
    return response;
  }

  // a general collection page. Need to add collection info and info about its services
  protected Element collectionPage(Element request, String page_name, String collection) {

    Document doc = XMLConverter.newDOM();
    
    UserContext userContext = new UserContext(request);
    // extract the params from the cgi-request, 
    Element cgi_paramList = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
    HashMap<String, Serializable> params = GSXML.extractParams(cgi_paramList, false);
    
    // get the collection or cluster description
    Element coll_about_message = doc.createElement(GSXML.MESSAGE_ELEM);
    
    Element coll_about_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_DESCRIBE, collection, userContext);
    coll_about_message.appendChild(coll_about_request);
    
    Element coll_about_response = (Element) this.mr.process(coll_about_message);
    
    Element response = (Element) GSXML.getChildByTagName(coll_about_response, GSXML.RESPONSE_ELEM);

    if (response == null) {
      // what should we be returning?
      return null;
    }
    
    //add the site metadata
    addSiteMetadata(response, userContext);
    addInterfaceOptions(response);

    if (page_name.equals(PREFS_PAGE) && this.language_list != null) {
      response.appendChild(response.getOwnerDocument().importNode(this.language_list, true));
    }

    if (page_name.equals(ABOUT_PAGE)) {
      // get service descriptions. Actually only want displayItems, but get everything for now
      NodeList services = coll_about_response.getElementsByTagName(GSXML.SERVICE_ELEM);
      if (services.getLength() > 0)
	{
	  sendMultipleRequests(doc, services, collection, GSXML.REQUEST_TYPE_DESCRIBE, userContext);
	}

    }
    // old code had adding a ct param to the paramList - for about/prefs page, only used for gs2 interface.
    // Since we don't support that anymore, am leaving it out.

    // add the global collection format info
    Element formatMessage = doc.createElement(GSXML.MESSAGE_ELEM);
    Element formatRequest = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_FORMAT, collection, userContext);
    formatMessage.appendChild(formatRequest);
    Element formatResponseMessage = (Element) this.mr.process(formatMessage);
    Element formatResponse = (Element) GSXML.getChildByTagName(formatResponseMessage, GSXML.RESPONSE_ELEM);
    
    Element globalFormat = (Element) GSXML.getChildByTagName(formatResponse, GSXML.FORMAT_ELEM);
    if (globalFormat != null)
      {
	response.appendChild(response.getOwnerDocument().importNode(globalFormat, true));
      }

    // security info for collection if we are a 'humanverify' page
    if (page_name.equals(VERIFY_PAGE)) {
      addSecurityInfo(request, collection, response);
    }
    
    return response;
  }


  protected Element homePage(Element request)
  {
    Document doc = XMLConverter.newDOM();

    UserContext userContext = new UserContext(request);

    // first, get the message router info
    Element info_message = doc.createElement(GSXML.MESSAGE_ELEM);
    Element info_request = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_DESCRIBE, "", userContext);
    //Create param list
    Element param_list_element = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
    info_request.appendChild(param_list_element);

    GSXML.addParameterToList(param_list_element, GSXML.SUBSET_PARAM, GSXML.CLUSTER_ELEM + GSXML.LIST_MODIFIER);
    GSXML.addParameterToList(param_list_element, GSXML.SUBSET_PARAM, GSXML.SERVICE_ELEM + GSXML.LIST_MODIFIER);
    GSXML.addParameterToList(param_list_element, GSXML.SUBSET_PARAM, GSXML.SITE_ELEM + GSXML.LIST_MODIFIER);
    GSXML.addParameterToList(param_list_element, GSXML.SUBSET_PARAM, GSXML.METADATA_ELEM + GSXML.LIST_MODIFIER);
    GSXML.addParameterToList(param_list_element, GSXML.SUBSET_PARAM, GSXML.DISPLAY_TEXT_ELEM + GSXML.LIST_MODIFIER);

    // are we using collection groups?
    // if not, get the coll list from MR
    if (this.groupInfoServiceName.equals ("NO_GROUPS")) {
      GSXML.addParameterToList(param_list_element, GSXML.SUBSET_PARAM, GSXML.COLLECTION_ELEM + GSXML.LIST_MODIFIER);
    }
    info_message.appendChild(info_request);

    //Send request to message router
    Element info_response_message = (Element) this.mr.process(info_message);
    //Check if it is not null
    if (info_response_message == null)
      {
	logger.error(" couldn't query the message router!");
	return null;
      }
    //Check if it is not null
    Element info_response = (Element) GSXML.getChildByTagName(info_response_message, GSXML.RESPONSE_ELEM);
    if (info_response == null)
      {
	logger.error("couldn't query the message router!");
	return null;
      }

    // import it into our current doc.
    info_response = (Element) doc.importNode(info_response, true);
		
    Element resp_service_list = (Element) GSXML.getChildByTagName(info_response, GSXML.SERVICE_ELEM + GSXML.LIST_MODIFIER);

    // TODO - is this true? can we run with no MR services? can't we still query for the collections/MR info?
    if (resp_service_list == null) {
      logger.error("No services available. Couldn't query the message router!");
      return null;
    }

    // if we are using the group service, get all the group info now
    if (!this.groupInfoServiceName.equals("NO_GROUPS")) {
      String group = null;
      Element cgi_paramList = (Element) GSXML.getChildByTagName(request, GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
      HashMap<String, Serializable> params = GSXML.extractParams(cgi_paramList, false);
      if (params != null) {
	group = (String) params.get(GSParams.GROUP);
	if (group != null && group.equals("")) {
	  group = null;
	}
      }
		  
      Element group_info_response = getGroupInfo(group, userContext);

      // Add all the needed parts of the response to the main page response
      Element collection_list = (Element) GSXML.getChildByTagName(group_info_response,
								  GSXML.COLLECTION_ELEM + GSXML.LIST_MODIFIER);
			
      if (collection_list != null) {
	info_response.appendChild(doc.importNode(collection_list, true));
      } else {
	logger.warn("Home page had no collection list");
      }
      Element group_list = (Element) GSXML.getChildByTagName(group_info_response,
							     GSXML.GROUP_ELEM + GSXML.LIST_MODIFIER);
      if (group_list != null) {
	info_response.appendChild(doc.importNode(group_list, true));
      }
      Element path_list = (Element) GSXML.getChildByTagName(group_info_response,
							    GSXML.PATH_ELEM + GSXML.LIST_MODIFIER);
      if (path_list != null) {
	info_response.appendChild(doc.importNode(path_list, true));
      }
		  
    }

    // second, get the metadata for each collection - we only want specific
    // elements but for now, we'll just get it all
    Element collection_list = (Element) GSXML.getChildByTagName(info_response, GSXML.COLLECTION_ELEM + GSXML.LIST_MODIFIER);
    if (collection_list != null)
      {
	NodeList colls = GSXML.getChildrenByTagName(collection_list, GSXML.COLLECTION_ELEM);
	if (colls.getLength() > 0)
	  {
	    sendMultipleRequests(doc, colls, null, GSXML.REQUEST_TYPE_DESCRIBE, userContext);
	  }
      } 

    // get metadata for any services
    Element service_list = (Element) GSXML.getChildByTagName(info_response, GSXML.SERVICE_ELEM + GSXML.LIST_MODIFIER);
    if (service_list != null)
      {
	NodeList services = GSXML.getChildrenByTagName(service_list, GSXML.SERVICE_ELEM);
	if (services.getLength() > 0)
	  {
	    sendMultipleRequests(doc, services, null, GSXML.REQUEST_TYPE_DESCRIBE, userContext);
	  }
      }

    // get metadata for service clusters
    Element cluster_list = (Element) GSXML.getChildByTagName(info_response, GSXML.CLUSTER_ELEM + GSXML.LIST_MODIFIER);
    if (cluster_list != null)
      {
	NodeList clusters = GSXML.getChildrenByTagName(cluster_list, GSXML.CLUSTER_ELEM);
	if (clusters.getLength() > 0)
	  {
	    sendMultipleRequests(doc, clusters, null, GSXML.REQUEST_TYPE_DESCRIBE, userContext);

	  }
      }

    addInterfaceOptions(info_response);
    // all the components have been merged into info_response

    return info_response;

  } // homePage

  /** sends a request to GroupCurrentContent service to get group info. null group will give top level info,
      otherwise will just give info for specified group */
  protected Element getGroupInfo(String group, UserContext userContext) {
    Document doc = XMLConverter.newDOM();
    // collections and groups list
    Element group_info_message = doc.createElement(GSXML.MESSAGE_ELEM);
    Element group_info_request = GSXML.createBasicRequest(doc, GSXML.TO_ATT,
							  groupInfoServiceName, userContext);
    group_info_message.appendChild(group_info_request);
    if (group != null) {
      Element param_list = doc.createElement(GSXML.PARAM_ELEM + GSXML.LIST_MODIFIER);
      GSXML.addParameterToList(param_list, GSParams.GROUP, group);
      group_info_request.appendChild(param_list);
    }
    // send off the request
    Element group_info_response_message = (Element) this.mr.process(group_info_message);
    Element group_info_response = (Element) GSXML.getChildByTagName(group_info_response_message,
								    GSXML.RESPONSE_ELEM);
    return group_info_response;
  }
  

  protected boolean addSecurityInfo(Element request, String collection, Element response) {
    logger.error("in add security");
    Document doc = XMLConverter.newDOM();
    Element securityMessage = doc.createElement(GSXML.MESSAGE_ELEM);
    Element securityRequest = GSXML.createBasicRequest(doc, GSXML.REQUEST_TYPE_SECURITY, collection, new UserContext(request));
    securityMessage.appendChild(securityRequest);
    
    Element securityResponse = (Element) GSXML.getChildByTagName(this.mr.process(securityMessage), GSXML.RESPONSE_ELEM);
    logger.error("security response = "+XMLConverter.getPrettyString(securityResponse));
    
    Document r_doc = response.getOwnerDocument();
    // just get the top level node and attributes
    // Element new_sec = (Element)r_doc.importNode(securityResponse, true); //createElement(GSXML.SECURITY_ELEMENT);
    Element new_sec = GSXML.duplicateWithNewName(r_doc, securityResponse, "security", true);
    // just in case this is present
    new_sec.removeAttribute("secretKey");
    logger.error("new security element = "+XMLConverter.getPrettyString(new_sec));
    response.appendChild(new_sec);
    return true;
    
  }

  protected boolean sendMultipleRequests(Document doc, NodeList items, String path_prefix, String request_type, UserContext userContext)
  {
    // we will send all the requests in a single message
    Element message = doc.createElement(GSXML.MESSAGE_ELEM);
    for (int i = 0; i < items.getLength(); i++)
      {
	Element c = (Element) items.item(i);
	String path = c.getAttribute(GSXML.NAME_ATT);
	if (path_prefix != null)
	  {
	    path = GSPath.appendLink(path_prefix, path);
	  }
	Element request = GSXML.createBasicRequest(doc, request_type, path, userContext);
	message.appendChild(request);
      }

    Element response_message = (Element) this.mr.process(message);

    NodeList responses = response_message.getElementsByTagName(GSXML.RESPONSE_ELEM);
    // check that have same number of responses as requests
    if (items.getLength() != responses.getLength())
      {
	logger.error("didn't get a response for each request - somethings gone wrong!");
	return false;
      }

    for (int i = 0; i < items.getLength(); i++)
      {
	Element c1 = (Element) items.item(i);
	Element c2 = (Element) GSXML.getChildByTagName((Element) responses.item(i), c1.getTagName());
	if (c1 != null && c2 != null && c1.getAttribute(GSXML.NAME_ATT).endsWith(c2.getAttribute(GSXML.NAME_ATT)))
	  {
	    GSXML.mergeElements(c1, c2);
	  }
	//else
        //{
            // If eg a service returns an empty response for a describe request, then we get to here.
            // its ok, lets not print lots of uninformative log messages about it
//	    logger.debug(" response does not correspond to request!");
        //}

      }

    return true;

  }

  protected Element gli4gs3Page(Element request)
  {
    Document doc = XMLConverter.newDOM();
		
    String lang = request.getAttribute(GSXML.LANG_ATT);
    String uid = request.getAttribute(GSXML.USER_ID_ATT);

    Element page_response = doc.createElement(GSXML.RESPONSE_ELEM);

    Element applet_elem = doc.createElement("Applet");
    page_response.appendChild(applet_elem);
    applet_elem.setAttribute("ARCHIVE", "SignedGatherer.jar"); // SignedGatherer.jar should be placed in web/applet. 
    applet_elem.setAttribute("CODE", "org.greenstone.gatherer.WebGatherer");
    applet_elem.setAttribute("CODEBASE", "applet"); // SignedGatherer.jar is in web/applet. But CODEBASE is the *URL* path to the (jar) file containing the main class, and is relative to documentroot "web".
    applet_elem.setAttribute("HEIGHT", "50");
    applet_elem.setAttribute("WIDTH", "380");
		
    Element gwcgi_param_elem = doc.createElement("PARAM");
    gwcgi_param_elem.setAttribute("name", "gwcgi");
    String library_name = GlobalProperties.getGSDL3WebAddress();
    gwcgi_param_elem.setAttribute("value", library_name);
    applet_elem.appendChild(gwcgi_param_elem);

    Element gsdl3_param_elem = doc.createElement("PARAM");
    gsdl3_param_elem.setAttribute("name", "gsdl3");
    gsdl3_param_elem.setAttribute("value", "true");
    applet_elem.appendChild(gsdl3_param_elem);
				
    // When an applet doesn't work in the browser, set the default display text to provide a link to the JNLP file to run with Java Web Start
    // The display text will be:
    // 		Applets don't seem to work in your browser. In place of the GLI Applet, try running its alternative <a href="applet/GLIappWebStart.jnlp">Java Web Start (JNLP) version</a>
    Node default_text = doc.createTextNode("Applets don't seem to work in your browser. In place of the GLI Applet, try running its alternative ");
    Element link_to_jnlp = doc.createElement("a");
    link_to_jnlp.setAttribute("href", "applet/GLIappWebStart.jnlp");
    Node anchor_text = doc.createTextNode("Java Web Start (JNLP) version");
    link_to_jnlp.appendChild(anchor_text);		
    applet_elem.appendChild(default_text);
    applet_elem.appendChild(link_to_jnlp);
		
    return page_response;
  }
}
