/*
 *    IIIFServerBridge.java
 *    Copyright (C) 2018 New Zealand Digital Library, http://www.nzdl.org
 *
 *    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.gsdl3;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.greenstone.gsdl3.comms.Communicator;
import org.greenstone.gsdl3.comms.SOAPCommunicator;
import org.greenstone.gsdl3.core.IIIFMessageRouter;
import org.greenstone.gsdl3.core.IIIFReceptionist;
import org.greenstone.gsdl3.util.GSConstants;
import org.greenstone.gsdl3.util.GSParams;
import org.greenstone.gsdl3.util.GSXML;
import org.greenstone.gsdl3.util.IIIFXML;
import org.greenstone.gsdl3.util.XMLConverter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;



import java.io.StringWriter;

//import javax.xml.parsers.DocumentBuilder;
//import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;


/** a class the serve as a bridge between the Cantaloupe IIIF image server and 
 * Greenstone collections.  Loosely based on OAIServer
 *
 * the init method is called only once - the first time the bridge is established
 * then doGet() each time a document image request is made
 * @see Receptionist
 */
/**
 * IIIF server configuration instructions *
 * 
 */
public class IIIFServerBridge
{
	/** the receptionist to send messages to */
	protected IIIFReceptionist recept = null;

        /**
	 * The name of the site with which we will finally be dealing, whether it is
	 * a local site or a remote site through a communicator.
	 */
	protected String site = "";

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

        private void configure() throws UnavailableException
        {	    
	    // Read in IIIFConfig.xml (residing web/WEB-INF/classes/) and 
	    //use it to configure the receptionist. 
	    Element iiif_config = IIIFXML.getIIIFConfigXML();
	    if (iiif_config == null)
		{
		    logger.error("Fail to parse IIIF config file IIIFConfig.xml.");
		    throw new UnavailableException("IIIFServerBridge: Couldn't parse IIIFConfig.xml");
		}
	    // pass it to the receptionist
	    if (!this.recept.configure(iiif_config)) {
		logger.error("Couldn't configure IIIF receptionist");
		throw new UnavailableException("IIIFServerBridge: Couldn't configure receptionist"); 
	    }	    
	}

    /**
     * initialise the class
     */
    public void init(String site_name) throws UnavailableException, Exception
	{
	    org.greenstone.util.GlobalProperties.loadGlobalProperties("");
	    java.io.InputStream in = Class.forName("org.greenstone.util.GlobalProperties").getClassLoader().getResourceAsStream("global.properties");

	    String tomcat_context = System.getProperty("tomcat.context");
	    
	    // the receptionist -the servlet will talk to this
	    this.recept = new IIIFReceptionist();

	    //this site_name could consist of comma separated more than one site name.
	    IIIFMessageRouter message_router = new IIIFMessageRouter();

	    message_router.setSiteName(site_name);
	    // lots of work is done in this step; see IIIFMessageRouter.java
	    if (!message_router.configure()) {
		throw new UnavailableException("IIIFServerBridge: Couldn't configure IIIFMessageRouter");
	    }
	    this.recept.setSiteName(site_name);
	    this.recept.setMessageRouter(message_router);
	    
	    configure();
	} // end of init()

    /*
      Written but never used/tested
    */
    
        public void remote_init(String remote_site_name, String remote_site_type, String remote_site_address) throws UnavailableException
        { 
	    if (remote_site_name == null || remote_site_type == null || remote_site_address == null)
		{
		    logger.error("Initialisation paramters not all set!");
		    logger.error("You must have remote_site_name, remote_site_type and remote_site_address set");
		    throw new UnavailableException("IIIFServerBridge: incorrect remote connection parameters specified");
		}

	    // the receptionist -the servlet will talk to this
	    this.recept = new IIIFReceptionist();
	    
	    // talking to a remote site, create a communicator
	    Communicator communicator = null;
	    // we need to create the XML to configure the communicator
	    Document site_doc = XMLConverter.newDOM();
	    Element site_elem = site_doc.createElement(GSXML.SITE_ELEM);
	    site_elem.setAttribute(GSXML.TYPE_ATT, remote_site_type);
	    site_elem.setAttribute(GSXML.NAME_ATT, remote_site_name);
	    site_elem.setAttribute(GSXML.ADDRESS_ATT, remote_site_address);
	    
	    if (remote_site_type.equals(GSXML.COMM_TYPE_SOAP_JAVA))
		{
		    communicator = new SOAPCommunicator();
		}
	    else
		{
		    logger.error("IIIFServerBridge.init Error: invalid Communicator type: " + remote_site_type);
		    throw new UnavailableException("IIIFServerBridge: invalid communicator type");
		}
	    
	    if (!communicator.configure(site_elem))
		{
		    logger.error("IIIFServerBridge.init Error: Couldn't configure communicator");
		    throw new UnavailableException("IIIFServerBridge: Couldn't configure communicator");
		}
	    this.recept.setSiteName(remote_site_name);
	    this.recept.setMessageRouter(communicator);
	    
	    configure();
	} // end of remote_init()

