package de.tu_dresden.diplom.richter_mirko_mat2628335.common.context;

import org.apache.log4j.Logger;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.Attr;
import org.w3c.dom.NodeList;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.dependency.IFSOAPDependency;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.dependency.IFSOAPDependencyStatement;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.dependency.SOAPDependency;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.dependency.SOAPDependencyStatement;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.NS;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.Constants;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.account.IFAmount;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.account.IFCurrency;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.account.Amount;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.account.Currency;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.transform.IFDigestTransform;

/**
 * This class represents a step-context that is red from the SOAP-header or maybe later
 * written to the header.
 */
public class StepContext implements IFStepContext {

   private static final Logger logger = Logger.getLogger(StepContext.class);

   private Element contextElement = null;

   private IFAmount servicePrice = null;

   private String nonce = null;
   private IFSOAPDependency dependency = null;
   private boolean performDigestAgreementInStep = false;
   private boolean keepForLaterReferencing = false;
   private IFDigestTransform[] digestTransforms = null;
   private String coordinationCtxIdentification = null;

   /**
    * Gets the context-element that represents this context in XML-format.
    * @return The context-element.
    */
   public Element getContextElement() {
      return contextElement;
   }

   /**
    * Stets the context-element and reads the information out of it.
    * @param contextElement The element to set and to read
    */
   public void setContextElement(Element contextElement) {
      this.contextElement = contextElement;
      readElements();
   }

   /**
    * Gets the price of the service that is being called using this step-context.
    * @return The price of the service.
    */
   public IFAmount getServicePrice() {
      return servicePrice;
   }

   /**
    * Sets the üprice of the service that is being called using this step-context
    * @param servicePrice The price
    */
   public void setServicePrice(IFAmount servicePrice) {
      this.servicePrice = servicePrice;
   }

   /**
    * @see IFStepContext#getNonce()
    */
   public String getNonce() {
      return nonce;
   }

   /**
    * Sets the nonce.
    * @param nonce The new nonce-value.
    */
   public void setNonce(String nonce) {
      this.nonce = nonce;
   }

   /**
    * @see IFStepContext#getDependency()
    */
   public IFSOAPDependency getDependency() {
      return dependency;
   }

   /**
    * Sets the dependencies that shall be used by client and service in its digest-agreement-process.
    * @param dependency The dependencies to be set.
    */
   public void setDependency(IFSOAPDependency dependency) {
      this.dependency = dependency;
   }

   /**
    * @see IFStepContext#isPerformDigestAgreementInStep()
    */
   public boolean isPerformDigestAgreementInStep() {
      return performDigestAgreementInStep;
   }

   /**
    * Sets the wish if the digest-agreement shall be performed by the other party or not.
    * @param performDigestAgreementInStep
    */
   public void setPerformDigestAgreementInStep(boolean performDigestAgreementInStep) {
      this.performDigestAgreementInStep = performDigestAgreementInStep;
   }

   /**
    * Gets the list of transformations that should be performed on the incomming
    * SOAP-message before performing the digest-operation.
    * @return The list of transformtaions that must be performed to be sure that the same
    * digest is being created by both parties.
    */
   public IFDigestTransform[] getDigestTransforms() {
      return digestTransforms;
   }

   /**
    * Sets the transformations that shall be performed by both parties before the digest
    * is calculated.
    * @param digestTransforms The new transforms
    */
   public void setDigestTransforms(IFDigestTransform[] digestTransforms) {
      this.digestTransforms = digestTransforms;
   }

   /**
    * @see IFStepContext#isKeepForLaterReferencing()
    */
   public boolean isKeepForLaterReferencing() {
      return keepForLaterReferencing;
   }

   /**
    * Sets the Flag if the result of the digest-agreement shall be kept for later referencing.
    * @param keepForLaterReferencing The flag.
    */
   public void setKeepForLaterReferencing(boolean keepForLaterReferencing) {
      this.keepForLaterReferencing = keepForLaterReferencing;
   }

