//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package de.ugoe.cs.autoquest.plugin.http.eventcore;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;
import java.util.logging.Level;

import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

import org.w3c.dom.Element;

import de.ugoe.cs.autoquest.plugin.http.HTTPUtils;
import de.ugoe.cs.autoquest.plugin.http.logdata.Header;
import de.ugoe.cs.autoquest.plugin.http.logdata.HttpExchange;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * represents the specific HTTP event for a SOAP message exchange. It contains the SOAP request
 * and response envelopes and is more concrete when comparing object. E.g., it considers the
 * called SOAP operation on performing comparison.
 * </p>
 * 
 * @author Patrick Harms
 */
public final class SOAPEventType extends HTTPEventType {

    /**  */
    private static final long serialVersionUID = 1L;
    
    /**
     * <p>
     * the SOAP request belonging to the event
     * </p>
     */
    transient private final SOAPMessage soapRequest;
    
    /**
     * <p>
     * the SOAP response belonging to the event
     * </p>
     */
    transient private final SOAPMessage soapResponse;
    
    /**
     * <p>
     * the SOAP method called in this request
     * </p>
     */
    private final String calledMethod;
    
    /**
     * <p>
     * the name of the client; this is either the host/ip and port of the sender or, if a path
     * map is available, a human readable name that may be based also on the receiver
     * </p>
     */
    private final String clientName;
   
    /**
     * <p>
     * the name of the service; this is either the path or, if a path map is available, a human
     * readable name that is mapped to the path
     * </p>
     */
    private final String serviceName;
    
    /**
     * <p>
     * the human readable name of this event type
     * </p>
     */
    private final String name;

    /**
     * <p>
     * initializes the event type with the represented HTTP exchange and the already extracted
     * SOAP request and response.
     * </p>
     *
     * @param exchange     the represented HTTP exchange
     * @param soapRequest  the already extracted SOAP request
     * @param soapResponse the already extracted SOAP response
     * @param urlNameMap   used to map paths of servives to logical names
     */
    public SOAPEventType(HttpExchange exchange, SOAPMessage soapRequest, SOAPMessage soapResponse, Properties urlNameMap) {
        super(exchange);
        this.soapRequest = soapRequest;
        this.soapResponse = soapResponse;
        this.calledMethod = normalizeCalledMethod(determineCalledMethod(exchange, soapRequest));
        
        String path = null;
        
        if ((exchange.getRequest() != null) && (exchange.getRequest().getUrl() != null)) {
            try {
                path = new URL(exchange.getRequest().getUrl()).getPath();
            }
            catch (MalformedURLException e) {
                // ignore and continue
            }
        }
        
        StringBuffer nameBuffer = new StringBuffer("SOAPEvent");
        
        // determine the client name
        if (urlNameMap == null) {
            clientName = exchange.getSender().getHost() + ":" + exchange.getSender().getPort();
        }
        else {
            // check for a mapping of the sender host
            String key = "clientName.sender." + exchange.getSender().getHost();
            String value = urlNameMap.getProperty(key);
            
            if (value == null) {
                key += ":" + exchange.getSender().getPort();
                value = urlNameMap.getProperty(key);
            }
            
            if (value == null) {
                key = "clientName.receiver.server." + exchange.getReceiver().getHost();
                value = urlNameMap.getProperty(key);
            }
            
            if (value == null) {
                key += ":" + exchange.getReceiver().getPort();
                value = urlNameMap.getProperty(key);
            }
            
            if (value == null) {
                key = "clientName.receiver.port." + exchange.getReceiver().getPort();
                value = urlNameMap.getProperty(key);
            }
            
            if (value == null) {
                key = "clientName.default";
                value = urlNameMap.getProperty(key);
            }

            if (value != null) {
                clientName = value;
            }
            else {
                clientName = exchange.getSender().getHost() + ":" + exchange.getSender().getPort();
            }
        }

        nameBuffer.append("(");
        nameBuffer.append(clientName);
        
        // determine the service name
        if (path != null) {
            nameBuffer.append(", ");
            if (urlNameMap == null) {
                serviceName = path;
            }
            else {
                String value = urlNameMap.getProperty("serviceName.path." + path);
                if (value != null) {
                    serviceName = value;
                }
                else {
                    serviceName = path;
                }
            }
            nameBuffer.append(serviceName);
        }
        else {
            serviceName = "NA";
        }
        
        if (calledMethod != null) {
            nameBuffer.append(", ");
            nameBuffer.append(calledMethod);
        }
        
        String senderStr = HTTPUtils.toString(exchange.getSender());
        String receiverStr = HTTPUtils.toString(exchange.getReceiver());
        
        if ((senderStr != null) && (receiverStr != null)) {
            nameBuffer.append(", ");
            nameBuffer.append(senderStr);
            nameBuffer.append(" --> ");
            nameBuffer.append(receiverStr);
        }
        else if (senderStr != null) {
            nameBuffer.append(", ");
            nameBuffer.append(senderStr);
        }
        else if (receiverStr != null) {
            nameBuffer.append(", ");
            nameBuffer.append(receiverStr);
        }
        
        nameBuffer.append(")");
        
        this.name = nameBuffer.toString();
    }

