package com.sun.identity.saml2.protocol.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.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2SDKUtils;
import com.sun.identity.saml2.protocol.LogoutRequest;
import com.sun.identity.saml2.protocol.ProtocolFactory;
import com.sun.identity.saml2.protocol.SessionIndex;
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.Date;
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 LogoutRequestImpl extends RequestAbstractImpl implements LogoutRequest {
   private String reason;
   private Date notOnOrAfter;
   private EncryptedID encryptedId;
   private NameID nameId;
   private BaseID baseId;
   private List<String> sessionIndexList;

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

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

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

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

   @Override
   public Date getNotOnOrAfter() {
      return this.notOnOrAfter;
   }

   @Override
   public void setNotOnOrAfter(Date value) throws SAML2Exception {
      if (this.isMutable) {
         this.notOnOrAfter = value;
      } else {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }
   }

   @Override
   public String getReason() {
      return this.reason;
   }

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

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

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

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

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

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

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

   @Override
   public List<String> getSessionIndex() {
      return this.sessionIndexList;
   }

   @Override
   public void setSessionIndex(List<String> sessionIndexList) throws SAML2Exception {
      if (this.isMutable) {
         this.sessionIndexList = sessionIndexList;
      } else {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }
   }

   @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 xmlString = new StringBuilder(1000);
      xmlString.append("<");
      if (includeNSPrefix) {
         xmlString.append("samlp:");
      }

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

      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.notOnOrAfter != null) {
         xmlString.append(" ").append("NotOnOrAfter").append("=").append("\"").append(DateUtils.toUTCDateFormat(this.notOnOrAfter)).append("\"");
      }

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

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

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

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

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

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

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

      if (this.sessionIndexList != null && !this.sessionIndexList.isEmpty()) {
         for (String s : this.sessionIndexList) {
            ProtocolFactory protoFactory = ProtocolFactory.getInstance();
            SessionIndex sIndex = protoFactory.createSessionIndex(s);
            xmlString.append(sIndex.toXMLString(includeNSPrefix, declareNS));
         }
      }

      xmlString.append("</samlp:").append("LogoutRequest").append(">");
      return xmlString.toString();
   }

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

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

         this.isMutable = false;
      }
   }

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

   private void parseElement(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");
      String notOnOrAfterStr = element.getAttribute("NotOnOrAfter");
      this.validateNotOnOrAfterStr(notOnOrAfterStr);
      this.reason = element.getAttribute("Reason");
      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.nameID = assertionFactory.createIssuer((Element)childNode);
               } else if (cName.equals("Signature")) {
                  this.signatureString = XMLUtils.print(childNode);
                  this.isSigned = true;
               } else if (cName.equals("Extensions")) {
                  this.extensions = protoFactory.createExtensions((Element)childNode);
               } else if (cName.equals("BaseID")) {
                  this.baseId = assertionFactory.createBaseID((Element)childNode);
               } else if (cName.equals("NameID")) {
                  this.nameId = assertionFactory.createNameID((Element)childNode);
               } else if (cName.equals("EncryptedID")) {
                  this.encryptedId = assertionFactory.createEncryptedID((Element)childNode);
               } else if (cName.equals("SessionIndex")) {
                  if (this.sessionIndexList == null || this.sessionIndexList.isEmpty()) {
                     this.sessionIndexList = new ArrayList<>();
                  }

                  String sessionIndexStr = XMLUtils.getElementString((Element)childNode);
                  this.sessionIndexList.add(sessionIndexStr);
               }
            }
         }

         this.validateBaseIDorNameIDorEncryptedID();
         if (this.sessionIndexList != null && !this.sessionIndexList.isEmpty()) {
            this.sessionIndexList = Collections.unmodifiableList(this.sessionIndexList);
         }
      }
   }

   private void validateNotOnOrAfterStr(String notOnOrAfterStr) throws SAML2Exception {
      if (notOnOrAfterStr != null && !notOnOrAfterStr.isEmpty()) {
         try {
            this.notOnOrAfter = DateUtils.stringToDate(notOnOrAfterStr);
         } catch (ParseException e) {
            SAML2SDKUtils.DEBUG.message("Error parsing NotOnOrAfterStr", e);
            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("incorrectNotOnOrAfterStr"));
         }
      }
   }

   private void validateBaseIDorNameIDorEncryptedID() throws SAML2Exception {
      if (this.baseId == null && this.nameId == null && this.encryptedId == null) {
         SAML2SDKUtils.DEBUG.message("BaseID ,NameID ,EncryptedID NULL ");
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("missingBaseIDorNameIDorEncryptedID"));
      }
   }
}
