package eu.osci.messagetypes;

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

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

import org.w3._2005._08.addressing.AttributedURIType;
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.Message;
import com.sun.xml.ws.api.message.Messages;
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.JaxbContextHolder;
import eu.osci.messageparts.OSCIFragmentCreator;
import eu.osci.messageparts.OSCIFragmentParser;
import eu.osci.ws._2008._05.transport.MsgBoxRequestType;
import eu.osci.ws._2008._05.transport.MsgSelector;
import eu.osci.ws._2008._05.transport.ObjectFactory;
import eu.osci.ws._2008._05.transport.TypeOfBusinessScenarioType;


/**
 * This class represents the OSCIMsgBoxFetchRequest message out of the specification. It will send the correct
 * OSCI SOAP message with the given selection rules and offers the first matching OSCI message as response.<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 OSCIMsgBoxFetchRequest extends OSCIRequestParent
{

  /** Logger to log the messages. */
  protected static final Logger LOG = Logger.getLogger(OSCIMsgBoxFetchRequest.class.getName());

  protected MsgBoxRequestType msgBoxRequest = null;

  private MetadataType metaData;

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

  /**
   * Constructor with the necessary parameters. The URL to the 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 OSCIMsgBoxFetchRequest(URL _wsdlURL,
                                QName _serviceQName,
                                QName _portQName,
                                WebServiceFeature... _features)
  {
    super(_wsdlURL, _serviceQName, _portQName, _features);
  }

  /**
   * This constructor is normally used by the client. The previous request will be given as parameter.
   * 
   * @param parent The previous request.
   */
  public OSCIMsgBoxFetchRequest(OSCIRequestParent parent)
  {
    super(parent);
    LOG.fine("Entry constructor with OSCIRequestParent");
  }

  /**
   * Constructor for endpointLocator parameter with all necessary information.
   * 
   * @param _endpointLocator The configured endpoint locator object.
   * @param _features To add other features for the endpoint (see Metro Api-Doc).
   */
  public OSCIMsgBoxFetchRequest(EndpointLocator _endpointLocator, WebServiceFeature... _features)
  {
    this(_endpointLocator.getWsdlFile(),
         _endpointLocator.getServiceForTypeOfBusinessScenario(_endpointLocator.getTypeOfBusinessScenario()),
         _endpointLocator.getPortForTypeOfBusinessScenario(_endpointLocator.getTypeOfBusinessScenario()),
         _features);
  }

  /**
   * <b>For internal usage only!</b>
   */
  public MsgBoxRequestType getMsgBoxRequest()
  {
    return msgBoxRequest;
  }

  /**
   * This method could be used if there is no setSelection* method that can be used.
   * 
   * @param _msgBoxRequest The preconfigured request selections.
   */
  public void setMsgBoxRequest(MsgBoxRequestType _msgBoxRequest)
  {
    msgBoxRequest = _msgBoxRequest;
  }

  /**
   * 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 the case of parsing or serialisation errors.
   */
  protected void prepareSendMessage(WSBindingProvider bindingProvider) throws JAXBException
  {
    ArrayList<Header> headerList = new ArrayList<Header>();
    super.prepareSendMessage(headerList);
    bindingProvider.setOutboundHeaders(headerList);

  }

  protected void interpretHeaders(HeaderList headerList) throws JAXBException
  {
    interpretWSAHeaders(headerList);
    typeOfBusinessScenario = ((TypeOfBusinessScenarioType)OSCIFragmentParser.createOSCIHeader(headerList,
                                                                                              OSCIHeaderTag.TYPEOFBUSINESS)).getValue();
    metaData = (MetadataType)OSCIFragmentParser.createAddressingHeader(headerList,
                                                                       AddressingHeaderTag.METADATA);
    setAdditionalHeaderList(headerList);
  }

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

  /**
   * Sends the request to present server. A SOAP message will be sent and the response message with the
   * fetched message will be created. Before this method can be used a 'setSelection*' method must be called.
   * 
   * @return The created response message.
   * @throws JAXBException In the case of parsing or serialisation errors.
   * @throws OSCIException In the case of OSCI SOAP Faults.
   */
  public OSCIMsgBoxFetchResponse sendRequest() throws JAXBException, OSCIException
  {
    if (msgBoxRequest == null)
    {
      throw new IllegalStateException(
                                      OSCIMessage.text.getString(ErrorTextTranslation.NO_SELECTION_PARAMETER.getConstant()));
    }
    prepareSendMessage(bindingProvider);
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    JaxbContextHolder.getOSCIJaxBContext()
                     .createMarshaller()
                     .marshal(new ObjectFactory().createMsgBoxFetchRequest(msgBoxRequest), out);
    Message sendMsg = Messages.createUsingPayload(new StreamSource(
                                                                   new ByteArrayInputStream(out.toByteArray())),
                                                  SOAPVersion.SOAP_12);
    LOG.fine("Now invoke sending.");
    OSCIMsgBoxFetchResponse rsp = new OSCIMsgBoxFetchResponse(sendMessage(sendMsg));
    interpretHeaders(sendMsg.getHeaders());
    fetchIteratorID = rsp.getFetchIteratorID();
    return rsp;
  }

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

  /**
   * Selects OSCI messages by using the addressing 'RelatesTo' attribute.
   * 
   * @param relatesToList A list of 'RelatesTo' Ids.
   * @param endpoint The endpoint that will be selected with this request.
   */
  public void setSelectionByRelatesTo(String[] relatesToList, EndpointReferenceType endpoint)
  {
    LOG.info("Entry setSelectionByRelatesTo");
    msgBoxRequest = new MsgBoxRequestType();
    msgBoxRequest.setEndpointReference(endpoint);
    MsgSelector selector = new MsgSelector();
    String msgTmp = "";
    if (relatesToList != null)
    {
      for ( String id : relatesToList )
      {
        RelatesToType relates = new RelatesToType();
        relates.setValue(id);
        msgTmp = msgTmp.concat(id);
        selector.getRelatesTo().add(relates);
      }
    }
    LOG.fine("Selected relatesTo Ids:" + msgTmp);
    msgBoxRequest.setMsgSelector(selector);
  }

  /**
   * Selects OSCI messages from the configured endpoint.
   * 
   * @param onlyNewElements Selects that only new messages will be selected.
   * @param endpoint The endpoint (Address and TypeOfBusinessScenario) to select.
   */
  public void setSelectionAll(boolean onlyNewElements, EndpointReferenceType endpoint)
  {
    LOG.info("Entry setSelectionAll: " + onlyNewElements);
    msgBoxRequest = new MsgBoxRequestType();
    msgBoxRequest.setEndpointReference(endpoint);

    MsgSelector selector = new MsgSelector();
    selector.setNewEntry(Boolean.valueOf(onlyNewElements));
    msgBoxRequest.setMsgSelector(selector);

  }

  /**
   * Selects the OSCI messages from the given endpoint by using the creation time of the stored messages.
   * 
   * @param from Starting date/time for the selection.
   * @param to End date/time for the selection.
   * @param onlyNewElements Selects that only new messages will be returned.
   * @param endpoint The endpoint (Address and TypeOfBusinessScenario) to select.
   * @throws DatatypeConfigurationException If there are converting errors with the date objects.
   */
  public void setSelectionByDate(Date from, Date to, boolean onlyNewElements, EndpointReferenceType endpoint)
    throws DatatypeConfigurationException
  {
    LOG.info("Entry setSelectionByDate: " + from + ", " + to + ", " + onlyNewElements);
    msgBoxRequest = new MsgBoxRequestType();
    msgBoxRequest.setEndpointReference(endpoint);

    MsgSelector selector = new MsgSelector();
    selector.setNewEntry(Boolean.valueOf(onlyNewElements));
    selector.setMsgBoxEntryTimeFrom(OSCIFragmentCreator.createXMLGregorianCalendar(from));
    selector.setMsgBoxEntryTimeTo(OSCIFragmentCreator.createXMLGregorianCalendar(to));
    msgBoxRequest.setMsgSelector(selector);
  }

  /**
   * Selects the message by giving the messageId directly.
   * 
   * @param messageIDList A List of messageIds.
   * @param endpoint The endpoint (Address and TypeOfBusinessScenario) to select.
   */
  public void setSelectionByMessageId(String[] messageIDList, EndpointReferenceType endpoint)
  {
    LOG.info("Entry setSelectionByMessageId");
    msgBoxRequest = new MsgBoxRequestType();
    msgBoxRequest.setEndpointReference(endpoint);
    MsgSelector selector = new MsgSelector();
    String msgTmp = "";
    if (messageIDList != null)
    {
      for ( String id : messageIDList )
      {
        AttributedURIType msgIDAtt = new AttributedURIType();
        msgIDAtt.setValue(id);
        msgTmp = msgTmp.concat(id);
        selector.getMessageID().add(msgIDAtt);
      }
    }
    LOG.fine("Selected msgIds: " + msgTmp);
    msgBoxRequest.setMsgSelector(selector);
  }

}
