package at.gv.util.client.mis.simple;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.xpath.XPathAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import at.gv.util.DOMUtils;

public class MISSimpleClient {

	private final static String SOAP_NS = "http://schemas.xmlsoap.org/soap/envelope/";
	private final static String MIS_NS = "http://reference.e-government.gv.at/namespace/mandates/mis/1.0/xsd";
	
	private static Element NS_NODE = null;
	
	private static Logger log = LoggerFactory.getLogger(MISSimpleClient.class);
	
	static {
		try {
			NS_NODE = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().createElement("test");
			NS_NODE.setAttribute("xmlns:soap", SOAP_NS);
			NS_NODE.setAttribute("xmlns:mis", MIS_NS);
    } catch (Exception e) {
	    log.warn("Error initializing namespace node.", e);
    }
	}
	
	public static List sendGetMandatesRequest(String webServiceURL, String sessionId) throws MISSimpleClientException {
		if (webServiceURL == null) {
			throw new NullPointerException("Argument webServiceURL must not be null.");
		}
		if (sessionId == null) {
			throw new NullPointerException("Argument sessionId must not be null.");
		}
		try {
	    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
	    Element mirElement = doc.createElementNS(MIS_NS, "MandateIssueRequest");
	    Element sessionIdElement = doc.createElementNS(MIS_NS, "SessionID");
	    sessionIdElement.appendChild(doc.createTextNode(sessionId));
	    mirElement.appendChild(sessionIdElement);
	    // send soap request
	    Element mandateIssueResponseElement = sendSOAPRequest(webServiceURL, mirElement);

	    // check for error
	    checkForError(mandateIssueResponseElement);
	    
	    // check for session id
	    NodeList mandateElements  = XPathAPI.selectNodeList(mandateIssueResponseElement, "//mis:MandateIssueResponse/mis:Mandates/mis:Mandate", NS_NODE);
	    
	    if (mandateElements == null || mandateElements.getLength() == 0) {
	    	throw new MISSimpleClientException("No mandates found in response.");
	    }
	    
	    ArrayList foundMandates = new ArrayList();
	    for (int i=0; i<mandateElements.getLength(); i++) {
	    	Element mandate = (Element) mandateElements.item(i);
	    	MISMandate misMandate = new MISMandate();
				if ("true".equalsIgnoreCase(mandate.getAttribute("ProfessionalRepresentative"))) {
					misMandate.setProfRep(true);
				}
				misMandate.setMandate(Base64.decodeBase64(DOMUtils.getText(mandate)));
				foundMandates.add(misMandate);
	    }
	    return foundMandates;
    } catch (ParserConfigurationException e) {
	    throw new MISSimpleClientException(e);
    } catch (DOMException e) {
    	throw new MISSimpleClientException(e);
    } catch (TransformerException e) {
    	throw new MISSimpleClientException(e);
    }
	}
	
	public static MISSessionId sendSessionIdRequest(String webServiceURL, byte[] idl, byte[] cert, String redirectURL, String refValue, String mandateIdentifier[]) throws MISSimpleClientException {
		if (webServiceURL == null) {
			throw new NullPointerException("Argument webServiceURL must not be null.");
		}
		if (idl == null) {
			throw new NullPointerException("Argument idl must not be null.");
		}
		if (redirectURL == null) {
			throw new NullPointerException("Argument redirectURL must not be null.");
		}
		if (refValue == null) {
			throw new NullPointerException("Argument refValue must not be null.");
		}
		try {
	    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
	    Element mirElement = doc.createElementNS(MIS_NS, "MandateIssueRequest");
	    Element idlElement = doc.createElementNS(MIS_NS, "IdentityLink");
	    idlElement.appendChild(doc.createTextNode(Base64.encodeBase64String(idl)));
	    mirElement.appendChild(idlElement);
	    if (cert != null && cert.length > 0) {
	    	Element certElement = doc.createElementNS(MIS_NS, "X509SignatureCertificate");
	    	certElement.appendChild(doc.createTextNode(Base64.encodeBase64String(cert)));
	    	mirElement.appendChild(certElement);
	    }
	    Element redirectElement = doc.createElementNS(MIS_NS, "RedirectURL");
	    redirectElement.appendChild(doc.createTextNode(redirectURL));
	    mirElement.appendChild(redirectElement);
	    Element refValElement = doc.createElementNS(MIS_NS, "ReferenceValue");
	    refValElement.appendChild(doc.createTextNode(refValue));
	    mirElement.appendChild(refValElement);
	    if (mandateIdentifier != null && mandateIdentifier.length > 0) {
	    	Element filtersElement = doc.createElementNS(MIS_NS, "Filters");
	    	Element mandateIdentifiersElement = doc.createElementNS(MIS_NS, "MandateIdentifiers");
	    	for (int i=0; i<mandateIdentifier.length; i++) {
	    		Element mandateIdentifierElement = doc.createElementNS(MIS_NS, "MandateIdentifier");
	    		mandateIdentifierElement.appendChild(doc.createTextNode(mandateIdentifier[i]));
	    		mandateIdentifiersElement.appendChild(mandateIdentifierElement);
	    	}
	    	filtersElement.appendChild(mandateIdentifiersElement);
	    	mirElement.appendChild(filtersElement);
	    }
	    // send soap request
	    Element mandateIssueResponseElement = sendSOAPRequest(webServiceURL, mirElement);

	    // check for error
	    checkForError(mandateIssueResponseElement);
	    
	    // check for session id
	    Node sessionIdNode = ((Node) XPathAPI.selectSingleNode(mandateIssueResponseElement, "//mis:MandateIssueResponse/mis:SessionID/text()", NS_NODE));
	    if (sessionIdNode == null) {
	    	throw new MISSimpleClientException("SessionId not found in response.");
	    }
	    String sessionId = sessionIdNode.getNodeValue();
	    
	    Node guiRedirectURLNode = ((Node) XPathAPI.selectSingleNode(mandateIssueResponseElement, "//mis:MandateIssueResponse/mis:GuiRedirectURL/text()", NS_NODE));
	    if (guiRedirectURLNode == null) {
	    	throw new MISSimpleClientException("GuiRedirectURL not found in response.");
	    }
	    String guiRedirectURL = guiRedirectURLNode.getNodeValue();
	    
	    // create return object
	    MISSessionId msid = new MISSessionId();
	    msid.setSessiondId(sessionId);
	    msid.setRedirectURL(guiRedirectURL);
	    
	    return msid;
    } catch (ParserConfigurationException e) {
	    throw new MISSimpleClientException(e);
    } catch (DOMException e) {
    	throw new MISSimpleClientException(e);
    } catch (TransformerException e) {
    	throw new MISSimpleClientException(e);
    }		
	}
	
