package eu.osci.messagetypes;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.util.Date;
import java.util.Iterator;
import java.util.logging.Logger;

import javax.xml.bind.JAXBException;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.ws.WebServiceFeature;

import org.w3._2005._08.addressing.RelatesToType;
import org.w3c.dom.Document;

import com.sun.xml.ws.api.message.HeaderList;

import eu.osci.OSCIConstants;
import eu.osci.OSCIException;
import eu.osci.helper.EndpointLocator;
import eu.osci.helper.XMLGeneralHelper;
import eu.osci.messageparts.JaxbContextHolder;
import eu.osci.messageparts.OSCIFragmentCreator;
import eu.osci.ws._2008._05.transport.MsgTimeStampsType;
import eu.osci.ws._2008._05.transport.ObjectFactory;
import eu.osci.ws._2008._05.transport.ReceiptInfoType;
import eu.osci.ws._2008._05.transport.ReceptionReceiptType;


/**
 * Class to create and send ReceptionReceipt messages.<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 ReceptionReceipt extends OSCIRequest
{

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

  private Document receptionReceiptDocument;

  /**
   * Constructor for a ReceptionReceipt with an endpointLocator parameter and all necessary information.
   * 
   * @param endpointLocator The configured EndpointLocator object.
   * @param _features To add other features for the endpoint (see Metro Api-Doc).
   */
  public ReceptionReceipt(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 ReceptionReceipt(URL _wsdlURL, QName _serviceQName, QName _portQName, WebServiceFeature... _features)
  {
    super(_wsdlURL, _serviceQName, _portQName, _features);
  }

  /**
   * This method creates a ReceptionReceipt object with the given parameter out of the SOAP message
   * (HeaderList). This method returns a DOM document representing the ReceptionReceipt object for further
   * processing. The created ReceptionReceipt is not complete, for example the signature, encryption and the
   * timestamp is missing. The DOM document could be used to fill the missing information. After adding the
   * information the method sendMessage() can be used to send the OSCI message because the DOM element has
   * been already set. In cases of other transformation instead of DOM the message sendMessage(InputStream)
   * with the complete ReceptionReceipt object must be set.
   * 
   * @param headerList Header list of the OSCI message that will be received.
   * @return The DOM document representing the ReceptionReceipt.
   * @throws DatatypeConfigurationException
   * @throws JAXBException
   * @throws ParserConfigurationException
   */
  public Document createReceptionReceiptDocument(HeaderList headerList)
    throws DatatypeConfigurationException, JAXBException, ParserConfigurationException
  {
    OSCIRequest req = new OSCIRequest();
    req.interpretHeaders(headerList);
    return createReceptionReceiptDocument(req);
  }

  /**
   * This method creates a ReceptionReceipt object with the given parameter out of the SOAP message
   * (HeaderList). The created object will be returned for further processing. The created ReceptionReceipt is
   * not complete, for example the signature, the encryption and the timestamp is missing. The JAXB object
   * could be used to fill in the missing information. After adding the information the method
   * sendMessage(InputStream) with the complete ReceptionReceipt object must be used. The created
   * ReceptionReceipt object does not have any dependencies to this class.
   * 
   * @param headerList Header list of the OSCI message that will be received.
   * @return The created ReceptionReceipt object.
   * @throws DatatypeConfigurationException
   * @throws JAXBException
   * @throws ParserConfigurationException
   */
  public static ReceptionReceiptType createReceptionReceiptObject(HeaderList headerList)
    throws DatatypeConfigurationException, JAXBException, ParserConfigurationException
  {
    OSCIRequest req = new OSCIRequest();
    req.interpretHeaders(headerList);
    return createReceptionReceiptObject(req);
  }

  /**
   * This method creates a ReceptionReceipt object with the given OSCI message. The created object will be
   * returned for further processing. The created ReceptionReceipt is not complete, for example the signature,
   * the encryption and the timestamp is missing. The JAXB object could be used to fill in the missing
   * information. After adding the information the method sendMessage(InputStream) with the complete
   * ReceptionReceipt object must be used. The created ReceptionReceipt object does not have any dependencies
   * to this class.
   * 
   * @param msg The OSCI message that will be received.
   * @return The created ReceptionReceipt object.
   * @throws DatatypeConfigurationException
   * @throws JAXBException
   * @throws ParserConfigurationException
   */
  public static ReceptionReceiptType createReceptionReceiptObject(OSCIRequest msg)
    throws DatatypeConfigurationException, ParserConfigurationException, JAXBException
  {
    ReceptionReceiptType receReceipt = new ReceptionReceiptType();
    ReceiptInfoType info = new ReceiptInfoType();
    receReceipt.setReceiptInfo(info);
    MsgTimeStampsType msgTimeStamp = null;
    if (msg.getMsgTimeStamps() == null)
      msgTimeStamp = new MsgTimeStampsType();
    else
      msgTimeStamp = msg.getMsgTimeStamps();
    msgTimeStamp.setReception(OSCIFragmentCreator.createXMLGregorianCalendar(new Date()));
    info.setId("ReceiptInfo");
    info.setReceiptIssuerRole(OSCIConstants.RECIPIENT_ISSUER_ROLE_RECIPIENT);
    info.setFrom(msg.getFrom());
    if (msg.getRelatesTo() != null && msg.getRelatesTo().size() > 0)
    {
      Iterator<RelatesToType> list = msg.getRelatesTo().iterator();
      while (list.hasNext())
      {
        info.getRelatesTo().add(list.next());
      }
    }
    LOG.fine("RelatesTo: " + info.getRelatesTo().size());
    if (msg.getRelatesTo() != null && msg.getRelatesTo().size() > 0)
    {
      Iterator<RelatesToType> relatesList = msg.getRelatesTo().iterator();
      while (relatesList.hasNext())
      {
        info.getRelatesTo().add(relatesList.next());
      }
    }
    info.setReplyTo(msg.getReplyTo());
    info.setMessageID(msg.getMessageId());
    info.setMsgTimeStamps(msgTimeStamp);
    if (msg.getTo() != null)
      info.setTo(OSCIFragmentCreator.createEndpointReferenceParameter(msg.getTo().getValue(),
                                                                      msg.getTypeOfBusinessScenario()));
    return receReceipt;
  }

  /**
   * This method creates a ReceptionReceipt object with the given parameter out of the OSCI message. This
   * method returns a DOM document representing the ReceptionReceipt object for further processing. The
   * created ReceptionReceipt is not complete. For example the signature, encryption and the timestamp is
   * missing. The DOM document could be used to fill in the missing information. After adding the information
   * the method sendMessage without any parameter can be used to send the OSCI message because the DOM element
   * has been already set. In cases of other transformation instead of DOM the message
   * sendMessage(InputStream) with the complete ReceptionReceipt object must be set.
   * 
   * @param msg The OSCI message that will be received.
   * @return The DOM document representing the ReceptionReceipt.
   * @throws DatatypeConfigurationException
   * @throws JAXBException
   * @throws ParserConfigurationException
   */
  public Document createReceptionReceiptDocument(OSCIRequest msg) throws DatatypeConfigurationException,
    ParserConfigurationException, JAXBException
  {
    convertFromJAXB(createReceptionReceiptObject(msg));
    return receptionReceiptDocument;
  }

  private void convertFromJAXB(ReceptionReceiptType rece) throws ParserConfigurationException, JAXBException
  {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder builder = dbf.newDocumentBuilder();

    receptionReceiptDocument = builder.newDocument();
    JaxbContextHolder.getOSCIJaxBContext()
                     .createMarshaller()
                     .marshal(new ObjectFactory().createReceptionReceipt(rece), receptionReceiptDocument);
  }

  /**
   * Sends the configured OSCI message with the underlying DOM document. The entire header will be added to
   * the SOAP message and the ReceptionReceipt will be put into the body element of the SOAP message.
   * 
   * @return The OSCI response of the related request.
   * @throws JAXBException In case of parsing or serialisation errors.
   * @throws OSCIException In case of OSCI SOAP faults.
   * @throws TransformerException
   **/
  public OSCIResponse sendMessage() throws JAXBException, OSCIException, TransformerException
  {
    prepareSendMessage();
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    XMLGeneralHelper.dumpDomNode(receptionReceiptDocument, out);
    return onlySendMessage(new ByteArrayInputStream(out.toByteArray()));
  }

}
