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

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
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.Base64Utils;
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.MetaInfo;
import at.gv.egovernment.moa.spss.api.common.XMLDataObjectAssociation;
import at.gv.egovernment.moa.spss.api.xmlsign.CreateSignatureEnvironmentProfile;
import at.gv.egovernment.moa.spss.api.xmlsign.CreateSignatureLocation;
import at.gv.egovernment.moa.spss.api.xmlsign.CreateTransformsInfo;
import at.gv.egovernment.moa.spss.api.xmlsign.CreateTransformsInfoProfile;
import at.gv.egovernment.moa.spss.api.xmlverify.SupplementProfile;
import at.gv.egovernment.moa.spss.api.xmlverify.TransformParameter;
import at.gv.egovernment.moa.spss.api.xmlverify.VerifyTransformsInfoProfile;

/**
 * Parse the various profile elements contained in the MOA web service requests
 * and given as separate files in the MOA configuration.
 * 
 * The profiles parsed must be schema valid according to the MOA XML schema.
 *  
 * @author Patrick Peck
 * @version $Id$
 */
public class ProfileParser {
  
  //
  // XPath expressions to select parts of the profiles
  //
  private static final String MOA = Constants.MOA_PREFIX + ":";
  private static final String DSIG = Constants.DSIG_PREFIX + ":";
  private static final String CREATE_TRANSFORMS_XPATH =
    MOA + "CreateTransformsInfo/" + DSIG + "Transforms";
  private static final String FINAL_DATA_META_INFO_XPATH =
    MOA + "CreateTransformsInfo/" + MOA + "FinalDataMetaInfo";
  private static final String CREATE_SIGNATURE_LOCATION_XPATH =
    MOA + "CreateSignatureLocation";
  private static final String SUPPLEMENT_XPATH = MOA + "Supplement";
  private static final String VERIFY_TRANSFORMS_XPATH = DSIG + "Transforms";
  private static final String TRANSFORM_PARAMETER_XPATH =
    MOA + "TransformParameter";
  private static final String TRANSFORM_PARAMETER_CONTENT_XPATH =
    MOA + "Base64Content | " + MOA + "Hash";
  private static final String DIGEST_METHOD_XPATH = DSIG + "DigestMethod";
  private static final String DIGEST_VALUE_XPATH = DSIG + "DigestValue";

  /** The factory used to create API objects. */
  private SPSSFactory factory = SPSSFactory.getInstance();

  /**
   * Parse a <code>CreateTransformsInfoProfile</code> DOM element.
   * 
   * @param profileElem The <code>CreateTransformsInfoProfile</code> element
   * to parse.
   * @return The <code>CreateTransformsInfoProfile</code> API object containing
   * the data from the <code>profileElem</code>.
   * @throws MOAApplicationException An error occurred parsing the DOM element. 
   */
  public CreateTransformsInfoProfile parseCreateTransformsInfoProfile(Element profileElem)
    throws MOAApplicationException {
    CreateTransformsInfo createTransformsInfo =
      parseCreateTransformsInfo(profileElem);
    List supplements = parseSupplements(profileElem);

    return factory.createCreateTransformsInfoProfile(
      createTransformsInfo,
      supplements);
  }

  /**
   * Parse the <code>CreateTransformsInfo</code> DOM element contained in a 
   * <code>CreateTransformsInfoProfile</code>.
   * 
   * @param profileElem The <code>CreateTransformsInfoProfile</code> DOM
   * element containing the <code>CreateTransformsInfo</code>.
   * @return The <code>CreateTransformsInfo</code> API object containinig the
   * data from the <code>CreateTransformsInfo</code> DOM element.
   * @throws MOAApplicationException An error occurred parsing the
   * <code>CreateTransformsInfo</code> DOM element.
   */
  private CreateTransformsInfo parseCreateTransformsInfo(Element profileElem)
    throws MOAApplicationException {

    Element transformsElem =
      (Element) XPathUtils.selectSingleNode(
        profileElem,
        CREATE_TRANSFORMS_XPATH);
    Element metaInfoElem =
      (Element) XPathUtils.selectSingleNode(
        profileElem,
        FINAL_DATA_META_INFO_XPATH);
    MetaInfo finalDataMetaInfo;
    List transforms;

    // parse the dsig:Transforms    
    if (transformsElem != null) {
      TransformParser transformsParser = new TransformParser();
      transforms = transformsParser.parseTransforms(transformsElem);
    } else {
      transforms = null;
    }

    // parse the meta info
    finalDataMetaInfo = RequestParserUtils.parseMetaInfo(metaInfoElem);

    return factory.createCreateTransformsInfo(transforms, finalDataMetaInfo);
  }

  /**
   * Parse a <code>CreateSignatureEnvironmentProfile</code> DOM element.
   * 
   * @param profileElem The <code>CreateSignatureEnvironmentProfile</code>
   * DOM element to parse.
   * @return The <code>CreateSignatureEnvironmentProfile</code> API object
   * containing the data from the <code>profileElem</code>.
   */
  public CreateSignatureEnvironmentProfile parseCreateSignatureEnvironmentProfile(Element profileElem) {
    CreateSignatureLocation createSignatureLocation =
      parseCreateSignatureLocation(profileElem);
    List supplements = parseSupplements(profileElem);

    return factory.createCreateSignatureEnvironmentProfile(
      createSignatureLocation,
      supplements);
  }

