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

import com.sun.identity.saml2.assertion.AssertionFactory;
import com.sun.identity.saml2.assertion.Attribute;
import com.sun.identity.saml2.assertion.EncryptedAttribute;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2SDKUtils;
import com.sun.identity.saml2.xmlenc.EncManager;
import com.sun.identity.shared.xml.XMLUtils;
import java.security.Key;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
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 AttributeImpl implements Attribute {
   private List<String> attrValues;
   private List<String> valueStrings;
   private String name;
   private String nameFormat;
   private String friendlyName;
   private Map<String, String> anyMap;
   private boolean mutable = true;

   private String validateAndGetAttributeValue(String value) throws SAML2Exception {
      Document doc = XMLUtils.toDOMDocument(value, SAML2SDKUtils.DEBUG);
      if (doc == null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("AttributeImpl.validateAttributeValue: could not obtain AttributeValue element.");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("errorObtainingElement"));
      } else {
         Element element = doc.getDocumentElement();
         if (element == null) {
            if (SAML2SDKUtils.DEBUG.messageEnabled()) {
               SAML2SDKUtils.DEBUG.message("AttributeImpl.validateAttributeValue: Input is null.");
            }

            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("nullInput"));
         } else {
            String tag = element.getLocalName();
            if (tag != null && tag.equals("AttributeValue")) {
               return XMLUtils.getChildrenValue(element);
            }

            if (SAML2SDKUtils.DEBUG.messageEnabled()) {
               SAML2SDKUtils.DEBUG.message("AttributeImpl.validateAttributeValue: not AttributeValue.");
            }

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

   private void parseElement(Element element) throws SAML2Exception {
      if (element == null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("AttributeImpl.parseElement: Input is null.");
         }

         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("nullInput"));
      } else {
         String tag = element.getLocalName();
         if (tag != null && tag.equals("Attribute")) {
            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();
                  switch (attrName) {
                     case "Name":
                        this.name = attrValue;
                        break;
                     case "NameFormat":
                        this.nameFormat = attrValue;
                        break;
                     case "FriendlyName":
                        this.friendlyName = attrValue;
                        break;
                     default:
                        if (!attrValue.equals("urn:oasis:names:tc:SAML:2.0:assertion")) {
                           if (this.anyMap == null) {
                              this.anyMap = new HashMap<>();
                           }

                           this.anyMap.put(attrName, 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("AttributeValue")) {
                     if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                        SAML2SDKUtils.DEBUG.message("Attributempl.parseElement: Invalid element:" + childName);
                     }

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

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

                  this.attrValues.add(XMLUtils.print(child));
                  if (this.valueStrings == null) {
                     this.valueStrings = new ArrayList<>();
                  }

                  this.valueStrings.add(XMLUtils.unescapeSpecialCharacters(XMLUtils.getChildrenValue((Element)child)));
               }
            }

            if (this.name != null && !this.name.trim().isEmpty()) {
               if (this.attrValues != null) {
                  this.attrValues = Collections.unmodifiableList(this.attrValues);
               }

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

               if (this.anyMap != null) {
                  this.anyMap = Collections.unmodifiableMap(this.anyMap);
               }

               this.mutable = false;
            } else {
               if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                  SAML2SDKUtils.DEBUG.message("AttributeImpl.parseElement: missing Name attribute.");
               }

               throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("missingAttribute"));
            }
         } else {
            if (SAML2SDKUtils.DEBUG.messageEnabled()) {
               SAML2SDKUtils.DEBUG.message("AttributeImpl.parseElement: not Attribute.");
            }

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

   public AttributeImpl() {
   }

   public AttributeImpl(Element element) throws SAML2Exception {
      this.parseElement(element);
   }

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

      this.parseElement(doc.getDocumentElement());
   }

   @Override
   public final void makeImmutable() {
      if (this.mutable) {
         if (this.attrValues != null) {
            this.attrValues = Collections.unmodifiableList(this.attrValues);
         }

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

         if (this.anyMap != null) {
            this.anyMap = Collections.unmodifiableMap(this.anyMap);
         }

         this.mutable = false;
      }
   }

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

   @Override
   public List<String> getAttributeValue() {
      return this.attrValues;
   }

   @Override
   public void setAttributeValue(List<String> value) throws SAML2Exception {
      if (!this.mutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      if (value != null) {
         this.attrValues = new ArrayList<>();
         this.valueStrings = new ArrayList<>();

         for (String attr : value) {
            this.valueStrings.add(this.validateAndGetAttributeValue(attr));
            this.attrValues.add(attr);
         }
      } else {
         this.attrValues = null;
         this.valueStrings = null;
      }
   }

   @Override
   public List<String> getAttributeValueString() {
      return this.valueStrings;
   }

   @Override
   public void setAttributeValueString(List<String> value) throws SAML2Exception {
      if (!this.mutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      if (value != null) {
         this.attrValues = new ArrayList<>();
         this.valueStrings = new ArrayList<>();

         for (String attr : value) {
            this.attrValues
               .add(
                  "<saml:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">"
                     + XMLUtils.escapeSpecialCharacters(attr)
                     + "</saml:AttributeValue>"
               );
            this.valueStrings.add(attr);
         }
      } else {
         this.attrValues = null;
         this.valueStrings = null;
      }
   }

   @Override
   public String getName() {
      return this.name;
   }

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

      this.name = value;
   }

   @Override
   public String getNameFormat() {
      return this.nameFormat;
   }

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

      this.nameFormat = value;
   }

   @Override
   public String getFriendlyName() {
      return this.friendlyName;
   }

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

      this.friendlyName = value;
   }

   @Override
   public Map<String, String> getAnyAttribute() {
      return this.anyMap;
   }

   @Override
   public void setAnyAttribute(Map<String, String> value) throws SAML2Exception {
      if (!this.mutable) {
         throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("objectImmutable"));
      }

      this.anyMap = value;
   }

   @Override
   public EncryptedAttribute encrypt(Key recipientPublicKey, String dataEncAlgorithm, int dataEncStrength, String recipientEntityID) throws SAML2Exception {
      Element el = EncManager.getEncInstance()
         .encrypt(this.toXMLString(true, true), recipientPublicKey, dataEncAlgorithm, dataEncStrength, recipientEntityID, "EncryptedAttribute");
      return AssertionFactory.getInstance().createEncryptedAttribute(el);
   }

   @Override
   public String toXMLString(boolean includeNS, boolean declareNS) throws SAML2Exception {
      if (this.name != null && !this.name.trim().isEmpty()) {
         StringBuilder result = new StringBuilder(1000);
         String prefix = "";
         String uri = "";
         if (includeNS) {
            prefix = "saml:";
         }

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

         result.append("<").append(prefix).append("Attribute").append(uri).append(" Name=\"").append(this.name).append("\"");
         if (this.nameFormat != null && !this.nameFormat.trim().isEmpty()) {
            result.append(" NameFormat=\"").append(this.nameFormat).append("\"");
         }

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

         if (this.anyMap != null) {
            for (Entry<String, String> e : this.anyMap.entrySet()) {
               String value = e.getValue();
               if (value == null) {
                  value = "";
               }

               result.append(" ").append(e.getKey()).append("=\"").append(value).append("\"");
            }
         }

         result.append(">");
         if (this.attrValues != null) {
            this.attrValues.forEach(result::append);
         }

         result.append("</").append(prefix).append("Attribute>");
         return result.toString();
      } else {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("AttributeImpl.toXMLString: missing Attribute Name.");
         }

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