package eu.osci.messagetypes;

import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import javax.xml.bind.JAXBException;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.WebServiceFeature;

import org.oasis_open.docs.ws_sx.ws_trust._200512.RequestSecurityTokenResponseCollectionType;
import org.w3._2002._03.xkms.CompoundResultType;
import org.w3._2005._08.addressing.EndpointReferenceType;
import org.w3._2005._08.addressing.MetadataType;
import org.w3._2005._08.addressing.RelatesToType;

import com.sun.xml.ws.api.SOAPVersion;
import com.sun.xml.ws.api.message.Header;
import com.sun.xml.ws.api.message.HeaderList;
import com.sun.xml.ws.api.message.Headers;
import com.sun.xml.ws.api.message.Message;

import eu.osci.AddressingHeaderTag;
import eu.osci.OSCIConstants;
import eu.osci.OSCIException;
import eu.osci.OSCIHeaderTag;
import eu.osci.OSCIRequestAction;
import eu.osci.helper.EndpointLocator;
import eu.osci.messageparts.JaxbContextHolder;
import eu.osci.messageparts.OSCIFragmentCreator;
import eu.osci.messageparts.OSCIFragmentParser;
import eu.osci.ws._2008._05.transport.DeliveryReceiptDemandType;
import eu.osci.ws._2008._05.transport.FetchedNotificationDemandType;
import eu.osci.ws._2008._05.transport.MsgTimeStampsType;
import eu.osci.ws._2008._05.transport.ReceptionReceiptDemandType;
import eu.osci.ws._2008._05.transport.TypeOfBusinessScenarioType;
import eu.osci.ws._2008._05.transport.X509TokenContainerType;