   /**
    * @see IFStepContext#getCoordinationCtxIdentification()
    */
   public String getCoordinationCtxIdentification() {
      return coordinationCtxIdentification;
   }

   /**
    * Sets the contextID that is placed within that step-context to keep the connection
    * to the coordination-context if they are separately signed.
    * @param coordinationCtxIdentification
    */
   public void setCoordinationCtxIdentification(String coordinationCtxIdentification) {
      this.coordinationCtxIdentification = coordinationCtxIdentification;
   }

   /**
    * Reads the values out of the set contextElement (XML-representation normally
    * red from the SOAP-header).
    */
   private void readElements() {
      final String fn = "[readElements]";
      Element elem = (Element) getContextElement().getElementsByTagNameNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Nonce").item(0);
      if (elem != null) {
         setNonce(elem.getTextContent());
         if (logger.isDebugEnabled()) logger.debug(fn + " setting NONCE to '" + getNonce() + "'");
      } else {
         logger.warn(fn + " couldn't find mandatory element NONCE!");
      }
      elem = (Element) getContextElement().getElementsByTagNameNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "ContextIdentifier").item(0);
      if (elem != null) {
         setCoordinationCtxIdentification(elem.getTextContent());
         if (logger.isDebugEnabled()) logger.debug(fn + " setting CONTEXTIDENTIFIER to '" + getNonce() + "'");
      } else {
         logger.warn(fn + " couldn't find mandatory element CONTEXTIDENTIFIER!");
      }
      elem = (Element) getContextElement().getElementsByTagNameNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "ServicePrice").item(0);
      if (elem != null) {
         Amount newServicePrice = new Amount();
         String amount = elem.getAttributeNode("Amount").getTextContent();
         if (amount != null) {
            newServicePrice.setAmount(Float.parseFloat(amount));
         }
         String curr = elem.getAttributeNode("KeepForLaterReferencing").getTextContent();
         if (curr != null) {
            Currency newCurr = new Currency(Integer.parseInt(curr));
            newServicePrice.setCurrency(newCurr);
         }
         setServicePrice(newServicePrice);
         if (logger.isDebugEnabled()) logger.debug(fn + " setting SERVICEPRICE to '" + getServicePrice() + "'");
      } else {
         logger.info(fn + " couldn't find optional element SERVICEPRICE!");
      }
      elem = (Element) getContextElement().getElementsByTagNameNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "DigestAgreement").item(0);
      if (elem != null) {
         if (elem.getAttributeNode("RequiredInStep").getTextContent().equals("true")) {
            setPerformDigestAgreementInStep(true);
         } else {
            setPerformDigestAgreementInStep(false);
         }
         if (elem.getAttributeNode("KeepForLaterReferencing").getTextContent().equals("true")) {
            setKeepForLaterReferencing(true);
         } else {
            setKeepForLaterReferencing(false);
         }
         NodeList transforms = elem.getElementsByTagNameNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Transform");
         for (int i = transforms.getLength() - 1; i >= 0; i--) {
            Element node = (Element) transforms.item(i);
            String alg = node.getAttribute("Algorithm");
            if (!alg.equals(Constants.TRANSFORM_ALGORITHM_DIGEST_FULL_BODY)) {
               throw new UnsupportedOperationException("Algorithms other than digest over full body are not supported yet!");
            }
         }
      }
      elem = (Element) getContextElement().getElementsByTagNameNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Dependencies").item(0);
      if (elem != null) {
         NodeList dependencies = elem.getElementsByTagNameNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Dependency");
         if (dependencies != null && dependencies.getLength() > 0) {
            SOAPDependencyStatement[] deps = new SOAPDependencyStatement[dependencies.getLength()];
            for (int i = dependencies.getLength() - 1; i >= 0; i--) {
               Element depElem = (Element) dependencies.item(i);
               SOAPDependencyStatement dep = new SOAPDependencyStatement();
               dep.setDependency(depElem.getTextContent());
               deps[i] = dep;
            }
            SOAPDependency dependency = new SOAPDependency();
            dependency.setDependencyStatements(deps);
            setDependency(dependency);
         }
      }
   }

   /**
    * Creates a org.w3c.dom.Element form data within thsi object.
    * @param doc Document into which the Element will be placed later.
    * @param countingContext The counting-context-element that is being used for NS-lookups f.e.
    * @return The newly created Element from the data within this object. 
    */
   public Element makeContextElement(Document doc, Element countingContext) {
      final String fn = "[makeContextElement]";
      String nsPrefix = countingContext.lookupPrefix(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE);
      Element stepContext = doc.createElementNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, Constants.ELEMENT_NAME_STEP_CONTEXT);
      stepContext.setPrefix(nsPrefix);

      Element nonce = doc.createElementNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Nonce");
      nonce.setPrefix(nsPrefix);
      nonce.setTextContent(getNonce());
      stepContext.appendChild(nonce);

      Element coordinationCtxId = doc.createElementNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "ContextIdentifier");
      coordinationCtxId.setPrefix(nsPrefix);
      coordinationCtxId.setTextContent(getCoordinationCtxIdentification());
      stepContext.appendChild(coordinationCtxId);

      if (getServicePrice() != null) {
         Element servicePrice = doc.createElementNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "ServicePrice");
         servicePrice.setPrefix(nsPrefix);
         Attr amount = doc.createAttributeNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Amount");
         amount.setValue("" + getServicePrice().getAmount());
         servicePrice.setAttributeNodeNS(amount);
         if (getServicePrice().getCurrency() != null) {
            Attr curr = doc.createAttributeNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Currency");
            curr.setValue("" + getServicePrice().getCurrency().getCurrency());
            servicePrice.setAttributeNodeNS(curr);
         }
         stepContext.appendChild(servicePrice);
      }

      Element digestAgreement = doc.createElementNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "DigestAgreement");
      digestAgreement.setPrefix(nsPrefix);
      Attr required = doc.createAttributeNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "RequiredInStep");
      if (isPerformDigestAgreementInStep()) {
         required.setValue("true");
      } else {
         required.setValue("false");
      }
      digestAgreement.setAttributeNodeNS(required);
      Attr keep = doc.createAttributeNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "KeepForLaterReferencing");
      if (isKeepForLaterReferencing()) {
         keep.setValue("true");
      } else {
         keep.setValue("false");
      }
      digestAgreement.setAttributeNodeNS(keep);
      stepContext.appendChild(digestAgreement);
      Element digestTransformElements = doc.createElementNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Transforms");
      digestTransformElements.setPrefix(nsPrefix);
      digestAgreement.appendChild(digestTransformElements);
      if (getDigestTransforms() == null) {
         Element transform = doc.createElementNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Transform");
         transform.setPrefix(nsPrefix);
         Attr algorithm = doc.createAttributeNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Algorithm");
         algorithm.setValue(Constants.TRANSFORM_ALGORITHM_DIGEST_FULL_BODY);
         transform.setAttributeNodeNS(algorithm);
         digestTransformElements.appendChild(transform);
      } else {
         logger.error(fn + " digest transforms not supported yet");
         throw new UnsupportedOperationException("digest transforms not supported yet");
      }
      if (getDependency() != null) {
         Element dependencies = doc.createElementNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Dependencies");
         dependencies.setPrefix(nsPrefix);
         stepContext.appendChild(dependencies);
         IFSOAPDependencyStatement[] statements = getDependency().getDependencyStatements();
         if (statements != null) {
            for (int i = 0; i < statements.length; i++) {
               IFSOAPDependencyStatement statement = statements[i];
               Element depStatement = doc.createElementNS(NS.ACCESS_CONTROL_PROTOTYP_NAMESPACE, "Dependency");
               depStatement.setPrefix(nsPrefix);
               depStatement.setTextContent(statement.getDependency());
               dependencies.appendChild(depStatement);
            }
         }
      }
      return stepContext;
   }

}
