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.Subject;
import com.sun.identity.saml2.assertion.SubjectConfirmation;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2SDKUtils;
import com.sun.identity.shared.xml.XMLUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class SubjectImpl implements Subject {
   private static final String SUBJECT_ELEMENT = "Subject";
   private static final String SUBJECT_CONFIRMATION_ELEMENT = "SubjectConfirmation";
   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 List<SubjectConfirmation> subjectConfirmations = new ArrayList<>();
   private BaseID baseId;
   private NameID nameId;
   private EncryptedID encryptedId;
   private boolean isMutable = true;

   public SubjectImpl() {
   }

   public SubjectImpl(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("SubjectImpl.processElement(): invalid XML input");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("errorObtainingElement"));
      }
   }

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

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

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

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

      NodeList nodes = element.getChildNodes();
      int numOfNodes = nodes.getLength();
      if (numOfNodes < 1) {
         SAML2SDKUtils.DEBUG.error("SubjectImpl.processElement(): subject has no subelements");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("missing_subelements"));
      }

      int nextElem = 0;

      Node child;
      for (child = nodes.item(nextElem); child.getNodeType() != 1; child = nodes.item(nextElem)) {
         if (++nextElem >= numOfNodes) {
            SAML2SDKUtils.DEBUG.error("SubjectImpl.processElement(): subject has no subelements");
            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("missing_subelements"));
         }
      }

      String childName = child.getLocalName();
      if (childName != null) {
         switch (childName) {
            case "SubjectConfirmation":
               this.subjectConfirmations.add(AssertionFactory.getInstance().createSubjectConfirmation((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("SubjectImpl.processElement(): unexpected subelement " + childName);
               throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("unexpected_subelement"));
         }
      }

      if (++nextElem < numOfNodes) {
         for (; nextElem < numOfNodes; nextElem++) {
            child = nodes.item(nextElem);
            if (child.getNodeType() == 1) {
               childName = child.getLocalName();
               if (childName != null) {
                  if (!childName.equals("SubjectConfirmation")) {
                     SAML2SDKUtils.DEBUG.error("SubjectImpl.processElement(): unexpected subelement " + childName);
                     throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("unexpected_subelement"));
                  }

                  this.subjectConfirmations.add(AssertionFactory.getInstance().createSubjectConfirmation((Element)child));
               }
            }
         }
      }
   }

   @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 List<SubjectConfirmation> getSubjectConfirmation() {
      return this.subjectConfirmations;
   }

   @Override
   public void setSubjectConfirmation(List<SubjectConfirmation> confirmations) throws SAML2Exception {
      if (!this.isMutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.subjectConfirmations = confirmations;
   }

   @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 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("Subject").append(ns).append(">\n");
      boolean idFound = false;
      if (this.baseId != null) {
         sb.append(this.baseId.toXMLString(includeNSPrefix, false));
         idFound = true;
      }

      if (this.nameId != null) {
         if (idFound) {
            SAML2SDKUtils.DEBUG.error("SubjectImpl.toXMLString(): more than one types of id specified");
            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("too_many_ids_specified"));
         }

         sb.append(this.nameId.toXMLString(includeNSPrefix, false));
         idFound = true;
      }

      if (this.encryptedId != null) {
         if (idFound) {
            SAML2SDKUtils.DEBUG.error("SubjectImpl.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));
         idFound = true;
      }

      int length = this.subjectConfirmations.size();
      if (length == 0) {
         if (!idFound) {
            SAML2SDKUtils.DEBUG.error("SubjectImpl.toXMLString(): Need at least one id or one subject confirmation in a subject");
            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("need_at_least_one_id_or_on_SubjectConfirmation"));
         }
      } else {
         for (int i = 0; i < length; i++) {
            SubjectConfirmation sc = this.subjectConfirmations.get(i);
            sb.append(sc.toXMLString(includeNSPrefix, false));
         }
      }

      sb.append("</").append(appendNS).append("Subject").append(">");
      return sb.toString();
   }

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

   @Override
   public void makeImmutable() {
      if (this.isMutable) {
         if (this.subjectConfirmations != null) {
            for (SubjectConfirmation subjectConfirmation : this.subjectConfirmations) {
               subjectConfirmation.makeImmutable();
            }

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

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

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

         this.isMutable = false;
      }
   }

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