package eu.osci.messagetypes;

import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Logger;

import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
import javax.xml.ws.Service;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.soap.AddressingFeature;
import javax.xml.ws.soap.SOAPFaultException;

import org.w3._2005._08.addressing.EndpointReferenceType;

import com.sun.xml.ws.api.addressing.AddressingVersion;
import com.sun.xml.ws.api.addressing.OneWayFeature;
import com.sun.xml.ws.api.message.Header;
import com.sun.xml.ws.api.message.HeaderList;
import com.sun.xml.ws.api.message.Message;
import com.sun.xml.ws.developer.WSBindingProvider;

import eu.osci.AddressingHeaderTag;
import eu.osci.ErrorTextTranslation;
import eu.osci.OSCIException;
import eu.osci.OSCIHeaderTag;
import eu.osci.helper.EndpointLocator;
import eu.osci.messageparts.OSCIFragmentCreator;
import eu.osci.messageparts.OSCIFragmentParser;


/**
 * This is the parent class for all OSCIRequest messages. All common fields with setters and getters and the
 * common methods for preparing and interpreting the SOAP message are offered.<br>
 * Copyright © 2010 Freie Hansestadt Bremen. <br>
 * Created by bremen online services Entwicklungs- und Betriebsgesellschaft mbH & Co. KG. <br>
 * This library (Starter Kit) can be used by anybody according to the Bremer Lizenz für freie
 * Softwarebibliotheken. The license terms can be viewed at www.osci.de or can be ordered at the
 * OSCI-Leitstelle (Die Senatorin für Finanzen, Freie Hansestadt Bremen, Postfach 10 15 40, 28015 Bremen).
 * 
 * @author R. Lindemann
 */
public abstract class OSCIRequestParent extends OSCIMessage
{

  /** Logger to log the messages. */
  private static Logger LOG = Logger.getLogger(OSCIRequestParent.class.getName());

  /** Addressing 'From' attribute */
  private EndpointReferenceType from = null;

  /** Addressing 'ReplyTo' attribute */
  protected EndpointReferenceType replyTo = null;

  boolean enableSetWSAReplyTo = false;

  /** OSCI 'TypeOfBusinessScenario' attribute */
  String typeOfBusinessScenario = null;

  boolean enableSetTypeOfBusinessSzenario = false;

  /** Addressing 'FaultTo' attribute */
  private EndpointReferenceType faultTo = null;

  protected String fetchIteratorID;

  /**
   * <b>For internal usage only!</b> To parse the incoming message in the case of fetch messages.
   */
  protected OSCIRequestParent()
  {
    super();
  }

  /**
   * Constructor with the necessary parameters. The URL to WSDL is given and the service and port out of it.
   * 
   * @param _wsdlURL The URL to the WSDL.
   * @param _serviceQName The service that will be used.
   * @param _portQName The port name that will be used.
   * @param _feature To add other features for the endpoint (see Metro Api-Doc).
   */
  protected OSCIRequestParent(URL _wsdlURL,
                              QName _serviceQName,
                              QName _portQName,
                              WebServiceFeature... _features)
  {
    LOG.fine("Constructor: url: " + _wsdlURL + " service: " + _serviceQName + " port: " + _portQName);
    if (_wsdlURL == null)
    {
      throw new IllegalArgumentException(
                                         OSCIMessage.text.getString(ErrorTextTranslation.NO_WSDL_FOUND.getConstant()));
    }
    if (_serviceQName == null)
    {
      throw new IllegalArgumentException(
                                         OSCIMessage.text.getString(ErrorTextTranslation.NO_SUCH_SERVICE.getConstant()));
    }
    if (_portQName == null)
    {
      throw new IllegalArgumentException(
                                         OSCIMessage.text.getString(ErrorTextTranslation.NO_SUCH_PORT.getConstant()));
    }
    GenericService service = new GenericService(_wsdlURL, _serviceQName);
    wsService = service;
    if ((_features != null) && (_features.length > 0))
    {
      WebServiceFeature[] features = new WebServiceFeature[_features.length + 2];
      for ( int i = 0 ; i < _features.length ; i++ )
      {
        features[i] = _features[i];
      }
      features[_features.length] = new AddressingFeature(true, true);
      features[_features.length + 1] = new OneWayFeature(true, AddressingVersion.W3C.anonymousEpr);
      LOG.fine("Count of features: " + features.length);
      jaxbDispatch = wsService.createDispatch(_portQName, Message.class, Service.Mode.MESSAGE, features);
    }
    else
    {
      jaxbDispatch = wsService.createDispatch(_portQName,
                                              Message.class,
                                              Service.Mode.MESSAGE,
                                              new AddressingFeature(true, true),
                                              new OneWayFeature(true, AddressingVersion.W3C.anonymousEpr));
    }
    bindingProvider = (WSBindingProvider)jaxbDispatch;
  }