/**
 * This class represents the OSCIRequest message. It will send the correct OSCI SOAP message, parses the
 * incoming SOAP message and creates the object structure.<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 class OSCIRequest extends OSCIRequestParent
{

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

  private RequestSecurityTokenResponseCollectionType rstrCol;

  /** Addressing 'RelatesTo' attribute. */
  private List<RelatesToType> relatesTo = null;

  /** <b>For internal usage only!</b> This field will be set after a SOAP message was sent. */
  protected Message sendSOAPMessage;

  private X509TokenContainerType x509TokenContainer = null;

  /** Will be used to fill into the MsgTimeStampsType object in case of outgoing messages. */
  protected XMLGregorianCalendar obsoleteAfter = null;

  /** This field will be set in case of incoming messages. */
  protected MsgTimeStampsType msgtime = null;

  private ReceptionReceiptDemandType receptionReceiptDemand = null;

  private FetchedNotificationDemandType fetchedNotiDemand = null;

  private DeliveryReceiptDemandType deliveryReceiptDemand = null;

  protected List<CompoundResultType> compoundResultList = new ArrayList<CompoundResultType>();

  private MetadataType metaData;

  /**
   * <b>For internal usage only!</b>
   */
  protected OSCIRequest()
  {
    super();
  }

  /**
   * The constructor should be used in 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.
   */
  public OSCIRequest(OSCIRequestParent parent)
  {
    super(parent);
  }

  /**
   * <b>For internal usage only!</b>
   * 
   * @param soapMessage The incoming SOAP stream in the case of fetch OSCI messages.
   * @throws JAXBException In the case of parsing errors.
   */
  protected OSCIRequest(Message soapMessage) throws JAXBException
  {
    interpretHeaders(soapMessage.getHeaders());
  }

  /**
   * 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).
   */
  public OSCIRequest(EndpointLocator endpointLocator, WebServiceFeature... _features)
  {
    super(endpointLocator, _features);
  }

  /**
   * 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 _features To add other features for the endpoint (see Metro Api-Doc).
   */
  public OSCIRequest(URL _wsdlURL, QName _serviceQName, QName _portQName, WebServiceFeature... _features)
  {
    super(_wsdlURL, _serviceQName, _portQName, _features);
  }

  /**
   * Will be used to read the incoming headers and set it on this object
   * 
   * @param headerList The soap header out of the soap message.
   * @throws JAXBException In the case of parsing errors.
   */
  protected void interpretHeaders(HeaderList headerList) throws JAXBException
  {
    for ( Header header : headerList )
    {
      LOG.fine("Found header: " + header.getLocalPart());
    }
    // ### Addressing Header ############
    interpretWSAHeaders(headerList);

    relatesTo = OSCIFragmentParser.getAddressingRelatesTo(headerList);
    // ##### OSCI header ##########
    msgtime = (MsgTimeStampsType)OSCIFragmentParser.createOSCIHeader(headerList, OSCIHeaderTag.MSGTIMESTAMPS);
    if (msgtime != null)
    {
      obsoleteAfter = msgtime.getObsoleteAfter();
    }
    deliveryReceiptDemand = (DeliveryReceiptDemandType)OSCIFragmentParser.createOSCIHeader(headerList,
                                                                                           OSCIHeaderTag.DELIVERY_DEMAND);
    receptionReceiptDemand = OSCIFragmentParser.getReceiptDemand(headerList);
    fetchedNotiDemand = (FetchedNotificationDemandType)OSCIFragmentParser.createOSCIHeader(headerList,
                                                                                           OSCIHeaderTag.FETCHED_NOTIFICATION_DEMAND);
    x509TokenContainer = OSCIFragmentParser.getX509Container(headerList);
    TypeOfBusinessScenarioType typeOf = (TypeOfBusinessScenarioType)OSCIFragmentParser.createOSCIHeader(headerList,
                                                                                                        OSCIHeaderTag.TYPEOFBUSINESS);
    metaData = (MetadataType)OSCIFragmentParser.createAddressingHeader(headerList,
                                                                       AddressingHeaderTag.METADATA);
    if (typeOf != null)
      typeOfBusinessScenario = typeOf.getValue();
    // other header
    compoundResultList = OSCIFragmentParser.getCompoundResultType(headerList);
    rstrCol = OSCIFragmentParser.getRequestSecurityTokenResponseCollection(headerList);
    setAdditionalHeaderList(headerList);
  }

  /**
   * Internal method for preparing the sent SOAP message. The header elements of the request will be set here.
   * 
   * @param bindingProvider The binding provider to set the header elements.
   * @throws JAXBException In case of parsing or JAXB errors.
   */
  protected ArrayList<Header> prepareSendMessage() throws JAXBException
  {
    ArrayList<Header> headerList = new ArrayList<Header>();
    super.prepareSendMessage(headerList);

    // ################### MsgTimeStamps #################
    if (obsoleteAfter != null)
    {
      LOG.fine("'obsoleteAfter' added to header list");
      if (msgtime == null)
        msgtime = new MsgTimeStampsType();
      msgtime.setObsoleteAfter(obsoleteAfter);
      // msgtime.setId("msgTimeStamps" + OSCI20FragmentHelper.getRandom());
      headerList.add(OSCIFragmentCreator.createOSCIHeader(OSCIHeaderTag.MSGTIMESTAMPS, msgtime));
    }
    // ################### Delivery receipt demand #################
    if (deliveryReceiptDemand != null)
    {
      LOG.fine("'deliveryReceiptDemand' added to header list");
      headerList.add(OSCIFragmentCreator.createOSCIHeader(OSCIHeaderTag.DELIVERY_DEMAND,
                                                          deliveryReceiptDemand));
    }
    // ################### Reception receipt demand #################
    if (receptionReceiptDemand != null)
    {
      LOG.fine("'receptionReceiptDemand' added to header list");
      headerList.add(OSCIFragmentCreator.createOSCIHeader(OSCIHeaderTag.RECEIPT_DEMAND,
                                                          receptionReceiptDemand));
    }
    if (fetchedNotiDemand != null)
    {
      LOG.fine("'fetchedNotiDemand' added to header list");
      headerList.add(OSCIFragmentCreator.createOSCIHeader(OSCIHeaderTag.FETCHED_NOTIFICATION_DEMAND,
                                                          fetchedNotiDemand));
    }
    // ################### X509 token container #################
    if (x509TokenContainer != null)
    {
      LOG.fine("'X509TokenContainer' added to header list");
      Header header = OSCIFragmentCreator.createOSCIHeader(OSCIHeaderTag.X509CONTAINER, x509TokenContainer);
      LOG.fine("Namespace x509TokenHeader: " + header.getNamespaceURI());
      headerList.add(header);
    }
    // ################### Compound result #################
    if ((compoundResultList != null) && (compoundResultList.size() > 0))
    {
      for ( CompoundResultType validate : compoundResultList )
      {
        LOG.fine("CompoundResult added.");
        headerList.add(OSCIFragmentCreator.createXKMSHeader(OSCIConstants.QNAME_COMPOUND_RESULT, validate));
      }
    }
    // ################### wsa:RelatesTo #################
    if ((relatesTo != null) && (relatesTo.size() > 0))
    {
      Iterator<RelatesToType> itRelates = relatesTo.iterator();
      while (itRelates.hasNext())
      {
        LOG.fine("Addressing header 'RelatesTo' added");
        headerList.add(OSCIFragmentCreator.createAddressingHeader(AddressingHeaderTag.RELATES_TO,
                                                                  itRelates.next()));
      }
    }
    if (rstrCol != null)
    {
      LOG.fine("Adding OneTimeToken for fetch notification.");
      headerList.add(Headers.create(SOAPVersion.SOAP_12,
                                    JaxbContextHolder.getTrustJaxBContext().createMarshaller(),
                                    OSCIConstants.QNAME_RSTR_COLLECTION,
                                    rstrCol));
    }
    bindingProvider.setOutboundHeaders(headerList);
    return headerList;
  }

  /**
   * Returns the OneTimeTokens for the receipts.
   * 
   * @return A collection of RSTRs.
   */
  public RequestSecurityTokenResponseCollectionType getRequestSecurityTokenResponseCollectionType()
  {
    return rstrCol;
  }

  /**
   * Sets the OneTimeTokens for the receipts.
   * 
   * @param _rstrCol The RSTRs for the OneTimeToken.
   */
  public void setRequestSecurityTokenResponseCollectionType(RequestSecurityTokenResponseCollectionType _rstrCol)
  {
    rstrCol = _rstrCol;
  }

  /**
   * Sends the configured OSCI message with the given stream. All the header will be added to the SOAP message
   * and the given stream will set as given (without transformation) into the body element of the SOAP
   * message.
   * 
   * @param content The body stream to put into the SOAP message.
   * @return The OSCI response of the related request.
   * @throws JAXBException In the case of parsing or serialisation errors.
   * @throws OSCIException In the case of OSCI SOAP faults.
   */
  public OSCIResponse sendMessage(InputStream content) throws JAXBException, OSCIException
  {
    prepareSendMessage();

    return onlySendMessage(content);
  }

  /**
   * Internal method to send the message without preparing it.
   * 
   * @param content The payload of the SOAP message to send with this request message.
   * @return The response of associated to the send message.
   * @throws JAXBException In the case of parsing or serialisation errors.
   * @throws OSCIException In the case of OSCI SOAP faults.
   */
  protected OSCIResponse onlySendMessage(InputStream content) throws JAXBException, OSCIException
  {

    sendSOAPMessage = com.sun.xml.ws.api.message.Messages.createUsingPayload(new StreamSource(content),
                                                                             SOAPVersion.SOAP_12);
    Message rspMsg = sendMessage(sendSOAPMessage);
    interpretHeaders(sendSOAPMessage.getHeaders());
    OSCIResponse osciRspMessage = new OSCIResponse(rspMsg);
    return osciRspMessage;
  }

  /**
   * Sets the addressing 'ReplyTo' attribute on the OSCI message. Including the URL for reply messages.<br>
   * Be careful with this setter because if the framework will put a 'ReplyTo' header into the SOAP message
   * there will be two headers available.
   * 
   * @param _replyTo The 'ReplyTo' attribute.
   */
  public void setReplyTo(EndpointReferenceType _replyTo)
  {
    enableSetWSAReplyTo = true;
    replyTo = _replyTo;
  }

  /**
   * Returns the set addressing 'RelatesTo' attribute.
   */
  public List<RelatesToType> getRelatesTo()
  {
    return relatesTo;
  }

  /**
   * Sets the addressing 'RelatesTo' attribute on the OSCI message. Including a previous messageId.
   * 
   * @param _relatesTo The relatesTo attribute.
   */
  public void addRelatesTo(RelatesToType _relatesTo)
  {
    if (relatesTo == null)
    {
      relatesTo = new ArrayList<RelatesToType>();
    }
    relatesTo.add(_relatesTo);
  }

  /**
   * Returns the OSCI 'X509TokenContainer' including the certificates to check on the transfer way.
   * 
   * @return The list of certificates that will be checked.
   */
  public X509TokenContainerType getX509TokenContainer()
  {
    return x509TokenContainer;
  }

  /**
   * Sets the certificates that will be checked on the transfer way.
   * 
   * @param _tokenContainer The list of certificates that will be checked.
   */
  public void setX509TokenContainer(X509TokenContainerType _tokenContainer)
  {
    x509TokenContainer = _tokenContainer;
  }

  /**
   * Returns the set ReceptionReceiptDemand header element.
   */
  public ReceptionReceiptDemandType getReceptionReceiptDemand()
  {
    return receptionReceiptDemand;
  }

  /**
   * Better use the method {@link #setReceptionReceiptDemand(boolean, boolean, String)} to set a
   * ReceptionReceiptDemand header.
   * 
   * @param _receptionReceiptDemand The preconfigured ReceptionReceiptDemand header.
   */
  public void setReceptionReceiptDemand(ReceptionReceiptDemandType _receptionReceiptDemand)
  {
    receptionReceiptDemand = _receptionReceiptDemand;
  }

  /**
   * Returns the FetchedNotificationDemand if existing.
   * 
   * @return The OSCI FetchedNotificationDemand header.
   */
  public FetchedNotificationDemandType getFetchedNotificationDemand()
  {
    return fetchedNotiDemand;
  }

  /**
   * Adds a certificate check result.
   * 
   * @param compoundResultType The XKMS result of a certificate check.
   */
  public void addCompoundResult(CompoundResultType compoundResultType)
  {
    compoundResultList.add(compoundResultType);
  }

  /**
   * Returns a List of the check results of the certificate checks.
   */
  public List<CompoundResultType> getCompoundResult()
  {
    return compoundResultList;
  }

  /**
   * Sets a List of XKMS check results on the OSCI message.
   * 
   * @param _compoundResult A List of XKMS results.
   */
  public void setCompoundResult(List<CompoundResultType> _compoundResult)
  {
    this.compoundResultList = _compoundResult;
  }

  /**
   * Sets the given OSCI FetchedNotificationDemand element as SOAP header. Better use the method
   * {@link #setFetchedNotificationDemand(String)} or
   * {@link #setFetchedNotificationDemand(String eprURI, OSCIRequestAction businessScenario)}to set a
   * FetchedNotificationDemand header.
   * 
   * @param _fetchedNotiDemand The FetchedNotificationDemand object.
   */
  public void setFetchedNotificationDemand(FetchedNotificationDemandType _fetchedNotiDemand)
  {
    fetchedNotiDemand = _fetchedNotiDemand;
  }

  /**
   * Returns the OSCI DeliveryReceiptDemand header.
   */
  public DeliveryReceiptDemandType getDeliveryReceiptDemand()
  {
    return deliveryReceiptDemand;
  }

  /**
   * Sets the given OSCI DeliveryReceiptDemand to make a request for a DeliveryReceipt. The
   * DeliveryReceiptDemandType object must be created from the user on its own. Better use the method
   * {@link #setDeliveryReceiptDemand(boolean, boolean, String)} or {@link #setDeliveryReceiptDemand()} to set
   * a DeliveryReceiptDemand header.
   * 
   * @param _deliveryReceiptDemand The DeliveryReceiptDemand.
   */
  public void setDeliveryReceiptDemand(DeliveryReceiptDemandType _deliveryReceiptDemand)
  {
    deliveryReceiptDemand = _deliveryReceiptDemand;
  }

  /**
   * Adds a ReceptionReceiptDemand OSCI header with the given parameter to the OSCI message.
   * 
   * @param echoRequest If this value is true the whole OSCI message will be returned in the receipt.
   * @param qualifiedTSP If this value is true the time stamp or the receipt will be qualified.
   * @param eprURI The URI where the receipt will be received.
   */
  public void setReceptionReceiptDemand(boolean echoRequest, boolean qualifiedTSP, String eprURI)
  {
    receptionReceiptDemand = new ReceptionReceiptDemandType();
    receptionReceiptDemand.setEchoRequest(echoRequest);
    receptionReceiptDemand.setRole("http://www.w3.org/2003/05/soap-envelope/role/next");
    receptionReceiptDemand.setEchoRequest(qualifiedTSP);
    receptionReceiptDemand.setQualTSPForReceipt(qualifiedTSP);
    receptionReceiptDemand.setReplyTo(OSCIFragmentCreator.createEndpointReferenceParameter(eprURI,
                                                                                           OSCIConstants.TYPE_OF_BUS_CON_NOTIFICATION));
  }

  /**
   * Adds a DeliveryReceiptDemand OSCI header with the given parameter to the OSCI message.
   * 
   * @param echoRequest If this value is true the whole OSCI message will be returned in the receipt.
   * @param qualifiedTSP If this value is true the time stamp or the receipt will be qualified.
   * @param eprURI The URI where the receipt will be received.
   */
  public void setDeliveryReceiptDemand(boolean echoRequest, boolean qualifiedTSP, String eprURI)
  {
    deliveryReceiptDemand = new DeliveryReceiptDemandType();
    deliveryReceiptDemand.setEchoRequest(echoRequest);
    deliveryReceiptDemand.setRole("http://www.w3.org/2003/05/soap-envelope/role/next");
    deliveryReceiptDemand.setQualTSPForReceipt(qualifiedTSP);
    deliveryReceiptDemand.setReplyTo(OSCIFragmentCreator.createEndpointReferenceParameter(eprURI,
                                                                                          OSCIConstants.TYPE_OF_BUS_CON_NOTIFICATION));
  }

  /**
   * Adds a DeliveryReceiptDemand OSCI header with default parameter to the OSCI message. As URI 'anonymous'
   * will be used and the receipt will be sent in the back channel
   */
  public void setDeliveryReceiptDemand()
  {
    setDeliveryReceiptDemand(false, false, "http://www.w3.org/2005/08/addressing/anonymous");
  }

  /**
   * Adds a FetchedNotificationDemand OSCI header with the given parameter to the OSCI message.
   * 
   * @param eprURI URI for the FetchNotification to send to.
   */
  public void setFetchedNotificationDemand(String eprURI)
  {
    setFetchedNotificationDemand(eprURI, OSCIRequestAction.NOTIFICATION);
  }

  /**
   * Adds a FetchedNotificationDemand OSCI header with the given parameter to the OSCI message.
   * 
   * @param eprURI URI for the FetchNotification to send to.
   * @param businessScenario Type of businessScenario to use for the receipt.
   */
  public void setFetchedNotificationDemand(String eprURI, OSCIRequestAction businessScenario)
  {
    fetchedNotiDemand = new FetchedNotificationDemandType();
    fetchedNotiDemand.setRole("http://www.w3.org/2003/05/soap-envelope/role/next");
    fetchedNotiDemand.setReplyTo(OSCIFragmentCreator.createEndpointReferenceParameter(eprURI,
                                                                                      businessScenario));
  }

  /**
   * Returns the date of the configured expire time of the OSCI message.
   */
  public Date getObsoleteAfter()
  {
    if (obsoleteAfter == null)
    {
      return null;
    }
    return obsoleteAfter.toGregorianCalendar().getTime();
  }

  /**
   * Sets the date of the expire time of the OSCI message that will be send.
   * 
   * @param expireDate The date to set the time when the message will expire.
   * @throws DatatypeConfigurationException In the case of format exceptions.
   */
  public void setObsoleteAfter(Date expireDate) throws DatatypeConfigurationException
  {
    obsoleteAfter = OSCIFragmentCreator.createXMLGregorianCalendar(expireDate);
  }

  /**
   * Returns the set MsgTimeStampsType or the MsgTimeStampsType of the incoming message in case of synchronous
   * messages.
   */
  public MsgTimeStampsType getMsgTimeStamps()
  {
    return msgtime;
  }

  /**
   * Sets the given MsgTimeStampsType object as part of the outgoing OSCI message. If an obsolateAfter value
   * will be set this value has higher priority.
   * 
   * @param _msgTime The configured object that will be set.
   */
  public void setMsgTimeStamps(MsgTimeStampsType _msgTime)
  {
    msgtime = _msgTime;
  }

  /**
   * Returns the set MetaDataType of the incoming message.
   */
  public MetadataType getMetaData()
  {
    return metaData;
  }

}