    // Based on:
    // https://stackoverflow.com/questions/4412848/xml-node-to-string-in-java

    private static String nodeToString(Node node) {
	StringWriter sw = new StringWriter();
	try {
	    Transformer t = TransformerFactory.newInstance().newTransformer();
	    t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
	    t.setOutputProperty(OutputKeys.INDENT, "yes");
	    t.transform(new DOMSource(node), new StreamResult(sw));
	} catch (TransformerException te) {
	    System.out.println("nodeToString Transformer Exception");
	}
	return sw.toString();
    }

        public String doGetDocumentMessage(String identifier)
	{
	    String result = "";
	    
	    String[] pairs = new String[2];
	    pairs[0] = "verb=GetRecord";
	    pairs[1] = "identifier="+identifier;
	    
		String verb = "GetRecord";
		Document response_doc = XMLConverter.newDOM();
		//Element xml_response = IIIFXML.createBasicResponse(response_doc, verb, pairs);
		//Element verb_elem = null;
		
		// compose the request message to the receptionist
		Document request_doc = XMLConverter.newDOM();
		Element xml_message = request_doc.createElement(GSXML.MESSAGE_ELEM);
		Element xml_request = request_doc.createElement(GSXML.REQUEST_ELEM);
		xml_request.setAttribute(GSXML.TO_ATT, verb);
		addParams(xml_request, pairs);

		xml_message.appendChild(xml_request);

		Node xml_result = this.recept.process(xml_message);
		if (xml_result == null)
		{
			logger.error("xml_result is null");
			//verb_elem = IIIFXML.createErrorElement(response_doc, "Internal error", "");
			//xml_response.appendChild(verb_elem);
		}
		else
		{
		    //logger.info("***** DEBUG: xml_result");		    
		    //logger.info(nodeToString(xml_result));
		     
			//
			// All response elements are in the form (with a corresponding verb
			// name): <message> <response> <verb> ... </verb> </response> </message>
			//
			Node res = GSXML.getChildByTagName(xml_result, GSXML.RESPONSE_ELEM);
			if (res == null)
			{
			    logger.error("response element in xml_result is null");
			    //verb_elem = IIIFXML.createErrorElement(response_doc, "Internal error", "");
			}
			else {
			    Element verb_elem = GSXML.getFirstElementChild(res); // GetRecord
			    Node record_node = GSXML.getFirstElementChild(verb_elem); // record
			    Element metadata_list_elem = (Element)GSXML.getChildByTagName(record_node,"metadata"); // metadata

			    Element assocfilepath_metadata_elem = (Element)GSXML.getChildByTagName(metadata_list_elem,"assocfilepath");

			    if (assocfilepath_metadata_elem == null) {
				logger.error("Failed to find metadata 'assocfilepath' for Document " + identifier);
				//verb_elem = IIIFXML.createErrorElement(response_doc, "Internal error", "");
			    }
			    else {
				String assocfilepath_metadata_val = GSXML.getNodeText(assocfilepath_metadata_elem);

				Element image_metadata_elem = (Element)GSXML.getChildByTagName(metadata_list_elem,"Image");
				if (assocfilepath_metadata_elem == null) {
				    logger.error("Failed to find metadata 'Image' for Document " + identifier);
				}
				else {
				    String image_metadata_val = GSXML.getNodeText(image_metadata_elem);

				    result = assocfilepath_metadata_val + "/" + image_metadata_val;
				}
			    }
			}

			//xml_response.appendChild(response_doc.importNode(verb_elem, true));
		}
		return result;
	}

	/** append parameter elements to the request sent to the receptionist */
	public void addParams(Element request, String[] pairs)
	{
	  Document doc = request.getOwnerDocument();
		// no params apart from the verb
		if (pairs == null || pairs.length < 2)
			return;

		/**
		 * the request xml is composed in the form: <request> <param name=.../>
		 * <param name=.../> </request> (No paramList element in between).
		 */
		for (int i = 1; i < pairs.length; i++)
		{
			//the first pair in pairs is the verb=xxx
			int index = pairs[i].indexOf("=");
			if (index != -1)
			{ //just a double check
			  Element param = GSXML.createParameter(doc, pairs[i].substring(0, index), IIIFXML.iiifDecode(pairs[i].substring(index + 1)));
			  request.appendChild(param);
			}
		}
	}


	public void destroy()
	{
		recept.cleanUp();
	}

}
