//   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;

import java.io.StringWriter;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Element;
import org.w3c.dom.Node;

import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.plugin.http.eventcore.SOAPEventType;
import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType;

/**
 * <p>
 * Utilities for working with SOAP events. Their main task is to simply working with
 * {@link SimpleSOAPEventType} and {@link SOAPEventType}.
 * </p>
 * 
 * @author Steffen Herbold
 */
public class SOAPUtils {
    
    /**
     * <p>
     * Replaces events with a {@link SOAPEventType} with new events of {@link SimpleSOAPEventType}
     * and no target.
     * </p>
     * 
     * @param sequence
     *            sequence where the events are replaced
     * @param keepOnlySOAPEvents
     *            if true, all events that are not of SOAPEventType or SimpleSOAPEventType are
     *            removed
     * @return sequence with {@link SimpleSOAPEventType}s instead of {@link SOAPEventType}s
     */
    public static List<Event> convertToSimpleSOAPEvent(List<Event> sequence,
                                                       boolean keepOnlySOAPEvents)
    {
        List<Event> newSequence = null;
        if (sequence != null) {
            newSequence = new LinkedList<>();
            for (Event event : sequence) {
                if (event.getType() instanceof SOAPEventType) {
                    SOAPEventType eventType = (SOAPEventType) event.getType();
                    newSequence.add(new Event(new SimpleSOAPEventType(eventType.getCalledMethod(),
                                                                      eventType.getServiceName(),
                                                                      eventType.getClientName(),
                                                                      eventType.getSoapRequestBody())));
                }
                else {
                    if (!keepOnlySOAPEvents || event.getType() instanceof SimpleSOAPEventType) {
                        newSequence.add(event);
                    }
                }
            }
        }
        return newSequence;
    }
    
    /**
     * <p>
     * Removes all non SOAP events from a sequence. Warning: this change is performed in-place!
     * </p>
     * 
     * @param sequence
     *            sequence where the events are replaced
     */
    public static void removeNonSOAPEvents(List<Event> sequence)
    {
        for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
            Event event = eventIter.next();
            if (!(event.getType() instanceof SOAPEventType)) {
                eventIter.remove();
            }
        }
    }

    /**
     * <p>
     * Helper function to get the name of a service from a SOAP event.
     * </p>
     * 
     * @param event
     *            event for which the service name is retrieved
     * @return service name
     */
    public static String getServiceNameFromEvent(Event event) {
        if (event.getType() instanceof SOAPEventType) {
            return ((SOAPEventType) event.getType()).getServiceName();
        }
        else if (event.getType() instanceof SimpleSOAPEventType) {
            return ((SimpleSOAPEventType) event.getType()).getServiceName();
        }
        else {
            throw new RuntimeException(
                                       "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " +
                                           event.getType().getClass().getName());
        }
    }

    /**
     * 
     * <p>
     * Helper function to get the called method from a SOAP event
     * </p>
     * 
     * @param event
     *            event for which the called method is retrieved
     * @return called method
     */
    public static String getCalledMethodFromEvent(Event event) {
        if (event.getType() instanceof SOAPEventType) {
            return ((SOAPEventType) event.getType()).getCalledMethod();
        }
        else if (event.getType() instanceof SimpleSOAPEventType) {
            return ((SimpleSOAPEventType) event.getType()).getCalledMethod();
        }
        else {
            throw new RuntimeException(
                                       "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " +
                                           event.getType().getClass().getName());
        }
    }

    /**
     * <p>
     * Helper function to get the name of a client from a SOAP event.
     * </p>
     * 
     * @param event
     *            event for which the client name is retrieved
     * @return service name
     */
    public static String getClientNameFromEvent(Event event) {
        if (event.getType() instanceof SOAPEventType) {
            return ((SOAPEventType) event.getType()).getClientName();
        }
        else if (event.getType() instanceof SimpleSOAPEventType) {
            return ((SimpleSOAPEventType) event.getType()).getClientName();
        }
        else {
            throw new RuntimeException(
                                       "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " +
                                           event.getType().getClass().getName());
        }
    }

    /**
     * <p>
     * Helper function to get the body of a SOAP request.
     * </p>
     * 
     * @param event
     *            event for which the SOAP request body is retrieved
     * @return body of the SOAP event
     */
    public static Element getSoapRequestBodyFromEvent(Event event) {
        Element requestBody = null;
        if (event.getType() instanceof SOAPEventType) {
            requestBody = ((SOAPEventType) event.getType()).getSoapRequestBody();
        }
        else if (event.getType() instanceof SimpleSOAPEventType) {
            requestBody = ((SimpleSOAPEventType) event.getType()).getSoapRequestBody();
        }
        return requestBody;
    }
    
    /**
     * <p>
     * returns the XML serialization of a DOM node; located here because it is used for SOAP request bodies
     * </p>
     * 
     * @param node
     *            DOM node that is serialized
     * @return XML serialization as String
     */
    public static String getSerialization(Node node) {
        try {
            StringWriter writer = new StringWriter();
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.transform(new DOMSource(node), new StreamResult(writer));
            return writer.toString();
        }
        catch (TransformerFactoryConfigurationError | TransformerException e) {
            throw new IllegalArgumentException(
                                               "could not create serialization for SOAP request body.",
                                               e);
        }
    }
    
    /**
     * <p>
     * prevent instantiation
     * </p>
     */
    private SOAPUtils() {}
}
