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

import com.sun.identity.saml2.assertion.AssertionFactory;
import com.sun.identity.saml2.assertion.AudienceRestriction;
import com.sun.identity.saml2.assertion.Condition;
import com.sun.identity.saml2.assertion.Conditions;
import com.sun.identity.saml2.assertion.OneTimeUse;
import com.sun.identity.saml2.assertion.ProxyRestriction;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2SDKUtils;
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 ConditionsImpl implements Conditions {
   private static final String CONDITIONS_ELEMENT = "Conditions";
   private static final String CONDITION_ELEMENT = "Condition";
   private static final String ONETIMEUSE_ELEMENT = "OneTimeUse";
   private static final String AUDIENCE_RESTRICTION_ELEMENT = "AudienceRestriction";
   private static final String PROXY_RESTRICTION_ELEMENT = "ProxyRestriction";
   private static final String NOT_BEFORE_ATTR = "NotBefore";
   private static final String NOT_ON_OR_AFTER_ATTR = "NotOnOrAfter";
   private Date notOnOrAfter;
   private List<Condition> conditions = new ArrayList<>();
   private List<AudienceRestriction> audienceRestrictions = new ArrayList<>();
   private List<OneTimeUse> oneTimeUses = new ArrayList<>();
   private List<ProxyRestriction> proxyRestrictions = new ArrayList<>();
   private Date notBefore;
   private boolean isMutable = true;

   public ConditionsImpl() {
   }

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

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

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

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

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

      String attrValue = element.getAttribute("NotBefore");
      if (attrValue != null && !attrValue.trim().isEmpty()) {
         try {
            this.notBefore = DateUtils.stringToDate(attrValue);
         } catch (ParseException pe) {
            SAML2SDKUtils.DEBUG.error("ConditionsImpl.processElement(): invalid NotBefore attribute");
            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("invalid_date_format"));
         }
      }

      attrValue = element.getAttribute("NotOnOrAfter");
      if (attrValue != null && !attrValue.trim().isEmpty()) {
         try {
            this.notOnOrAfter = DateUtils.stringToDate(attrValue);
         } catch (ParseException pe) {
            SAML2SDKUtils.DEBUG.error("ConditionsImpl.processElement(): invalid NotOnORAfter attribute");
            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("invalid_date_format"));
         }
      }

      NodeList nodes = element.getChildNodes();
      int numOfNodes = nodes.getLength();

      for (int nextElem = 0; nextElem < numOfNodes; nextElem++) {
         Node child = nodes.item(nextElem);
         if (child.getNodeType() == 1) {
            String childName = child.getLocalName();
            if (childName != null) {
               switch (childName) {
                  case "Condition":
                     this.conditions.add(AssertionFactory.getInstance().createCondition((Element)child));
                     break;
                  case "AudienceRestriction":
                     this.audienceRestrictions.add(AssertionFactory.getInstance().createAudienceRestriction((Element)child));
                     break;
                  case "OneTimeUse":
                     this.oneTimeUses.add(AssertionFactory.getInstance().createOneTimeUse((Element)child));
                     break;
                  case "ProxyRestriction":
                     this.proxyRestrictions.add(AssertionFactory.getInstance().createProxyRestriction((Element)child));
                     break;
                  default:
                     SAML2SDKUtils.DEBUG.error("ConditionsImpl.processElement(): unexpected subelement " + childName);
                     throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("unexpected_subelement"));
               }
            }
         }
      }
   }

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

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

      this.notOnOrAfter = value;
   }

   @Override
   public List<Condition> getConditions() {
      return this.conditions;
   }

   @Override
   public List<AudienceRestriction> getAudienceRestrictions() {
      return this.audienceRestrictions;
   }

   @Override
   public List<OneTimeUse> getOneTimeUses() {
      return this.oneTimeUses;
   }

   @Override
   public List<ProxyRestriction> getProxyRestrictions() {
      return this.proxyRestrictions;
   }

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

      this.conditions = conditions;
   }

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

      this.audienceRestrictions = ars;
   }

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

      this.oneTimeUses = oneTimeUses;
   }

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

      this.proxyRestrictions = prs;
   }

   @Override
   public Date getNotBefore() {
      return this.notBefore;
   }

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

      this.notBefore = value;
   }

   @Override
   public boolean checkDateValidity(long someTime) {
      if (this.notBefore == null) {
         return this.notOnOrAfter == null ? true : someTime < this.notOnOrAfter.getTime();
      } else {
         return this.notOnOrAfter == null
            ? someTime >= this.notBefore.getTime()
            : someTime >= this.notBefore.getTime() && someTime < this.notOnOrAfter.getTime();
      }
   }

   @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("Conditions").append(ns);
      if (this.notBefore != null) {
         String str = DateUtils.toUTCDateFormat(this.notBefore);
         sb.append(" ").append("NotBefore").append("=\"").append(str).append("\"");
      }

      if (this.notOnOrAfter != null) {
         String str = DateUtils.toUTCDateFormat(this.notOnOrAfter);
         sb.append(" ").append("NotOnOrAfter").append("=\"").append(str).append("\"");
      }

      sb.append(">\n");
      if (this.conditions != null) {
         for (Condition condition : this.conditions) {
            sb.append(condition.toXMLString(includeNSPrefix, false));
         }
      }

      if (this.audienceRestrictions != null) {
         for (AudienceRestriction ar : this.audienceRestrictions) {
            sb.append(ar.toXMLString(includeNSPrefix, false));
         }
      }

      if (this.oneTimeUses != null) {
         for (OneTimeUse otu : this.oneTimeUses) {
            sb.append(otu.toXMLString(includeNSPrefix, false));
         }
      }

      if (this.proxyRestrictions != null) {
         for (ProxyRestriction pr : this.proxyRestrictions) {
            sb.append(pr.toXMLString(includeNSPrefix, false));
         }
      }

      sb.append("</").append(appendNS).append("Conditions").append(">\n");
      return sb.toString();
   }

   @Override
   public final void makeImmutable() {
      if (this.isMutable) {
         if (this.conditions != null) {
            for (Condition condition : this.conditions) {
               condition.makeImmutable();
            }

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

         if (this.audienceRestrictions != null) {
            for (AudienceRestriction ar : this.audienceRestrictions) {
               ar.makeImmutable();
            }

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

         if (this.oneTimeUses != null) {
            for (OneTimeUse oneTimeUse : this.oneTimeUses) {
               oneTimeUse.makeImmutable();
            }

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

         if (this.proxyRestrictions != null) {
            for (ProxyRestriction pr : this.proxyRestrictions) {
               pr.makeImmutable();
            }

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

         this.isMutable = false;
      }
   }

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