  /**
   * Parse a <code>CreateSignatureLocation</code> DOM element contained in 
   * a <code>CreateSignatureEnvironmentProfile</code>.
   * 
   * @param profileElem The <code>CreateSignatureEnvironmentProfile</code> DOM
   * element containing the <code>CreateSignatureLocation</code>.
   * @return The <code>CreateSignatureLocation</code> API object containing
   * the data from the <code>CreateSignatureLocation</code> DOM element.
   */
  private CreateSignatureLocation parseCreateSignatureLocation(Element profileElem) {
    Element locationElem =
      (Element) XPathUtils.selectSingleNode(
        profileElem,
        CREATE_SIGNATURE_LOCATION_XPATH);
    String xPathExpression = DOMUtils.getText(locationElem);
    Map namespaceDeclarations = DOMUtils.getNamespaceDeclarations(locationElem);
    String indexStr = locationElem.getAttribute("Index");
    int index = Integer.parseInt(indexStr);

    return factory.createCreateSignatureLocation(
      xPathExpression,
      index,
      namespaceDeclarations);
  }

  /**
   * Parse all <code>Supplement</code> DOM elements contained in a given
   * parent DOM element.
   * 
   * @param supplementsParentElem The DOM element being the parent of the
   * <code>Supplement</code>s.
   * @return A <code>List</code> of <code>Supplement</code> API objects 
   * containing the data from the <code>Supplement</code> DOM elements.
   */
  private List parseSupplements(Element supplementsParentElem) {
    List supplements = new ArrayList();
    NodeIterator supplementElems =
      XPathUtils.selectNodeIterator(supplementsParentElem, SUPPLEMENT_XPATH);
    Element supplementElem;

    while ((supplementElem = (Element) supplementElems.nextNode()) != null) {
      XMLDataObjectAssociation supplement =
        RequestParserUtils.parseXMLDataObjectAssociation(supplementElem);
      supplements.add(supplement);
    }
    return supplements;
  }

  /**
   * Parse a <code>SupplementProfile</code> DOM element.
   * 
   * @param profileElem The <code>SupplementProfile</code> DOM element to parse.
   * @return The <code>SupplementProfile</code> API object containing the
   * data from the <code>SupplementProfile</code> DOM element.
   */
  public SupplementProfile parseSupplementProfile(Element profileElem) {
    XMLDataObjectAssociation supplementProfile =
      RequestParserUtils.parseXMLDataObjectAssociation(profileElem);

    return factory.createSupplementProfile(supplementProfile);
  }

  /**
   * Parse a <code>VerifyTransformsInfoProfile</code> DOM element. 
   * 
   * @param profileElem The <code>VerifyTransformsInfoProfile</code> DOM 
   * element to parse.
   * @return A <code>VerifyTransformsInfoProfile</code> API object containing
   * the information from the <code>VerifyTransformsInfoProfile</code> DOM 
   * element.
   * @throws MOAApplicationException An error occurred parsing the
   * <code>VerifyTransformsInfoProfile</code>.
   */
  public VerifyTransformsInfoProfile parseVerifyTransformsInfoProfile(Element profileElem)
    throws MOAApplicationException {
    Element transformsElem =
      (Element) XPathUtils.selectSingleNode(
        profileElem,
        VERIFY_TRANSFORMS_XPATH);
    List transforms = null;
    NodeIterator paramElems =
      XPathUtils.selectNodeIterator(profileElem, TRANSFORM_PARAMETER_XPATH);
    Element paramElem;
    List transformParameters = new ArrayList();

    // parse the dsig:Transforms
    if (transformsElem != null) {
      TransformParser transformsParser = new TransformParser();
      transforms = transformsParser.parseTransforms(transformsElem);
    }

    // parse the TransformParameter elements
    while ((paramElem = (Element) paramElems.nextNode()) != null) {
      transformParameters.add(parseTransformParameter(paramElem));
    }

    return factory.createVerifyTransformsInfoProfile(
      transforms,
      transformParameters);
  }

  /**
   * Parse a <code>TransformParameter</code> DOM element.
   * 
   * @param paramElem The <code>TransformParameter</code> DOM element to
   * parse.
   * @return The <code>TransformParameter</code> API object containing the
   * information from the <code>TransformParameter</code> DOM element.
   * @throws MOAApplicationException An error occurred parsing the
   * <code>TransformParameter</code> DOM element.
   */
  private TransformParameter parseTransformParameter(Element paramElem) 
    throws MOAApplicationException {
    String uri = paramElem.getAttribute("URI");
    Element contentElem =
      (Element) XPathUtils.selectSingleNode(
        paramElem,
        TRANSFORM_PARAMETER_CONTENT_XPATH);

    if (contentElem == null) {
      return factory.createTransformParameter(uri);
    } else if ("Base64Content".equals(contentElem.getLocalName())) {
      String base64Str = DOMUtils.getText(contentElem);
      InputStream binaryContent = Base64Utils.decodeToStream(base64Str, true);

      return factory.createTransformParameter(uri, binaryContent);
    } else { // "Hash".equals(contentElem.getLocalName())
      String digestMethodStr =
        XPathUtils.getElementValue(contentElem, DIGEST_METHOD_XPATH, "");
      String digestValueStr =
        XPathUtils.getElementValue(contentElem, DIGEST_VALUE_XPATH, "");
      byte[] digestValue = null;

      try {
        digestValue = Base64Utils.decode(digestValueStr, true);
      } catch (IOException e) {
        throw new MOAApplicationException("2270", null); 
      }
      return factory.createTransformParameter(
        uri,
        digestMethodStr,
        digestValue);
    }
  }

}