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

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.w3c.dom.Element;
import org.w3c.dom.traversal.NodeIterator;

import at.gv.egovernment.moa.util.BoolUtils;
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.common.Content;
import at.gv.egovernment.moa.spss.api.xmlverify.ReferenceInfo;
import at.gv.egovernment.moa.spss.api.xmlverify.SignatureManifestCheckParams;
import at.gv.egovernment.moa.spss.api.xmlverify.SupplementProfile;
import at.gv.egovernment.moa.spss.api.xmlverify.VerifySignatureInfo;
import at.gv.egovernment.moa.spss.api.xmlverify.VerifySignatureLocation;
import at.gv.egovernment.moa.spss.api.xmlverify.VerifyXMLSignatureRequest;

/**
 * @author Patrick Peck
 * @version $Id$
 */
public class VerifyXMLSignatureRequestParser {

  //
  // XPath expressions for parsing parts of the request
  //
  private static final String MOA = Constants.MOA_PREFIX + ":";
  private static final String DATE_TIME_XPATH = MOA + "DateTime";
  private static final String RETURN_HASH_INPUT_DATA_XPATH =
    MOA + "ReturnHashInputData";
  private static final String TRUST_PROFILE_ID_XPATH = MOA + "TrustProfileID";
  private static final String VERIFY_SIGNATURE_ENVIRONMENT_XPATH =
    MOA + "VerifySignatureInfo/" + MOA + "VerifySignatureEnvironment";
  private static final String VERIFY_SIGNATURE_LOCATION_XPATH =
    MOA + "VerifySignatureInfo/" + MOA + "VerifySignatureLocation";
  private static final String SUPPLEMENT_PROFILE_XPATH =
    MOA + "SupplementProfile | " + MOA + "SupplementProfileID";
  private static final String SIGNATURE_MANIFEST_CHECK_PARAMS_XPATH =
    MOA + "SignatureManifestCheckParams";
  private static final String VERIFY_TRANSFORMS_INFO_PROFILE_XPATH =
    (MOA + "VerifyTransformsInfoProfile | ")
      + (MOA + "VerifyTransformsInfoProfileID");
  private static final String REFERENCE_INFO_XPATH = MOA + "ReferenceInfo";

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


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

    Date dateTime =
      RequestParserUtils.parseDateTime(requestElem, DATE_TIME_XPATH);
    VerifySignatureInfo verifySignatureInfo =
      parseVerifySignatureInfo(requestElem);
    List supplementProfiles = parseSupplementProfiles(requestElem);
    SignatureManifestCheckParams signatureManifestCheckParams =
      parseSignatureManifestCheckParams(requestElem);
    boolean returnHashInputData =
      XPathUtils.selectSingleNode(requestElem, RETURN_HASH_INPUT_DATA_XPATH)
        != null;
    String trustProfileID =
      XPathUtils.getElementValue(requestElem, TRUST_PROFILE_ID_XPATH, null);

