package at.gv.egovernment.moa.spss.api.xmlbind;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;

import org.w3c.dom.Element;

import at.gv.egovernment.moa.util.Base64Utils;
import at.gv.egovernment.moa.util.CollectionUtils;
import at.gv.egovernment.moa.util.Constants;
import at.gv.egovernment.moa.util.DOMUtils;
import at.gv.egovernment.moa.util.XPathUtils;

import at.gv.egovernment.moa.spss.MOAApplicationException;
import at.gv.egovernment.moa.spss.api.SPSSFactory;
import at.gv.egovernment.moa.spss.api.cmsverify.CMSContent;
import at.gv.egovernment.moa.spss.api.cmsverify.CMSDataObject;
import at.gv.egovernment.moa.spss.api.cmsverify.VerifyCMSSignatureRequest;
import at.gv.egovernment.moa.spss.api.common.MetaInfo;

/**
 * A parser to parse <code>VerifyCMSSignatureRequest</code> DOM trees into
 * <code>VerifyCMSSignatureRequest</code> API objects.
 * 
 * @author Patrick Peck
 * @version $Id$
 */
public class VerifyCMSSignatureRequestParser {

  //
  // XPath expressions for selecting parts of the DOM message
  //
  private static final String MOA = Constants.MOA_PREFIX + ":";
  private static final String DATE_TIME_XPATH = MOA + "DateTime";
  private static final String CMS_SIGNATURE_XPATH = MOA + "CMSSignature";
  private static final String TRUST_PROFILE_ID_XPATH = MOA + "TrustProfileID";
  private static final String DATA_OBJECT_XPATH = MOA + "DataObject";
  private static final String META_INFO_XPATH = MOA + "MetaInfo";
  private static final String CONTENT_XPATH = MOA + "Content";
  private static final String BASE64_CONTENT_XPATH = MOA + "Base64Content";

  /** The <code>SPSSFactory</code> for creating new API objects. */
  private SPSSFactory factory = SPSSFactory.getInstance();

  /**
   * Parse a <code>VerifyCMSSignatureRequest</code> DOM element, as defined
   * by the MOA schema.
   * 
   * @param requestElem The <code>VerifyCMSSignatureRequest</code> to parse. The
   * request must have been successfully parsed against the schema for this
   * method to succeed.
   * @return A <code>VerifyCMSSignatureRequest</code> API objects containing
   * the data from the DOM element.
   * @throws MOAApplicationException An error occurred parsing the request.
   */
  public VerifyCMSSignatureRequest parse(Element requestElem)
    throws MOAApplicationException {

    int[] signatories = parseSignatories(requestElem);
    Date dateTime =
      RequestParserUtils.parseDateTime(requestElem, DATE_TIME_XPATH);
    String cmsSignatureStr =
      XPathUtils.getElementValue(requestElem, CMS_SIGNATURE_XPATH, "");
    CMSDataObject dataObject = parseDataObject(requestElem);
    String trustProfileID =
      XPathUtils.getElementValue(requestElem, TRUST_PROFILE_ID_XPATH, null);
    InputStream cmsSignature =
      Base64Utils.decodeToStream(cmsSignatureStr, true);

    return factory.createVerifyCMSSignatureRequest(
      signatories,
      dateTime,
      cmsSignature,
      dataObject,
      trustProfileID);
  }

  /**
   * Parse the <code>Signatories</code> attribute contained in the 
   * <code>VerifyCMSSignatureRequest</code> DOM element.
   * 
   * @param requestElem The <code>VerifyCMSSignatureRequest</code> DOM element.
   * @return The signatories contained in the given 
   * <code>VerifyCMSSignatureRequest</code> DOM element. 
   */
  private int[] parseSignatories(Element requestElem) {
    String signatoriesStr = requestElem.getAttribute("Signatories");

    if ("all".equals(signatoriesStr)) {
      return VerifyCMSSignatureRequest.ALL_SIGNATORIES;
    } else {
      StringTokenizer tokenizer = new StringTokenizer(signatoriesStr);
      List signatoriesList = new ArrayList();
      int[] signatories;

      // put the signatories into a List
      while (tokenizer.hasMoreTokens()) {
        try {
          signatoriesList.add(new Integer(tokenizer.nextToken()));
        } catch (NumberFormatException e) {
          // this cannot occur if the request has been validated
        }
      }

      // convert the List into an int array
      signatories = CollectionUtils.toIntArray(signatoriesList);

      return signatories;
    }
  }

  /**
   * Parse a the <code>DataObject</code> DOM element contained in a given 
   * <code>VerifyCMSSignatureRequest</code> DOM element.
   * 
   * @param requestElem The <code>VerifyCMSSignatureRequest</code> DOM element
   * to parse.
   * @return The <code>CMSDataObject</code> API object containing the data
   * from the <code>DataObject</code> DOM element.
   */
  private CMSDataObject parseDataObject(Element requestElem) {
    Element dataObjectElem =
      (Element) XPathUtils.selectSingleNode(requestElem, DATA_OBJECT_XPATH);

    if (dataObjectElem != null) {
      Element metaInfoElem =
        (Element) XPathUtils.selectSingleNode(dataObjectElem, META_INFO_XPATH);
      MetaInfo metaInfo = null;
      Element contentElem =
        (Element) XPathUtils.selectSingleNode(dataObjectElem, CONTENT_XPATH);
      CMSContent content = parseContent(contentElem);

      if (metaInfoElem != null) {
        metaInfo = RequestParserUtils.parseMetaInfo(metaInfoElem);
      }

      return factory.createCMSDataObject(metaInfo, content);
    } else {
      return null;
    }

  }

  /**
   * Parse the content contained in a <code>CMSContentBaseType</code> kind of
   * DOM element.
   * 
   * @param contentElem The <code>CMSContentBaseType</code> kind of element to
   * parse.
   * @return A <code>CMSDataObject</code> API object containing the data
   * from the given DOM element.
   */
  private CMSContent parseContent(Element contentElem) {
    Element base64ContentElem =
      (Element) XPathUtils.selectSingleNode(contentElem, BASE64_CONTENT_XPATH);

    if (base64ContentElem != null) {
      String base64Str = DOMUtils.getText(base64ContentElem);
      InputStream binaryContent = Base64Utils.decodeToStream(base64Str, true);
      return factory.createCMSContent(binaryContent);
    } else {
      return factory.createCMSContent(
        contentElem.getAttribute("Reference"));
    }
  }
}