//   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.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

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

import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import de.ugoe.cs.autoquest.eventcore.IEventType;
import de.ugoe.cs.autoquest.plugin.http.HTTPUtils;
import de.ugoe.cs.autoquest.plugin.http.SOAPUtils;

/**
 * <p>
 * A simplified SOAP event type that only contains the name of the called method and the name of the
 * service.
 * </p>
 * 
 * @author Steffen Herbold
 */
public class SimpleSOAPEventType implements IEventType {

    /**
     * <p>
     * Defines if this event represents an request or an response.
     * </p>
     * 
     * @author Steffen Herbold
     */
    public enum CallType {
        REQUEST, RESPONSE
    }

    /**  */
    private static final long serialVersionUID = 1L;

    /**
     * <p>
     * the SOAP method called in this request
     * </p>
     */
    private final String calledMethod;

    /**
     * <p>
     * the name of the service; this is either the path or, if a path map is available
     * </p>
     */
    private final String serviceName;

    /**
     * <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 body of the SOAP message; storage as serialized XML string to allow object serialization
     * </p>
     */
    private final String soapMsgBody;

    /**
     * <p>
     * defines whether this event represents a request or a response
     * </p>
     */
    private final CallType callType;

    /**
     * reference to the {@link EqualSOAPDataMap} associated with the sequence this event belongs to.
     */
    private transient EqualSOAPDataMap equalBodyMap;

    /**
     * <p>
     * Constructor. Creates a new SimpleSOAPEventType with a EqualSOAPDATAMap. Should always call
     * initEqualBodyMap afterwards.
     * </p>
     * 
     */
    public SimpleSOAPEventType(String calledMethod,
                               String serviceName,
                               String clientName,
                               Element soapMsgBody,
                               EqualSOAPDataMap equalRequestsMap,
                               CallType callType)
    {
        if (calledMethod == null) {
            throw new IllegalArgumentException("called method must not be null");
        }
        if (serviceName == null) {
            throw new IllegalArgumentException("serviceName must not be null");
        }
        if (clientName == null) {
            throw new IllegalArgumentException("clientName must not be null");
        }
        this.calledMethod = calledMethod;
        this.serviceName = serviceName;
        this.clientName = clientName;
        this.equalBodyMap = equalRequestsMap;
        this.soapMsgBody = SOAPUtils.getSerialization(soapMsgBody);
        this.callType = callType;
        
        if (equalBodyMap != null) {
            equalBodyMap.add(this, this.soapMsgBody);
        }
    }

    /**
     * <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 service called in this request
     * </p>
     * 
     * @return the name of the service called in this request
     */
    public String getServiceName() {
        return serviceName;
    }

    /**
     * <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>
     * returns the body of the SOAP request; how the body is determined is defined by the
     * {@link RequestBodyMode}.
     * </p>
     * 
     * @return body of the SOAP request
     */
    public Element getSoapMsgBody() {
        return createDOMElement(soapMsgBody);
    }

    /**
     * <p>
     * returns a randomly draw request body for the called method using
     * {@link EqualSOAPDataMap#getRandom(SimpleSOAPEventType)}
     * </p>
     * 
     * @return randomly drawn body of the SOAP request
     */
    public Element getRandomSoapMsgBody() {
        if (equalBodyMap == null) {
            throw new RuntimeException(
                                       "cannot use random mode is no request map is available; different data missing");
        }
        return createDOMElement(equalBodyMap.getRandom(this));
    }

    /**
     * <p>
     * returns the {@link EqualSOAPDataMap} associated with this event
     * </p>
     * 
     * @return the {@link EqualSOAPDataMap}
     */
    public EqualSOAPDataMap getEqualSOAPDataMap() {
        return equalBodyMap;
    }

    public CallType getCallType() {
        return callType;
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.autoquest.eventcore.IEventType#getName()
     */
    @Override
    public String getName() {
        return "(" + callType + ":" + clientName + "->" + serviceName + ", " + calledMethod + ")";
    }

    /*
     * (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 SimpleSOAPEventType) {
            boolean callTypesEqual = true;
            if (callType == null) {
                if (((SimpleSOAPEventType) obj).callType == null) {
                    callTypesEqual = true;
                }
                else {
                    callTypesEqual = false;
                }
            }
            else {
                callTypesEqual = callType.equals(((SimpleSOAPEventType) obj).callType);
            }
            return callTypesEqual &&
                HTTPUtils.equals(calledMethod, ((SimpleSOAPEventType) obj).calledMethod) &&
                HTTPUtils.equals(serviceName, ((SimpleSOAPEventType) obj).serviceName) &&
                HTTPUtils.equals(clientName, ((SimpleSOAPEventType) obj).clientName);
        }
        else {
            return false;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.autoquest.plugin.http.eventcore.HTTPEventType#hashCode()
     */
    @Override
    public int hashCode() {
        int hashCode = 13;
        if (callType != null) {
            hashCode += callType.hashCode();
        }
        if (calledMethod == null || serviceName == null || clientName == null) {
            System.out.print("fu!");
        }
        return hashCode + calledMethod.hashCode() + serviceName.hashCode() + clientName.hashCode();
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "SimpleSOAPEventType" + getName();
    }

    private Element createDOMElement(String requestBody) {
        try {
            return DocumentBuilderFactory.newInstance().newDocumentBuilder()
                .parse(new ByteArrayInputStream(requestBody.getBytes())).getDocumentElement();
        }
        catch (SAXException | IOException | ParserConfigurationException e) {
            return null;
        }
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeObject(equalBodyMap);
    }

    private void readObject(ObjectInputStream in)
        throws ClassNotFoundException, IOException
    {
        in.defaultReadObject();
        equalBodyMap = (EqualSOAPDataMap) in.readObject();
        if ( equalBodyMap == null) {
            throw new InvalidObjectException("null");
        }
    }
}