	private static void checkForError(Element mandateIssueResponseElement) throws MISSimpleClientException {
		if (mandateIssueResponseElement == null) {
			throw new NullPointerException("Argument mandateIssueResponseElement must not be null.");
		}
		try {
	    Element errorElement = (Element) XPathAPI.selectSingleNode(mandateIssueResponseElement, "/mis:MandateIssueResponse/mis:Error", NS_NODE);
	    if (errorElement != null) {
	    	String code = ((Node) XPathAPI.selectSingleNode(mandateIssueResponseElement, "/mis:MandateIssueResponse/mis:Error/mis:Code/text()", NS_NODE)).getNodeValue();
	    	String text = ((Node) XPathAPI.selectSingleNode(mandateIssueResponseElement, "/mis:MandateIssueResponse/mis:Error/mis:Text/text()", NS_NODE)).getNodeValue();
	    	throw new MISSimpleClientException("Fehler beim Abfragen des Online-Vollmachten Services: " + code + " / " + text); 
	    }
    } catch (TransformerException e) {
	    throw new MISSimpleClientException(e);
    }
	}
	
	private static Element sendSOAPRequest(String webServiceURL, Element request) throws MISSimpleClientException {
		if (webServiceURL == null) {
			throw new NullPointerException("Argument webServiceURL must not be null.");
		}
		if (request == null) {
			throw new NullPointerException("Argument request must not be null.");
		}
		try {
			HttpClient httpclient = HttpClients.createDefault();	
			HttpPost post = new HttpPost(webServiceURL);
			StringEntity re = new StringEntity(DOMUtils.serializeNode(packIntoSOAP(request)),"text/xml");
			post.setEntity(re);
			
			HttpResponse response = httpclient.execute(post);
			if (response.getStatusLine().getStatusCode() != 200) {
				throw new MISSimpleClientException("Invalid HTTP response code " + response.getStatusLine().getStatusCode());
			}
			return unpackFromSOAP(DOMUtils.parseXmlNonValidating(post.getEntity().getContent()));
		} catch(IOException e) {
			throw new MISSimpleClientException(e);
		} catch (TransformerException e) {
			throw new MISSimpleClientException(e);
    } catch (ParserConfigurationException e) {
    	throw new MISSimpleClientException(e);
    } catch (SAXException e) {
    	throw new MISSimpleClientException(e);
    }
	}
	
	private static Element packIntoSOAP(Element element) throws MISSimpleClientException {
		try {
			Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
			Element soapEnvelope = doc.createElement("Envelope");
			soapEnvelope.setAttribute("xmlns", SOAP_NS);
			Element soapBody = doc.createElement("Body");
			soapEnvelope.appendChild(soapBody);
			soapBody.appendChild(doc.importNode(element, true));
			return soapEnvelope;
		} catch(ParserConfigurationException e) {
			throw new MISSimpleClientException(e);
		}
	}
	
	private static Element unpackFromSOAP(Element element) throws MISSimpleClientException {
		try {
			return (Element) XPathAPI.selectSingleNode(element, "/soap:Envelope/soap:Body/child::*[position()=1]", NS_NODE);
		} catch(TransformerException e) {
			throw new MISSimpleClientException(e);
		}
	}
	
}