    /**
     * <p>
     * the SOAP method called in this request
     * </p>
     *
     * @return the SOAP method called in this request
     */
    public String getCalledMethod() {
        return calledMethod;
    }
    
    /**
     * <p>
     * the name of the client calling in this request
     * </p>
     *
     * @return the name of the client calling in this request
     */
    public String getClientName() {
        return clientName;
    }
    
    /**
     * <p>
     * the name of the service called in this request
     * </p>
     *
     * @return the name of the service called in this request
     */
    public String getServiceName() {
        return serviceName;
    }

    /**
     * @return the soapRequest
     */
    public SOAPMessage getSoapRequest() {
        return soapRequest;
    }

    /**
     * @return the soapResponse
     */
    public SOAPMessage getSoapResponse() {
        return soapResponse;
    }
    
    /**
     * @return the body of the soapRequest
     */
    public Element getSoapRequestBody() {
        try {
            return soapRequest.getSOAPBody();
        }
        catch (SOAPException e) {
            return null;
        }
    }
    
    /**
     * @return the body of the soapResponse
     */
    public Element getSoapResponseBody() {
        try {
            return soapResponse.getSOAPBody();
        }
        catch (SOAPException e) {
            return null;
        }
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.eventcore.IEventType#getName()
     */
    @Override
    public String getName() {
        return name;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.plugin.http.eventcore.HTTPEventType#toString()
     */
    @Override
    public String toString() {
        return name;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.plugin.http.eventcore.HTTPEventType#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        else if (obj instanceof SOAPEventType) {
            if (!obj.getClass().isAssignableFrom(this.getClass())) {
                return false;
            }
            
            return
                super.equals(obj) &&
                HTTPUtils.equals(calledMethod, ((SOAPEventType) obj).calledMethod) &&
                HTTPUtils.equals(serviceName, ((SOAPEventType) obj).serviceName) &&
                HTTPUtils.equals(clientName, ((SOAPEventType) obj).clientName);
        }
        else {
            return false;
        }
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.plugin.http.eventcore.HTTPEventType#hashCode()
     */
    @Override
    public int hashCode() {
        int hashCode = super.hashCode();
        if (calledMethod != null) {
            hashCode += calledMethod.hashCode();
        }
        if( serviceName != null ) {
            hashCode += serviceName.hashCode();
        }
        if( clientName != null ) {
            hashCode += clientName.hashCode();
        }
        return hashCode;
    }

    /**
     * <p>
     * determines the name of the method called in a SOAP request either through the HTTP header
     * or through the SOAP body
     * </p>
     */
    private String determineCalledMethod(HttpExchange exchange, SOAPMessage soapRequest) {
        // first check for a header containing the SOAP action
        
        if ((exchange.getRequest() != null) && (exchange.getRequest().getHeaders() != null) &&
            (exchange.getRequest().getHeaders().getHeader() != null))
        {
            for (Header header : exchange.getRequest().getHeaders().getHeader()) {
                if ("SOAPAction".equalsIgnoreCase(header.getKey())) {
                    return header.getValue();
                }
            }
        }
        
        // if there is none, use the root element of the body
        try {
            if ((soapRequest.getSOAPBody() != null) &&
                (soapRequest.getSOAPBody().getChildNodes() != null) &&
                (soapRequest.getSOAPBody().getChildNodes().getLength() > 0))
            {
                return soapRequest.getSOAPBody().getChildNodes().item(0).getNodeName();
            }
        }
        catch (SOAPException e) {
            Console.traceln(Level.WARNING, "could not process SOAP message correctly: " + e);
            Console.logException(e);
        }
        
        return null;
    }
    
    /**
     * removes unnecessary characters from the method name
     */
    private String normalizeCalledMethod(String calledMethod) {
        if (calledMethod != null) {
            if (calledMethod.startsWith("\"") && calledMethod.endsWith("\"")) {
                calledMethod = calledMethod.substring(1, calledMethod.length() - 1);
            }
            if (calledMethod.startsWith("urn:")) {
                calledMethod = calledMethod.substring(4);
            }
            if (calledMethod.startsWith("http://")) {
                calledMethod = calledMethod.split("/")[calledMethod.split("/").length-1];
            }
        }
        return calledMethod;
    }
}
