package eu.osci.messagetypes;


import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.bind.JAXBException;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.w3._2002._03.xkms.CompoundResultType;
import org.w3._2005._08.addressing.AttributedURIType;
import org.w3._2005._08.addressing.EndpointReferenceType;
import org.w3._2005._08.addressing.RelatesToType;

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 eu.osci.AddressingHeaderTag;
import eu.osci.OSCIConstants;
import eu.osci.OSCIHeaderTag;
import eu.osci.helper.CryptoTools;
import eu.osci.messageparts.OSCIFragmentCreator;
import eu.osci.messageparts.OSCIFragmentParser;
import eu.osci.ws._2008._05.transport.DeliveryReceiptType;
import eu.osci.ws._2008._05.transport.MsgTimeStampsType;
import eu.osci.ws._2008._05.transport.ReceiptInfoType;
import eu.osci.ws._2008._05.transport.ReceptionReceiptDemandType;
import eu.osci.ws._2008._05.transport.X509TokenContainerType;


/**
 * This class represents the response of an OSCIRequest.<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 OSCIResponse extends OSCIMessage
{

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

  private Message incomingMessage;

  private ReceptionReceiptDemandType receptionReceiptDemand;

  private DeliveryReceiptType deliveryReceipt = null;

  private X509TokenContainerType x509TokenContainer = null;

  /** Addressing 'ReplyTo' attribute */
  private EndpointReferenceType replyTo;

  private RelatesToType relatesTo = null;

  private List<CompoundResultType> compoundResult;

  /**
   * Internal message.
   */
  public OSCIResponse()
  {}

  /**
   * This constructor will be used to parse the incoming SOAP header and the body element. It will be called
   * by the send method of the associate request message.
   * 
   * @param _soapMessage The incoming SOAP message with the given header and body elements.
   * @throws JAXBException In case that the header elements or the body could not be parsed.
   */
  public OSCIResponse(Message _soapMessage) throws JAXBException
  {
    interpretHeaders(_soapMessage.getHeaders());
    incomingMessage = _soapMessage;
  }

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

  /**
   * 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);
    EndpointReferenceType eft = new EndpointReferenceType();
    AttributedURIType add = new AttributedURIType();
    add.setValue(eprURI);
    eft.setAddress(add);
    receptionReceiptDemand.setReplyTo(eft);
  }

  /**
   * Returns the OSCI DeliveryReceiptDemand header.
   */
  public DeliveryReceiptType getDeliveryReceipt()
  {
    return deliveryReceipt;
  }

  /**
   * This method can be used to create a DeliveryReceiptType. As creation time the system time will be used.
   * The time stamp and the signature must be included by the user of this method. The created DeliveryReceipt
   * will be added to the OSCIResponse object.
   * 
   * @param messageId The messageId for the receipt
   * @param toEndpoint The wsa:To address out of the OSCI message.
   * @return The created DeliveryReceiptType object for further processing.
   * @throws DatatypeConfigurationException
   */
  public DeliveryReceiptType setDeliveryReceipt(String messageId, EndpointReferenceType toEndpoint)
    throws DatatypeConfigurationException
  {
    deliveryReceipt = new DeliveryReceiptType();
    ReceiptInfoType info = new ReceiptInfoType();
    MsgTimeStampsType timeStamps = new MsgTimeStampsType();
    timeStamps.setDelivery(OSCIFragmentCreator.createXMLGregorianCalendar(new Date()));
    info.setId("ReceiptInfo" + CryptoTools.createRandom());
    info.setMessageID(OSCIFragmentCreator.createAttributedURI(messageId));
    deliveryReceipt.setReceiptInfo(info);
    info.setMsgTimeStamps(timeStamps);
    info.setTo(toEndpoint);
    info.setReceiptIssuerRole(OSCIConstants.RECIPIENT_ISSUER_ROLE_RECIPIENT);
    return deliveryReceipt;
  }

  /**
   * Sets the given OSCI ReceptionReceipt element as SOAP header.
   * 
   * @param deliveryReceipt The configured or parsed ReceptionReceipt header.
   */
  public void setDeliveryReceipt(DeliveryReceiptType deliveryReceipt)
  {
    this.deliveryReceipt = deliveryReceipt;
  }

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

  /**
   * The payload of the received OSCI SOAP message.
   * 
   * @param out The payload out of the body.
   * @throws TransformerException
   */
  public void getContent(OutputStream out) throws TransformerException
  {
    LOG.info("Entry getContent()");
    TransformerFactory factory = TransformerFactory.newInstance();
    Transformer transformer = factory.newTransformer();
    StreamResult streamResult = new StreamResult();
    streamResult.setOutputStream(out);

    transformer.transform(incomingMessage.readPayloadAsSource(), streamResult);
  }

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

  /**
   * Sets the addressing 'RelatesTo' attribute on the OSCI message including a previous messageId.
   * 
   * @param _relatesTo The 'RelatesTo' attribute.
   */
  public void setRelatesTo(RelatesToType _relatesTo)
  {
    relatesTo = _relatesTo;
  }

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

  /**
   * 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.compoundResult = compoundResult;
  }

  /**
   * Returns the addressing 'ReplyTo' field. In the 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;
  }

  /**
   * 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)
  {
    replyTo = _replyTo;
  }

  /**
   * Parses the addressing header from the incoming/sent OSCI message.
   * 
   * @param headerList Header list of the incoming/sent OSCI message.
   * @throws JAXBException In cases of parsing errors.
   */
  protected void interpretHeaders(HeaderList headerList) throws JAXBException
  {
    if (LOG.isLoggable(Level.FINE))
    {
      LOG.fine("Entry interpretHeaders()");
      for ( Header header : headerList )
      {
        LOG.fine("Found header: " + header.getLocalPart());
      }
    }
    // ### Addressing header ############
    super.interpretWSAHeaders(headerList);
    List<RelatesToType> relatesToList = OSCIFragmentParser.getAddressingRelatesTo(headerList);
    if ((relatesToList != null) && (relatesToList.size() > 0))
    {
      relatesTo = relatesToList.get(0);
    }
    // ##### OSCI header ##########
    deliveryReceipt = (DeliveryReceiptType)OSCIFragmentParser.createOSCIHeader(headerList,
                                                                               OSCIHeaderTag.DELIVERY_RECEIPT);
    receptionReceiptDemand = OSCIFragmentParser.getReceiptDemand(headerList);
    x509TokenContainer = OSCIFragmentParser.getX509Container(headerList);
    // Other header
    compoundResult = OSCIFragmentParser.getCompoundResultType(headerList);
    setAdditionalHeaderList(headerList);
  }

  /**
   * Internal method for preparing the sent SOAP message. The header elements of the request will be set here.
   * 
   * @return The created header elements for further processing.
   * @throws JAXBException In the case of parsing or JAXB errors
   */
  public ArrayList<Header> prepareSendMessage() throws JAXBException
  {
    LOG.fine("Entry prepareSendMessage()");
    ArrayList<Header> headerList = new ArrayList<Header>();
    super.prepareSendMessage(headerList);
    // ################################ OSCI header objects ##########################
    // ################### Reception receipt demand #################
    if (receptionReceiptDemand != null)
    {
      LOG.fine("'receptionReceiptDemand' added to header list");
      headerList.add(OSCIFragmentCreator.createOSCIHeader(OSCIHeaderTag.RECEIPT_DEMAND,
                                                          receptionReceiptDemand));
    }
    // ################### Delivery receipt demand #################
    if (deliveryReceipt != null)
    {
      LOG.fine("'deliveryReceipt' added to header list");
      headerList.add(OSCIFragmentCreator.createOSCIHeader(OSCIHeaderTag.DELIVERY_RECEIPT, deliveryReceipt));
    }
    // ################### 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);
    }
    if (compoundResult != null && compoundResult.size() > 0)
    {
      Iterator<CompoundResultType> compList = compoundResult.iterator();
      while (compList.hasNext())
      {
        LOG.fine("'CompoundResultType' added to header list");
        Header header = OSCIFragmentCreator.createXKMSHeader(OSCIConstants.QNAME_COMPOUND_RESULT,
                                                             compList.next());
        headerList.add(header);
      }
    }
    // #################################### Addressing fields ##########################
    if (replyTo != null)
    {
      LOG.fine("'ReplyTo' added to header list");
      headerList.add(OSCIFragmentCreator.createAddressingHeader(AddressingHeaderTag.REPLY_TO, replyTo));
    }
    if (relatesTo != null)
    {
      LOG.fine("'RelatesTo' added to header list");
      headerList.add(OSCIFragmentCreator.createAddressingHeader(AddressingHeaderTag.RELATES_TO, relatesTo));
    }
    return headerList;
  }

}
