package com.sun.identity.saml2.protocol.impl;

import com.sun.identity.saml2.assertion.AssertionFactory;
import com.sun.identity.saml2.assertion.Conditions;
import com.sun.identity.saml2.assertion.Subject;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2SDKUtils;
import com.sun.identity.saml2.protocol.AuthnRequest;
import com.sun.identity.saml2.protocol.NameIDPolicy;
import com.sun.identity.saml2.protocol.ProtocolFactory;
import com.sun.identity.saml2.protocol.RequestedAuthnContext;
import com.sun.identity.saml2.protocol.Scoping;
import com.sun.identity.shared.DateUtils;
import com.sun.identity.shared.xml.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class AuthnRequestImpl extends RequestAbstractImpl implements AuthnRequest {
   private Subject subject;
   private NameIDPolicy nameIDPolicy;
   private Conditions conditions;
   private RequestedAuthnContext reqAuthnContext;
   private Scoping scoping;
   private Integer assertionConsumerSvcIndex;
   private Integer attrConsumingSvcIndex;
   private String providerName;
   private Boolean forceAuthn;
   private Boolean isPassive;
   private String protocolBinding;
   private String assertionConsumerServiceURL;

   public AuthnRequestImpl() {
      this.isMutable = true;
   }

   public AuthnRequestImpl(Element element) throws SAML2Exception {
      this.parseDOMElement(element);
      if (this.isSigned) {
         this.signedXMLString = XMLUtils.print(element);
      }
   }

   public AuthnRequestImpl(String xmlString) throws SAML2Exception {
      Document xmlDocument = XMLUtils.toDOMDocument(xmlString, SAML2SDKUtils.DEBUG);
      if (xmlDocument == null) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("errorObtainingElement"));
      }

      this.parseDOMElement(xmlDocument.getDocumentElement());
      if (this.isSigned) {
         this.signedXMLString = xmlString;
      }
   }

   @Override
   public Subject getSubject() {
      return this.subject;
   }

   @Override
   public void setSubject(Subject subject) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.subject = subject;
   }

   @Override
   public NameIDPolicy getNameIDPolicy() {
      return this.nameIDPolicy;
   }

   @Override
   public void setNameIDPolicy(NameIDPolicy nameIDPolicy) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.nameIDPolicy = nameIDPolicy;
   }

   @Override
   public Conditions getConditions() {
      return this.conditions;
   }

   @Override
   public void setConditions(Conditions conditions) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.conditions = conditions;
   }

   @Override
   public RequestedAuthnContext getRequestedAuthnContext() {
      return this.reqAuthnContext;
   }

   @Override
   public void setRequestedAuthnContext(RequestedAuthnContext reqAuthnContext) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.reqAuthnContext = reqAuthnContext;
   }

   @Override
   public void setScoping(Scoping scoping) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.scoping = scoping;
   }

   @Override
   public Scoping getScoping() {
      return this.scoping;
   }

   @Override
   public Boolean isForceAuthn() {
      return this.forceAuthn;
   }

   @Override
   public void setForceAuthn(Boolean value) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.forceAuthn = value;
   }

   @Override
   public Boolean isPassive() {
      return this.isPassive;
   }

   @Override
   public void setIsPassive(Boolean value) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.isPassive = value;
   }

   @Override
   public void setProtocolBinding(String protocolBinding) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.protocolBinding = protocolBinding;
   }

   @Override
   public String getProtocolBinding() {
      return this.protocolBinding;
   }

   @Override
   public String getAssertionConsumerServiceURL() {
      return this.assertionConsumerServiceURL;
   }

   @Override
   public void setAssertionConsumerServiceURL(String url) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.assertionConsumerServiceURL = url;
   }

   @Override
   public Integer getAssertionConsumerServiceIndex() {
      return this.assertionConsumerSvcIndex;
   }

   @Override
   public void setAssertionConsumerServiceIndex(Integer index) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.assertionConsumerSvcIndex = index;
   }

   @Override
   public Integer getAttributeConsumingServiceIndex() {
      return this.attrConsumingSvcIndex;
   }

   @Override
   public void setAttributeConsumingServiceIndex(Integer index) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.attrConsumingSvcIndex = index;
   }

   @Override
   public void setProviderName(String providerName) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.providerName = providerName;
   }

   @Override
   public String getProviderName() {
      return this.providerName;
   }

   @Override
   public String toXMLString() throws SAML2Exception {
      return this.toXMLString(true, false);
   }

   @Override
   public String toXMLString(boolean includeNSPrefix, boolean declareNS) throws SAML2Exception {
      if (this.isSigned && this.signedXMLString != null) {
         return this.signedXMLString;
      }

      this.validateData();
      this.validateAssertionConsumerServiceIndex(this.assertionConsumerSvcIndex);
      this.validateAttributeConsumingServiceIndex(this.attrConsumingSvcIndex);
      StringBuilder xmlString = new StringBuilder(1000);
      xmlString.append("<");
      if (includeNSPrefix) {
         xmlString.append("samlp:");
      }

      xmlString.append("AuthnRequest").append(" ");
      if (declareNS) {
         xmlString.append(" xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"").append("\n");
      }

      xmlString.append("ID")
         .append("=")
         .append("\"")
         .append(this.requestId)
         .append("\"")
         .append(" ")
         .append("Version")
         .append("=")
         .append("\"")
         .append(this.version)
         .append("\"")
         .append(" ")
         .append("IssueInstant")
         .append("=")
         .append("\"")
         .append(DateUtils.toUTCDateFormat(this.issueInstant))
         .append("\"");
      if (this.destinationURI != null && !this.destinationURI.isEmpty()) {
         xmlString.append(" ").append("Destination").append("=").append("\"").append(this.destinationURI).append("\"");
      }

      if (this.consent != null && !this.consent.isEmpty()) {
         xmlString.append(" ").append("Consent").append("=").append("\"").append(this.consent).append("\"");
      }

      if (this.forceAuthn != null) {
         xmlString.append(" ").append("ForceAuthn").append("=").append("\"").append(this.forceAuthn).append("\"");
      }

      if (this.isPassive != null) {
         xmlString.append(" ").append("IsPassive").append("=").append("\"").append(this.isPassive).append("\"");
      }

      if (this.assertionConsumerSvcIndex != null) {
         xmlString.append(" ").append("AssertionConsumerServiceIndex").append("=").append("\"").append(this.assertionConsumerSvcIndex).append("\"");
      } else {
         if (this.protocolBinding != null && !this.protocolBinding.isEmpty()) {
            xmlString.append(" ").append("ProtocolBinding").append("=").append("\"").append(this.protocolBinding).append("\"");
         }

         if (this.assertionConsumerServiceURL != null && !this.assertionConsumerServiceURL.isEmpty()) {
            xmlString.append(" ")
               .append("AssertionConsumerServiceURL")
               .append("=")
               .append("\"")
               .append(XMLUtils.escapeSpecialCharacters(this.assertionConsumerServiceURL))
               .append("\"");
         }
      }

      if (this.attrConsumingSvcIndex != null) {
         xmlString.append(" ").append("AttributeConsumingServiceIndex").append("=").append("\"").append(this.attrConsumingSvcIndex).append("\"");
      }

      if (this.providerName != null && !this.providerName.isEmpty()) {
         xmlString.append(" ").append("ProviderName").append("=").append("\"").append(this.providerName).append("\"");
      }

      xmlString.append(">");
      if (this.nameID != null) {
         String issuerString = this.nameID.toXMLString(includeNSPrefix, declareNS);
         xmlString.append("\n").append(issuerString);
      }

      if (this.signatureString != null && !this.signatureString.isEmpty()) {
         xmlString.append("\n").append(this.signatureString);
      }

      if (this.extensions != null) {
         xmlString.append("\n").append(this.extensions.toXMLString(includeNSPrefix, declareNS));
      }

      if (this.subject != null) {
         xmlString.append("\n").append(this.subject.toXMLString(includeNSPrefix, declareNS));
      }

      if (this.nameIDPolicy != null) {
         xmlString.append("\n").append(this.nameIDPolicy.toXMLString(includeNSPrefix, declareNS));
      }

      if (this.conditions != null) {
         xmlString.append("\n").append(this.conditions.toXMLString(includeNSPrefix, declareNS));
      }

      if (this.reqAuthnContext != null) {
         xmlString.append("\n").append(this.reqAuthnContext.toXMLString(includeNSPrefix, declareNS));
      }

      if (this.scoping != null) {
         xmlString.append("\n").append(this.scoping.toXMLString(includeNSPrefix, declareNS));
      }

      xmlString.append("\n").append("</samlp:").append("AuthnRequest").append(">");
      return xmlString.toString();
   }

   @Override
   public void makeImmutable() {
      if (this.isMutable) {
         super.makeImmutable();
         if (this.subject != null && this.subject.isMutable()) {
            this.subject.makeImmutable();
         }

         if (this.nameIDPolicy != null && this.nameIDPolicy.isMutable()) {
            this.nameIDPolicy.makeImmutable();
         }

         if (this.conditions != null && this.conditions.isMutable()) {
            this.conditions.makeImmutable();
         }

         if (this.reqAuthnContext != null && this.reqAuthnContext.isMutable()) {
            this.reqAuthnContext.makeImmutable();
         }

         if (this.scoping != null && this.scoping.isMutable()) {
            this.scoping.makeImmutable();
         }

         this.isMutable = false;
      }
   }

   @Override
   protected void parseDOMElement(Element element) throws SAML2Exception {
      AssertionFactory assertionFactory = AssertionFactory.getInstance();
      ProtocolFactory protoFactory = ProtocolFactory.getInstance();
      this.requestId = element.getAttribute("ID");
      validateID(this.requestId);
      this.version = element.getAttribute("Version");
      validateVersion(this.version);
      String issueInstantStr = element.getAttribute("IssueInstant");
      this.validateIssueInstant(issueInstantStr);
      this.destinationURI = element.getAttribute("Destination");
      this.consent = element.getAttribute("Consent");
      NodeList nList = element.getChildNodes();
      if (nList != null && nList.getLength() > 0) {
         for (int i = 0; i < nList.getLength(); i++) {
            Node childNode = nList.item(i);
            String cName = childNode.getLocalName();
            if (cName != null) {
               if (cName.equals("Issuer")) {
                  this.validateIssuer();
                  this.nameID = assertionFactory.createIssuer((Element)childNode);
               } else if (cName.equals("Signature")) {
                  this.validateSignature();
                  this.signatureString = XMLUtils.print(childNode);
                  this.isSigned = true;
               } else if (cName.equals("Extensions")) {
                  this.validateExtensions();
                  this.extensions = protoFactory.createExtensions((Element)childNode);
               } else if (cName.equals("Subject")) {
                  this.validateSubject();
                  this.subject = assertionFactory.createSubject((Element)childNode);
               } else if (cName.equals("NameIDPolicy")) {
                  this.validateNameIDPolicy();
                  this.nameIDPolicy = protoFactory.createNameIDPolicy((Element)childNode);
               } else if (cName.equals("Conditions")) {
                  this.validateConditions();
                  this.conditions = assertionFactory.createConditions((Element)childNode);
               } else if (cName.equals("RequestedAuthnContext")) {
                  this.validateReqAuthnContext();
                  this.reqAuthnContext = protoFactory.createRequestedAuthnContext((Element)childNode);
               } else if (cName.equals("Scoping")) {
                  this.validateScoping();
                  this.scoping = protoFactory.createScoping((Element)childNode);
               }
            }
         }
      }

      String forceAuthnAttr = element.getAttribute("ForceAuthn");
      if (forceAuthnAttr != null && !forceAuthnAttr.isEmpty()) {
         this.forceAuthn = SAML2SDKUtils.booleanValueOf(forceAuthnAttr);
      }

      String isPassiveAttr = element.getAttribute("IsPassive");
      if (isPassiveAttr != null && !isPassiveAttr.isEmpty()) {
         this.isPassive = SAML2SDKUtils.booleanValueOf(isPassiveAttr);
      }

      this.protocolBinding = element.getAttribute("ProtocolBinding");
      String index = element.getAttribute("AssertionConsumerServiceIndex");
      if (index != null && !index.isEmpty()) {
         this.assertionConsumerSvcIndex = Integer.valueOf(index);
         this.validateAssertionConsumerServiceIndex(this.assertionConsumerSvcIndex);
      }

      this.assertionConsumerServiceURL = XMLUtils.unescapeSpecialCharacters(element.getAttribute("AssertionConsumerServiceURL"));
      index = element.getAttribute("AttributeConsumingServiceIndex");
      if (index != null && !index.isEmpty()) {
         this.attrConsumingSvcIndex = Integer.valueOf(index);
         this.validateAttributeConsumingServiceIndex(this.attrConsumingSvcIndex);
      }

      this.providerName = element.getAttribute("ProviderName");
   }

   private void validateAssertionConsumerServiceIndex(Integer assertionConsumerSvcIndex) throws SAML2Exception {
      if (assertionConsumerSvcIndex != null && assertionConsumerSvcIndex < 0 && assertionConsumerSvcIndex > 65535) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("invalidAssertionConsumerIndex"));
      }
   }

   private void validateAttributeConsumingServiceIndex(Integer attrConsumingSvcIndex) throws SAML2Exception {
      if (attrConsumingSvcIndex != null && attrConsumingSvcIndex < 0 && attrConsumingSvcIndex > 65535) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("invalidAttributeConsumingSvcIdx"));
      }
   }

   private void validateIssuer() throws SAML2Exception {
      if (this.nameID != null) {
         SAML2SDKUtils.DEBUG.message("Request has too many Issuer Element");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }

      if (this.signatureString != null
         || this.extensions != null
         || this.subject != null
         || this.nameIDPolicy != null
         || this.conditions != null
         || this.reqAuthnContext != null
         || this.scoping != null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("Issuer Element should be the first element in the Request");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }
   }

   private void validateSignature() throws SAML2Exception {
      if (this.signatureString != null) {
         SAML2SDKUtils.DEBUG.message("Request has too many Signature Elements");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }

      if (this.extensions != null
         || this.subject != null
         || this.nameIDPolicy != null
         || this.conditions != null
         || this.reqAuthnContext != null
         || this.scoping != null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("Signature should be the second element in the Request");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }
   }

   private void validateExtensions() throws SAML2Exception {
      if (this.extensions != null) {
         SAML2SDKUtils.DEBUG.message("Request has too many Extension Elements");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }

      if (this.subject != null || this.nameIDPolicy != null || this.conditions != null || this.reqAuthnContext != null || this.scoping != null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("Extensions should be the third element in the Request");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }
   }

   private void validateSubject() throws SAML2Exception {
      if (this.subject != null) {
         SAML2SDKUtils.DEBUG.message("Request has too many Subject Elements");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }

      if (this.nameIDPolicy != null || this.conditions != null || this.reqAuthnContext != null || this.scoping != null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("Subject should be the fourth element in the Request");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }
   }

   private void validateNameIDPolicy() throws SAML2Exception {
      if (this.nameIDPolicy != null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("Request has too many NameIDPolicy Elements");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      } else if (this.conditions != null || this.reqAuthnContext != null || this.scoping != null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("Subject should be the fourth element in the Request");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }
   }

   private void validateConditions() throws SAML2Exception {
      if (this.conditions != null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("Request has too many Conditions Elements");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      } else if (this.reqAuthnContext != null || this.scoping != null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("Conditions should be the fifth element in the Request");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }
   }

   private void validateReqAuthnContext() throws SAML2Exception {
      if (this.reqAuthnContext != null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("Request has too many RequestedAuthnContext Elements");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      } else if (this.scoping != null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("RequestedAuthnContext should be the sixth element in the Request");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }
   }

   private void validateScoping() throws SAML2Exception {
      if (this.scoping != null) {
         SAML2SDKUtils.DEBUG.message("Request has too many Scoping Elements");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("schemaViolation"));
      }
   }
}
