package eu.osci.messageparts;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;

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.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.ws._2008._05.transport.MsgBoxResponseType;
import eu.osci.ws._2008._05.transport.MsgStatusListType;
import eu.osci.ws._2008._05.transport.ReceptionReceiptDemandType;
import eu.osci.ws._2008._05.transport.TypeOfBusinessScenarioType;
import eu.osci.ws._2008._05.transport.X509TokenContainerType;


/**
 * Internal class for parsing the incoming OSCI message.<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 OSCIFragmentParser
{

  private static Logger LOG = Logger.getLogger(OSCIFragmentParser.class.getName());

  private static Unmarshaller addressingUnmarshaller = null;

  private static Unmarshaller osciUnmarshaller = null;

  private static Unmarshaller xkmsUnmarshaller = null;
  static
  {
    try
    {
      addressingUnmarshaller = JaxbContextHolder.getAddressingJaxBContext().createUnmarshaller();
      osciUnmarshaller = JaxbContextHolder.getOSCIJaxBContext().createUnmarshaller();
      xkmsUnmarshaller = JaxbContextHolder.getXKMSJaxBContext().createUnmarshaller();
    }
    catch (Exception ex)
    {
      LOG.log(Level.SEVERE, "Error: ", ex);
      throw new IllegalStateException(ex);
    }
  }

  // ############################### Addressing header ###########################
  // ############################### Addressing header ###########################
  // ############################### Addressing header ###########################

  /**
   * Returns the addressing 'RelatesTo' element out of the given header list.
   * 
   * @param hl Header list including the 'RelatesTo' header.
   * @return The requested addressing element or <code>null</code> if not present.
   * @throws JAXBException In the case of parsing errors.
   */
  @SuppressWarnings("unchecked")
  public static List<RelatesToType> getAddressingRelatesTo(HeaderList hl) throws JAXBException
  {
    Object obj = createAddressingHeader(hl, AddressingHeaderTag.RELATES_TO);
    if (obj == null)
    {
      return null;
    }

    if (obj instanceof List)
    {
      return (List<RelatesToType>)obj;
    }

    ArrayList<RelatesToType> ret = new ArrayList<RelatesToType>();
    ret.add((RelatesToType)obj);
    return ret;

  }

  // ########################## OSCI Header ############################
  // ########################## OSCI Header ############################
  // ########################## OSCI Header ############################

  /**
   * Returns the OSCI 'ReceptionReceiptDemand' element out of the given header list.
   * 
   * @param hl Header list including the 'ReceptionReceiptDemand' header.
   * @return The requested OSCI element or <code>null</code> if not present.
   * @throws JAXBException In the case of parsing errors.
   */
  public static ReceptionReceiptDemandType getReceiptDemand(HeaderList hl) throws JAXBException
  {
    return (ReceptionReceiptDemandType)createOSCIHeader(hl, OSCIHeaderTag.RECEIPT_DEMAND);
  }

  /**
   * Returns the OSCI 'X509TokenContainer' element out of the given header list.
   * 
   * @param hl Header list including the 'X509TokenContainer' header.
   * @return The requested OSCI element or <code>null</code> if not present.
   * @throws JAXBException In the case of parsing errors.
   */
  public static X509TokenContainerType getX509TokenContainer(HeaderList hl) throws JAXBException
  {
    return (X509TokenContainerType)createOSCIHeader(hl, OSCIHeaderTag.X509CONTAINER);
  }

  /**
   * Returns the OSCI 'X509TokenContainer' element out of the given header list.
   * 
   * @param hl Header list including the 'X509TokenContainer' header.
   * @return The requested OSCI element or <code>null</code> if not present.
   * @throws JAXBException In the case of parsing errors.
   */
  public static X509TokenContainerType getX509Container(HeaderList hl) throws JAXBException
  {
    return (X509TokenContainerType)createOSCIHeader(hl, OSCIHeaderTag.X509CONTAINER);
  }

  /**
   * Returns the OSCI 'RequestSecurityTokenResponseCollection' element out of the given header list.
   * 
   * @param hl Header list including the 'RequestSecurityTokenResponseCollection' header.
   * @return The requested OSCI element or <code>null</code> if not present.
   * @throws JAXBException In the case of parsing errors.
   */
  public static RequestSecurityTokenResponseCollectionType getRequestSecurityTokenResponseCollection(HeaderList hl)
    throws JAXBException
  {
    Header h = hl.get(OSCIConstants.QNAME_RSTR_COLLECTION, true);

    if (h != null)
    {
      JAXBElement<?> obj = h.readAsJAXB(osciUnmarshaller);
      hl.remove(h);
      return (RequestSecurityTokenResponseCollectionType)obj.getValue();
    }
    LOG.info("No OSCI header found for QName: " + OSCIConstants.QNAME_RSTR_COLLECTION);
    return null;
  }

  /**
   * Returns the OSCI 'MsgBoxResponse' element out of the given header list.
   * 
   * @param hl Header list including the 'MsgBoxResponse' header.
   * @return The requested OSCI element or <code>null</code> if not present.
   * @throws JAXBException In the case of parsing errors.
   */
  public static MsgBoxResponseType getMsgBoxResponseType(HeaderList hl) throws JAXBException
  {
    return (MsgBoxResponseType)createOSCIHeader(hl, OSCIHeaderTag.MSGBOX_RESPONSE);
  }

  /**
   * Returns the OSCI 'MsgStatusList' element out of the given SOAP message.
   * 
   * @param message The SOAP message to search for the MsgStatusList.
   * @return The requested OSCI element or <code>null</code> if not present.
   * @throws JAXBException In the case of parsing errors.
   */
  @SuppressWarnings("unchecked")
  public static MsgStatusListType getMsgStatusListType(Message message) throws JAXBException
  {
    return ((JAXBElement<MsgStatusListType>)message.readPayloadAsJAXB(osciUnmarshaller)).getValue();
  }

  // ################################# XKMS header ###########################################
  // ################################# XKMS header ###########################################
  // ################################# XKMS header ###########################################

  /**
   * Returns the XKMS 'CompoundResult' element out of the given header list.
   * 
   * @param headerList Header list including the 'CompoundResult' header.
   * @return The requested XKMS 'CompoundResult' element or <code>null</code> if not present.
   * @throws JAXBException In the case of parsing errors.
   */
  public static List<CompoundResultType> getCompoundResultType(HeaderList headerList) throws JAXBException
  {
    return createXKMSHeader(headerList, OSCIConstants.QNAME_COMPOUND_RESULT);
  }

  /**
   * <b>For internal usage only!</b> Creates a addressing object out of the given header list.
   * 
   * @param hl Header list that includes the addressing header.
   * @param addressingHeaderTag QName of the addressing element that would be created.
   * @return The created addressing object or <code>null</code> in the case of no occurrence.
   * @throws JAXBException If the structure of the element is not the expected.
   */
  public static Object createAddressingHeader(HeaderList hl, AddressingHeaderTag addressingHeaderTag)
    throws JAXBException
  {
    Iterator<Header> h = hl.getHeaders(addressingHeaderTag.getQName(), true);
    if ((h != null) && h.hasNext())
    {
      LOG.fine("Found header: " + addressingHeaderTag.getQName().getLocalPart());
      ArrayList<Object> elements = new ArrayList<Object>();
      while (h.hasNext())
      {
        Header header = h.next();
        JAXBElement<?> obj = header.readAsJAXB(addressingUnmarshaller);
        elements.add(obj.getValue());
        hl.remove(header);
      }
      if (elements.size() > 1)
      {
        return elements;
      }
      return elements.get(0);
    }
    return null;
  }

  /**
   * Creates a wsa:Metadata header out of the given header list.
   * 
   * @param hl Header list out of the SOAP message.
   * @return The created wsa:Metadata header.
   */
  public static Header createMetadata(HeaderList hl)
  {
    Header h = hl.get(AddressingHeaderTag.METADATA.getQName(), true);
    if (h != null)
    {
      LOG.fine("Create OSCI header: " + AddressingHeaderTag.METADATA);
      hl.remove(h);
      return h;
    }
    LOG.info("No OSCI header found for QName: " + AddressingHeaderTag.METADATA);
    return null;
  }

  /**
   * Returns the TypeOfBusinessConnector string out of the given EndpointReference.
   * 
   * @param epr EndpointReference with a TypeOfBusinessConnector element.
   */
  public static String getTypeOfBusinessScenario(EndpointReferenceType epr)
  {
    if (epr.getReferenceParameters() != null && epr.getReferenceParameters().getAny() != null
        && epr.getReferenceParameters().getAny().get(0) != null)
    {
      JAXBElement<?> elem = (JAXBElement<?>)epr.getReferenceParameters().getAny().get(0);
      if (elem.getValue() instanceof TypeOfBusinessScenarioType)
        return ((TypeOfBusinessScenarioType)elem.getValue()).getValue();
    }
    return null;
  }

  /**
   * <b>For internal usage only!</b> Creates an OSCI object out of the given header list.
   * 
   * @param hl Header list that includes the OSCI header.
   * @param osciHeaderTag QName of the OSCI element that would be created.
   * @return The created OSCI object or <code>null</code> in the case of no occurrence.
   * @throws JAXBException If the structure of the element is not the expected.
   */
  public static Object createOSCIHeader(HeaderList hl, OSCIHeaderTag osciHeaderTag) throws JAXBException
  {
    Header h = hl.get(osciHeaderTag.getValue(), true);
    if (h != null)
    {
      LOG.fine("Create OSCI header: " + osciHeaderTag);
      JAXBElement<?> obj = h.readAsJAXB(osciUnmarshaller);
      hl.remove(h);
      return obj.getValue();
    }
    LOG.info("No OSCI header found for QName: " + osciHeaderTag);
    return null;
  }

  private static List<CompoundResultType> createXKMSHeader(HeaderList headerList, QName qName)
    throws JAXBException
  {
    ArrayList<CompoundResultType> compList = new ArrayList<CompoundResultType>();
    ArrayList<Header> removeHeader = new ArrayList<Header>();
    Iterator<Header> headerIt = headerList.iterator();
    while (headerIt.hasNext())
    {
      Header header = headerIt.next();
      if (header.getLocalPart().equals(qName.getLocalPart())
          && header.getNamespaceURI().equals(qName.getNamespaceURI()))
      {
        JAXBElement<?> obj = header.readAsJAXB(xkmsUnmarshaller);
        CompoundResultType comp = (CompoundResultType)obj.getValue();
        compList.add(comp);
        removeHeader.add(header);
      }
    }
    for ( Header header : removeHeader )
    {
      headerList.remove(header);
    }
    if (compList.size() == 0)
    {
      LOG.info("No XKMS header found for QName: " + qName);
    }
    return compList;
  }

}