  /**
   * Constructor for endpointLocator parameter with all necessary information.
   * 
   * @param endpointLocator The configured endpointLocator object.
   * @param features To add other features for the endpoint (see Metro Api-Doc).
   */
  protected OSCIRequestParent(EndpointLocator endpointLocator, WebServiceFeature... features)
  {
    this(endpointLocator.getWsdlFile(),
         endpointLocator.getServiceForTypeOfBusinessScenario(endpointLocator.getTypeOfBusinessScenario()),
         endpointLocator.getPortForTypeOfBusinessScenario(endpointLocator.getTypeOfBusinessScenario()),
         features);
    LOG.fine("Entry constructor with EndpointLocator");
  }

  /**
   * The constructor should be used in the case of multiple OSCI messages. If a {@link OSCIMsgBoxFetchRequest}
   * , {@link OSCIMsgBoxGetNextRequest} and then a {@link OSCIMsgBoxCloseRequest} will be sent, then the
   * previous OSCI message could be given to this constructor because the configuration is the same.
   * 
   * @param parent Previous OSCI message in the same sequence.
   */
  protected OSCIRequestParent(OSCIRequestParent parent)
  {
    LOG.fine("Entry constructor with OSCIRequestParent");
    faultTo = parent.getFaultTo();
    from = parent.getFrom();
    messageId = parent.getMessageId();
    typeOfBusinessScenario = parent.getTypeOfBusinessScenario();
    fetchIteratorID = parent.getFetchIteratorID();
    to = parent.getTo();
    wsService = parent.wsService;
    bindingProvider = parent.bindingProvider;
    jaxbDispatch = parent.jaxbDispatch;
  }

  /**
   * Returns the OSCI 'TypeOfBusinessScenario'. In most cases the value out of the WSDL will be used.
   */
  public String getTypeOfBusinessScenario()
  {
    return typeOfBusinessScenario;
  }

  /**
   * Sets the OSCI 'TypeOfBusinessScenario' attribute. This setter has no sense in most cases because the
   * 'TypeOfBusinessScenario' attribute out of the WSDL will be used. Be careful with this setter because if
   * the framework will put a 'typeOfBusinessScenario' header into the SOAP message there will be two headers
   * available.
   * 
   * @param typeOfBusinessScenario The 'TypeOfBusinessScenario' element that will be used.
   */
  public void setTypeOfBusinessScenario(String typeOfBusinessScenario)
  {
    enableSetTypeOfBusinessSzenario = true;
    this.typeOfBusinessScenario = typeOfBusinessScenario;
  }

  /**
   * Returns the addressing 'From' field.
   */
  public EndpointReferenceType getFrom()
  {
    return from;
  }

  /**
   * Sets the addressing 'From' attribute.
   * 
   * @param _from The 'From' element that will be used.
   */
  public void setFrom(EndpointReferenceType _from)
  {
    from = _from;
  }

  /**
   * Returns the fetchIteratorID in case of OSCI fetch messages.
   */
  public String getFetchIteratorID()
  {
    return fetchIteratorID;
  }

  /**
   * Returns the addressing 'ReplyTo' field. In case of symmetrical binding like HTTP this value must be
   * 'http://www.w3.org/2005/08/addressing/anonymous'. Be careful with this setter because if the framework
   * will put a 'ReplyTo' header into the SOAP message there will be two headers available.
   */
  public EndpointReferenceType getReplyTo()
  {
    return replyTo;
  }

  /**
   * Returns the addressing 'FaultTo' field.
   */
  public EndpointReferenceType getFaultTo()
  {
    return faultTo;
  }

  /**
   * Sends the OSCI message to the configured endpoint.
   * 
   * @param sendMessage
   * @return The response OSCI message.
   * @throws OSCIException In the case of OSCI SOAP faults.
   */
  protected Message sendMessage(Message sendMessage) throws OSCIException
  {
    LOG.fine("Entry sendMessage(Message)");
    try
    {
      return jaxbDispatch.invoke(sendMessage);
    }
    catch (SOAPFaultException ex)
    {
      LOG.severe("Processing SOAPFault");
      Iterator<?> subcodes = ex.getFault().getFaultSubcodes();
      while (subcodes.hasNext())
      {
        Object obj = subcodes.next();
        if (obj instanceof QName)
        {
          QName sub = (QName)obj;
          if (sub.getNamespaceURI().equals("osci"))
          {

            try
            {
              LOG.info("Reason: " + ex.getFault().getFaultReasonTexts().next());
              // TODO > Version 1.0 warum geht hier nicht ex.getFault().getFaultReasonText(Locale.ENGLISH
              // anstatt ex.getFault().getFaultReasonTexts().next().toString()
              OSCIException newEX = new OSCIException(ex.getFault().getFaultCode(), sub.getLocalPart(),
                                                      ex.getFault().getFaultReasonTexts().next().toString(),
                                                      ex);
              throw newEX;
            }
            catch (SOAPException e)
            {
              throw ex;
            }
          }
        }
      }
      throw ex;
    }
  }

