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

import com.sun.identity.saml2.assertion.Assertion;
import com.sun.identity.saml2.assertion.AssertionFactory;
import com.sun.identity.saml2.assertion.EncryptedAssertion;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2SDKUtils;
import com.sun.identity.saml2.protocol.ProtocolFactory;
import com.sun.identity.saml2.protocol.Response;
import com.sun.identity.shared.DateUtils;
import com.sun.identity.shared.xml.XMLUtils;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ResponseImpl extends StatusResponseImpl implements Response {
   private List<Assertion> assertions;
   private List<EncryptedAssertion> encAssertions;

   private void parseElement(Element element, boolean makeImmutable) throws SAML2Exception {
      if (element == null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement: element input is null.");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("nullInput"));
      } else {
         String tag = element.getLocalName();
         if (tag != null && tag.equals("Response")) {
            NamedNodeMap atts = element.getAttributes();
            if (atts != null) {
               int length = atts.getLength();

               for (int i = 0; i < length; i++) {
                  Attr attr = (Attr)atts.item(i);
                  String attrName = attr.getName();
                  String attrValue = attr.getValue().trim();
                  if (attrName.equals("ID")) {
                     this.responseId = attrValue;
                  } else if (attrName.equals("InResponseTo")) {
                     this.inResponseTo = attrValue;
                  } else if (attrName.equals("Version")) {
                     this.version = attrValue;
                  } else if (attrName.equals("IssueInstant")) {
                     try {
                        this.issueInstant = DateUtils.stringToDate(attrValue);
                     } catch (ParseException pe) {
                        throw new SAML2Exception(pe.getMessage());
                     }
                  } else if (attrName.equals("Destination")) {
                     this.destination = attrValue;
                  } else if (attrName.equals("Consent")) {
                     this.consent = attrValue;
                  }
               }
            }

            NodeList nl = element.getChildNodes();
            int length = nl.getLength();

            for (int i = 0; i < length; i++) {
               Node child = nl.item(i);
               String childName;
               if ((childName = child.getLocalName()) != null) {
                  if (childName.equals("Issuer")) {
                     if (this.issuer != null) {
                        if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                           SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement: included more than one Issuer.");
                        }

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

                     if (this.signatureString != null
                        || this.extensions != null
                        || this.status != null
                        || this.assertions != null
                        || this.encAssertions != null) {
                        if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                           SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement:wrong sequence.");
                        }

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

                     this.issuer = AssertionFactory.getInstance().createIssuer((Element)child);
                  } else if (childName.equals("Signature")) {
                     if (this.signatureString != null) {
                        if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                           SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement:included more than one Signature.");
                        }

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

                     if (this.extensions != null || this.status != null || this.assertions != null || this.encAssertions != null) {
                        if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                           SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement:wrong sequence.");
                        }

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

                     this.signatureString = XMLUtils.print(child, "UTF-8");
                     this.isSigned = true;
                  } else if (childName.equals("Extensions")) {
                     if (this.extensions != null) {
                        if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                           SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement:included more than one Extensions.");
                        }

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

                     if (this.status != null || this.assertions != null || this.encAssertions != null) {
                        if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                           SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement:wrong sequence.");
                        }

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

                     this.extensions = ProtocolFactory.getInstance().createExtensions((Element)child);
                  } else if (childName.equals("Status")) {
                     if (this.status != null) {
                        if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                           SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement: included more than one Status.");
                        }

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

                     if (this.assertions != null || this.encAssertions != null) {
                        if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                           SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement:wrong sequence.");
                        }

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

                     this.status = ProtocolFactory.getInstance().createStatus((Element)child);
                  } else if (childName.equals("Assertion")) {
                     if (this.assertions == null) {
                        this.assertions = new ArrayList<>();
                     }

                     this.assertions.add(AssertionFactory.getInstance().createAssertion((Element)child));
                  } else {
                     if (!childName.equals("EncryptedAssertion")) {
                        if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                           SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement: Invalid element:" + childName);
                        }

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

                     if (this.encAssertions == null) {
                        this.encAssertions = new ArrayList<>();
                     }

                     this.encAssertions.add(AssertionFactory.getInstance().createEncryptedAssertion((Element)child));
                  }
               }
            }

            super.validateData();
            if (makeImmutable) {
               if (this.assertions != null) {
                  for (Assertion assertion : this.assertions) {
                     assertion.makeImmutable();
                  }

                  this.assertions = Collections.unmodifiableList(this.assertions);
               }

               if (this.encAssertions != null) {
                  this.encAssertions = Collections.unmodifiableList(this.encAssertions);
               }

               this.isMutable = false;
            }
         } else {
            if (SAML2SDKUtils.DEBUG.messageEnabled()) {
               SAML2SDKUtils.DEBUG.message("ResponseImpl.parseElement: not Response.");
            }

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

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

   public ResponseImpl(Element element) throws SAML2Exception {
      this.parseElement(element, true);
      if (this.isSigned) {
         this.signedXMLString = XMLUtils.print(element, "UTF-8");
      }
   }

   public ResponseImpl(String xmlString) throws SAML2Exception {
      this(xmlString, true);
   }

   public ResponseImpl(String xmlString, boolean makeImmutable) throws SAML2Exception {
      this.isMutable = !makeImmutable;
      Document doc = XMLUtils.toDOMDocument(xmlString, SAML2SDKUtils.DEBUG);
      if (doc == null) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("errorObtainingElement"));
      }

      this.parseElement(doc.getDocumentElement(), makeImmutable);
      if (this.isSigned) {
         this.signedXMLString = xmlString;
      }
   }

   @Override
   public List<Assertion> getAssertion() {
      return this.assertions;
   }

   @Override
   public void setAssertion(List<Assertion> value) throws SAML2Exception {
      if (this.isMutable) {
         this.assertions = value;
      } else {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }
   }

   @Override
   public List<EncryptedAssertion> getEncryptedAssertion() {
      return this.encAssertions;
   }

   @Override
   public void setEncryptedAssertion(List<EncryptedAssertion> value) throws SAML2Exception {
      if (this.isMutable) {
         this.encAssertions = value;
      } else {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }
   }

   @Override
   public void makeImmutable() {
      if (this.isMutable) {
         if (this.assertions != null) {
            for (Assertion assertion : this.assertions) {
               assertion.makeImmutable();
            }

            this.assertions = Collections.unmodifiableList(this.assertions);
         }

         if (this.encAssertions != null) {
            this.encAssertions = Collections.unmodifiableList(this.encAssertions);
         }

         super.makeImmutable();
      }
   }

   @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();
      StringBuilder result = new StringBuilder(1000);
      String prefix = "";
      String uri = "";
      if (includeNSPrefix) {
         prefix = "samlp:";
      }

      if (declareNS) {
         uri = " xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"";
      }

      result.append("<").append(prefix).append("Response").append(uri).append(" ID=\"").append(this.responseId).append("\"");
      if (this.inResponseTo != null && !this.inResponseTo.trim().isEmpty()) {
         result.append(" InResponseTo=\"").append(this.inResponseTo).append("\"");
      }

      result.append(" Version=\"")
         .append(this.version)
         .append("\"")
         .append(" IssueInstant=\"")
         .append(DateUtils.toUTCDateFormat(this.issueInstant))
         .append("\"");
      if (this.destination != null && !this.destination.trim().isEmpty()) {
         result.append(" Destination=\"").append(this.destination).append("\"");
      }

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

      result.append(">");
      if (this.issuer != null) {
         result.append(this.issuer.toXMLString(includeNSPrefix, declareNS));
      }

      if (this.signatureString != null) {
         result.append(this.signatureString);
      }

      if (this.extensions != null) {
         result.append(this.extensions.toXMLString(includeNSPrefix, declareNS));
      }

      result.append(this.status.toXMLString(includeNSPrefix, declareNS));
      if (this.assertions != null) {
         for (Assertion assertion : this.assertions) {
            result.append(assertion.toXMLString(includeNSPrefix, declareNS));
         }
      }

      if (this.encAssertions != null) {
         for (EncryptedAssertion encAssertion : this.encAssertions) {
            result.append(encAssertion.toXMLString(includeNSPrefix, declareNS));
         }
      }

      result.append("</").append(prefix).append("Response>");
      return result.toString();
   }
}