    return factory.createVerifyXMLSignatureRequest(
      dateTime,
      verifySignatureInfo,
      supplementProfiles,
      signatureManifestCheckParams,
      returnHashInputData,
      trustProfileID);
  }

  /**
   * Parse the <code>VerifySignatureInfo</code> DOM element contained in
   * the <code>VerifyXMLSignatureRequest</code> DOM element.
   * 
   * @param requestElem The <code>VerifyXMLSignatureRequest</code> DOM element
   * containing the <code>VerifySignatureInfo</code> DOM element.
   * @return The <code>VerifySignatureInfo</code> API object containing the
   * data from the DOM element.
   */
  private VerifySignatureInfo parseVerifySignatureInfo(Element requestElem) {
    Element verifySignatureEnvironmentElem =
      (Element) XPathUtils.selectSingleNode(
        requestElem,
        VERIFY_SIGNATURE_ENVIRONMENT_XPATH);
    Content verifySignatureEnvironment =
      RequestParserUtils.parseContent(verifySignatureEnvironmentElem);
    VerifySignatureLocation verifySignatureLocation =
      parseVerifySignatureLocation(requestElem);

    return factory.createVerifySignatureInfo(
      verifySignatureEnvironment,
      verifySignatureLocation);
  }

  /**
   * Parse the <code>VerifySignatureLocation</code> DOM element contained
   * in the given <code>VerifyXMLSignatureRequest</code> DOM element.
   * 
   * @param requestElem The <code>VerifyXMLSignatureRequst</code> DOM element.
   * @return The <code>VerifySignatureLocation</code> API object containing the
   * data from the DOM element.
   */
  private VerifySignatureLocation parseVerifySignatureLocation(Element requestElem) {
    Element locationElem =
      (Element) XPathUtils.selectSingleNode(
        requestElem,
        VERIFY_SIGNATURE_LOCATION_XPATH);
    String xPathExpression = DOMUtils.getText(locationElem);
    Map namespaceDeclarations = DOMUtils.getNamespaceDeclarations(locationElem);

    return factory.createVerifySignatureLocation(
      xPathExpression,
      namespaceDeclarations);
  }

  /**
   * Parse the supplement profiles contained in the given 
   * <code>VerifyXMLSignatureRequest</code> DOM element.
   * 
   * @param requestElem The <code>VerifyXMLSignatureRequest</code> DOM element.
   * @return A <code>List</code> of <code>SupplementProfile</code> API objects
   * containing the data from the <code>SupplementProfile</code> DOM elements.
   */
  private List parseSupplementProfiles(Element requestElem) {
    List supplementProfiles = new ArrayList();
    NodeIterator profileElems =
      XPathUtils.selectNodeIterator(requestElem, SUPPLEMENT_PROFILE_XPATH);
    Element profileElem;

    while ((profileElem = (Element) profileElems.nextNode()) != null) {
      SupplementProfile profile;

      if ("SupplementProfile".equals(profileElem.getLocalName())) {
        ProfileParser profileParser = new ProfileParser();
        profile = profileParser.parseSupplementProfile(profileElem);
      } else {
        String profileID = DOMUtils.getText(profileElem);
        profile = factory.createSupplementProfile(profileID);
      }
      supplementProfiles.add(profile);
    }
    return supplementProfiles;
  }

  /**
   * Parse the <code>SignatureManifestCheckParams</code> DOM element contained
   * in the given <code>VerifyXMLSignatureRequest</code> DOM element.
   * @param requestElem The <code>VerifyXMLSignatureRequest</code> DOM element.
   * @return The <code>SignatureManifestCheckParams</code> API object containing
   * the data from the <code>SignatureManifestCheckParams</code> DOM element.
   * @throws MOAApplicationException An error occurred parsing the
   * <code>SignatureManifestCheckParams</code> DOM element.
   */
  private SignatureManifestCheckParams parseSignatureManifestCheckParams(Element requestElem)
    throws MOAApplicationException {
    Element paramsElem =
      (Element) XPathUtils.selectSingleNode(
        requestElem,
        SIGNATURE_MANIFEST_CHECK_PARAMS_XPATH);

    if (paramsElem != null) {
      String returnReferenceInputDataStr =
        paramsElem.getAttribute("ReturnReferenceInputData");
      boolean returnReferencInputData =
        BoolUtils.valueOf(returnReferenceInputDataStr);
      List referenceInfos = parseReferenceInfos(paramsElem);

      return factory.createSignatureManifestCheckParams(
        referenceInfos,
        returnReferencInputData);
    } else {
      return null;
    }
  }

  /**
   * Parse the <code>ReferenceInfo</code> DOM elements contained in a
   * <code>SignatureManifestCheckParams</code> DOM element.
   * 
   * @param paramsElem The <code>SignatureManifestCheckParams</code> DOM element
   * containing the <code>ReferenceInfo</code> DOM elements.
   * @return A <code>List</code> of <code>RefernceInfo</code> API objects
   * containing the data from the <code>ReferenceInfo</code> DOM elements.
   * @throws MOAApplicationException An error occurred parsing the 
   * <code>ReferenceInfo</code> DOM elements.
   */
  private List parseReferenceInfos(Element paramsElem)
    throws MOAApplicationException {

    List referenceInfos = new ArrayList();
    NodeIterator refInfoElems =
      XPathUtils.selectNodeIterator(paramsElem, REFERENCE_INFO_XPATH);
    Element refInfoElem;

    while ((refInfoElem = (Element) refInfoElems.nextNode()) != null) {
      ReferenceInfo referenceInfo = parseReferenceInfo(refInfoElem);

      referenceInfos.add(referenceInfo);
    }

    return referenceInfos;
  }

  /**
   * Parse a <code>ReferenceInfo</code> DOM element.
   *  
   * @param refInfoElem The <code>ReferenceInfo</code> DOM element to parse.
   * @return The <code>ReferenceInfo</code> API object containing the data
   * from the given <code>ReferenceInfo</code> DOM element.
   * @throws MOAApplicationException An error occurred parsing the
   * <code>ReferenceInfo</code> DOM element.
   */
  private ReferenceInfo parseReferenceInfo(Element refInfoElem)
    throws MOAApplicationException {
    List profiles = parseVerifyTransformsInfoProfiles(refInfoElem);
    return factory.createReferenceInfo(profiles);
  }

  /**
   * Parse the <code>VerifyTransformsInfoProfile</code> DOM elements contained
   * in a <code>ReferenceInfo</code> DOM element.
   * 
   * @param refInfoElem <code>ReferenceInfo</code> DOM element containing
   * the <code>VerifyTransformsInfoProfile</code> DOM elements.
   * @return A <code>List</code> of <code>VerifyTransformsInfoProfile</code>
   * API objects containing the profile data.
   * @throws MOAApplicationException An error occurred building the
   * <code>VerifyTransformsInfoProfile</code>s.
   */
  private List parseVerifyTransformsInfoProfiles(Element refInfoElem)
    throws MOAApplicationException {

    List profiles = new ArrayList();
    NodeIterator profileElems =
      XPathUtils.selectNodeIterator(
        refInfoElem,
        VERIFY_TRANSFORMS_INFO_PROFILE_XPATH);
    Element profileElem;

    while ((profileElem = (Element) profileElems.nextNode()) != null) {
      if ("VerifyTransformsInfoProfile".equals(profileElem.getLocalName())) {
        ProfileParser profileParser = new ProfileParser();
        profiles.add(
          profileParser.parseVerifyTransformsInfoProfile(profileElem));
      } else {
        String profileID = DOMUtils.getText(profileElem);
        profiles.add(factory.createVerifyTransformsInfoProfile(profileID));
      }
    }
    return profiles;
  }

}