  /**
   * Sets the addressing 'FaultTo' attribute. Should not be used with HTTP binding because the fault will be
   * returned on the back-channel. May force an exception from the framework if this setter will be used with
   * HTTP binding.
   * 
   * @param _faultTo The 'FaultTo' element that will be used.
   */
  public void setFaultTo(EndpointReferenceType _faultTo)
  {
    faultTo = _faultTo;
  }

  /**
   *{@inheritDoc}
   */
  @Override
  protected void prepareSendMessage(ArrayList<Header> headerList) throws JAXBException
  {
    LOG.fine("Entry prepareSendMessage prepared headers: " + headerList.size() + " headers");
    super.prepareSendMessage(headerList);
    // ################### Reference parameter #################
    if (typeOfBusinessScenario != null && enableSetTypeOfBusinessSzenario)
    {
      // TODO > Version 1.0 hier sollte noch isReferenceParameter="1" gesetzt werden
      LOG.fine("OSCI header 'TypeOfBusinessScenario' added");
      headerList.add(OSCIFragmentCreator.createOSCIHeader(OSCIHeaderTag.TYPEOFBUSINESS,
                                                          typeOfBusinessScenario));
    }
    // ################### wsa:ReplyTo #################
    if (replyTo != null && enableSetWSAReplyTo)
    {
      LOG.fine("Addressing header 'ReplyTo' added: " + replyTo.getAddress().getValue());
      headerList.add(OSCIFragmentCreator.createAddressingHeader(AddressingHeaderTag.REPLY_TO, replyTo));
    }
    // ################### wsa:From #################
    if (from != null)
    {
      LOG.fine("Addressing header 'From' added");
      headerList.add(OSCIFragmentCreator.createAddressingHeader(AddressingHeaderTag.FROM, from));
    }
    // ################### wsa:FaultTo #################
    if (faultTo != null)
    {
      LOG.fine("Addressing header 'FaultTo' added");
      headerList.add(OSCIFragmentCreator.createAddressingHeader(AddressingHeaderTag.FAULT_TO, faultTo));
    }
  }

  /**
   * Parses the addressing header from the incoming/sent OSCI message. The super method will be called and the
   * additional fields will be set.
   * 
   * @param headerList Header list of the incoming/sent OSCI message.
   * @throws JAXBException In case of parsing errors.
   */
  @Override
  protected void interpretWSAHeaders(HeaderList headerList) throws JAXBException
  {
    // ### Addressing Header ############
    super.interpretWSAHeaders(headerList);
    replyTo = (EndpointReferenceType)OSCIFragmentParser.createAddressingHeader(headerList,
                                                                               AddressingHeaderTag.REPLY_TO);
    from = (EndpointReferenceType)OSCIFragmentParser.createAddressingHeader(headerList,
                                                                            AddressingHeaderTag.FROM);
    faultTo = (EndpointReferenceType)OSCIFragmentParser.createAddressingHeader(headerList,
                                                                               AddressingHeaderTag.FAULT_TO);
  }

  /**
   * To provide a public constructor for the service class.<br>
   * Copyright © 2010 Freie Hansestadt Bremen. <br>
   * Created by bremen online services Entwicklungs- und Betriebsgesellschaft mbH & Co. KG. <br>
   * This library (Starter Kit) can be used by anybody according to the Bremer Lizenz für freie
   * Softwarebibliotheken. The license terms can be viewed at www.osci.de or can be ordered at the
   * OSCI-Leitstelle (Die Senatorin für Finanzen, Freie Hansestadt Bremen, Postfach 10 15 40, 28015 Bremen).
   * 
   * @author R. Lindemann
   */
  private static class GenericService extends Service
  {

    /**
     * <b>For internal usage only!</b>
     * 
     * @param wsdlLocation
     * @param serviceName
     */
    public GenericService(URL wsdlLocation, QName serviceName)
    {
      super(wsdlLocation, serviceName);
    }
  }
}
