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

import com.sun.identity.saml2.assertion.AssertionFactory;
import com.sun.identity.saml2.assertion.BaseID;
import com.sun.identity.saml2.assertion.EncryptedID;
import com.sun.identity.saml2.assertion.NameID;
import com.sun.identity.saml2.assertion.SubjectConfirmation;
import com.sun.identity.saml2.assertion.SubjectConfirmationData;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2SDKUtils;
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 SubjectConfirmationImpl implements SubjectConfirmation {
   private static final String SUBJECT_CONFIRMATION_ELEMENT = "SubjectConfirmation";
   private static final String SUBJECT_CONFIRMATION_DATA_ELEMENT = "SubjectConfirmationData";
   private static final String BASE_ID_ELEMENT = "BaseID";
   private static final String NAME_ID_ELEMENT = "NameID";
   private static final String ENCRYPTED_ID_ELEMENT = "EncryptedID";
   private static final String METHOD_ATTR = "Method";
   private SubjectConfirmationData subjectConfirmationData;
   private BaseID baseId;
   private NameID nameId;
   private EncryptedID encryptedId;
   private String method;
   private boolean isMutable = true;

   public SubjectConfirmationImpl() {
   }

   public SubjectConfirmationImpl(String xml) throws SAML2Exception {
      Document document = XMLUtils.toDOMDocument(xml, SAML2SDKUtils.DEBUG);
      if (document != null) {
         Element rootElement = document.getDocumentElement();
         this.processElement(rootElement);
         this.makeImmutable();
      } else {
         SAML2SDKUtils.DEBUG.error("SubjectConfirmationImpl.processElement(): invalid XML input");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("errorObtainingElement"));
      }
   }

   public SubjectConfirmationImpl(Element element) throws SAML2Exception {
      this.processElement(element);
      this.makeImmutable();
   }

   private void processElement(Element element) throws SAML2Exception {
      if (element == null) {
         SAML2SDKUtils.DEBUG.error("SubjectConfirmationImpl.processElement(): invalid root element");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("invalid_element"));
      }

      String elemName = element.getLocalName();
      if (elemName == null) {
         SAML2SDKUtils.DEBUG.error("SubjectConfirmationImpl.processElement(): local name missing");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("missing_local_name"));
      }

      if (!elemName.equals("SubjectConfirmation")) {
         SAML2SDKUtils.DEBUG.error("SubjectConfirmationImpl.processElement(): invalid local name " + elemName);
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("invalid_local_name"));
      }

      String attrValue = element.getAttribute("Method");
      if (attrValue != null && !attrValue.isEmpty()) {
         this.method = attrValue;
         NodeList nodes = element.getChildNodes();
         int numOfNodes = nodes.getLength();
         if (numOfNodes >= 1) {
            int nextElem = 0;

            Node child;
            for (child = nodes.item(nextElem); child.getNodeType() != 1; child = nodes.item(nextElem)) {
               if (++nextElem >= numOfNodes) {
                  return;
               }
            }

            String childName = child.getLocalName();
            if (childName != null) {
               switch (childName) {
                  case "SubjectConfirmationData":
                     this.subjectConfirmationData = AssertionFactory.getInstance().createSubjectConfirmationData((Element)child);
                     break;
                  case "BaseID":
                     this.baseId = AssertionFactory.getInstance().createBaseID((Element)child);
                     break;
                  case "NameID":
                     this.nameId = AssertionFactory.getInstance().createNameID((Element)child);
                     break;
                  case "EncryptedID":
                     this.encryptedId = AssertionFactory.getInstance().createEncryptedID((Element)child);
                     break;
                  default:
                     SAML2SDKUtils.DEBUG.error("SubjectConfirmationImpl.processElement(): unexpected subelement " + childName);
                     throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("unexpected_subelement"));
               }
            }
         }
      } else {
         SAML2SDKUtils.DEBUG.error("SubjectConfirmationImpl.processElement(): method missing");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("missing_confirmation_method"));
      }
   }

   @Override
   public EncryptedID getEncryptedID() {
      return this.encryptedId;
   }

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

      this.encryptedId = value;
   }

   @Override
   public NameID getNameID() {
      return this.nameId;
   }

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

      this.nameId = value;
   }

   @Override
   public SubjectConfirmationData getSubjectConfirmationData() {
      return this.subjectConfirmationData;
   }

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

      this.subjectConfirmationData = value;
   }

   @Override
   public BaseID getBaseID() {
      return this.baseId;
   }

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

      this.baseId = value;
   }

   @Override
   public String getMethod() {
      return this.method;
   }

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

      this.method = value;
   }

   @Override
   public String toXMLString(boolean includeNSPrefix, boolean declareNS) throws SAML2Exception {
      StringBuilder sb = new StringBuilder(2000);
      String ns = "";
      String appendNS = "";
      if (declareNS) {
         ns = " xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\"";
      }

      if (includeNSPrefix) {
         appendNS = "saml:";
      }

      sb.append("<").append(appendNS).append("SubjectConfirmation").append(ns);
      if (this.method != null && !this.method.trim().isEmpty()) {
         sb.append(" ").append("Method").append("=\"").append(this.method).append("\"").append(">\n");
         if (this.subjectConfirmationData == null) {
            if (this.baseId != null || this.nameId != null || this.encryptedId != null) {
               if (this.baseId != null && this.nameId == null && this.encryptedId == null) {
                  sb.append(this.baseId.toXMLString(includeNSPrefix, false));
               } else if (this.nameId != null && this.baseId == null && this.encryptedId == null) {
                  sb.append(this.nameId.toXMLString(includeNSPrefix, false));
               } else {
                  if (this.encryptedId == null || this.baseId != null || this.nameId != null) {
                     SAML2SDKUtils.DEBUG.error("SubjectConfirmationImpl.toXMLString(): more than one types of id specified");
                     throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("too_many_ids_specified"));
                  }

                  sb.append(this.encryptedId.toXMLString(includeNSPrefix, false));
               }
            }
         } else {
            if (this.baseId != null || this.nameId != null || this.encryptedId != null) {
               SAML2SDKUtils.DEBUG.error("SubjectConfirmationImpl.toXMLString(): Only one of the ids or confirmation can be specified");
               throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("id_and_confirmation_both_specified"));
            }

            sb.append(this.subjectConfirmationData.toXMLString(includeNSPrefix, false));
         }

         sb.append("</").append(appendNS).append("SubjectConfirmation").append(">\n");
         return sb.toString();
      } else {
         SAML2SDKUtils.DEBUG.error("SubjectConfirmationImpl.toXMLString(): method missing");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("missing_confirmation_method"));
      }
   }

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

   @Override
   public final void makeImmutable() {
      if (this.isMutable) {
         if (this.subjectConfirmationData != null) {
            this.subjectConfirmationData.makeImmutable();
         }

         if (this.baseId != null) {
            this.baseId.makeImmutable();
         }

         if (this.nameId != null) {
            this.nameId.makeImmutable();
         }

         this.isMutable = false;
      }
   }

   @Override
   public boolean isMutable() {
      return this.isMutable;
   }
}
