/**
 *
 * Copyright 2015 Florian Schmaus.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jivesoftware.smack.packet;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.jivesoftware.smack.util.MultiMap;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;

import org.jxmpp.util.XmppStringUtils;

/**
 * An {@link ExtensionElement} modeling the often required and used XML features when using XMPP. It
 * is therefore suitable for most use cases. Use
 * {@link StandardExtensionElement#builder(String, String)} to build these elements.
 * <p>
 * Note the this is only meant as catch-all if no particular extension element provider is
 * registered. Protocol implementations should prefer to model their own extension elements tailored
 * to their use cases.
 * </p>
 *
 * @since 4.2
 * @author Florian Schmaus
 */
public final class StandardExtensionElement implements ExtensionElement {

    private final String name;
    private final String namespace;
    private final Map<String, String> attributes;
    private final String text;
    private final MultiMap<String, StandardExtensionElement> elements;

    private XmlStringBuilder xmlCache;

    /**
     * Constructs a new extension element with the given name and namespace and nothing else.
     * <p>
     * This is meant to construct extension elements used as simple flags in Stanzas.
     * <p>
     *
     * @param name the name of the extension element.
     * @param namespace the namespace of the extension element.
     */
    public StandardExtensionElement(String name, String namespace) {
        this(name, namespace, null, null, null);
    }

    private StandardExtensionElement(String name, String namespace, Map<String, String> attributes, String text,
                    MultiMap<String, StandardExtensionElement> elements) {
        this.name = StringUtils.requireNotNullOrEmpty(name, "Name must not be null or empty");
        this.namespace = StringUtils.requireNotNullOrEmpty(namespace, "Namespace must not be null or empty");
        if (attributes == null) {
            this.attributes = Collections.emptyMap();
        }
        else {
            this.attributes = attributes;
        }
        this.text = text;
        this.elements = elements;
    }

    @Override
    public String getElementName() {
        return name;
    }

    @Override
    public String getNamespace() {
        return namespace;
    }

    public String getAttributeValue(String attribute) {
        return attributes.get(attribute);
    }

    public Map<String, String> getAttributes() {
        return Collections.unmodifiableMap(attributes);
    }

    public StandardExtensionElement getFirstElement(String element, String namespace) {
        if (elements == null) {
            return null;
        }
        String key = XmppStringUtils.generateKey(element, namespace);
        return elements.getFirst(key);
    }

    public StandardExtensionElement getFirstElement(String element) {
        return getFirstElement(element, namespace);
    }

    public List<StandardExtensionElement> getElements(String element, String namespace) {
        if (elements == null) {
            return null;
        }
        String key = XmppStringUtils.generateKey(element, namespace);
        return elements.getAll(key);
    }

    public List<StandardExtensionElement> getElements(String element) {
        return getElements(element, namespace);
    }

    public List<StandardExtensionElement> getElements() {
        if (elements == null) {
            return Collections.emptyList();
        }
        return elements.values();
    }

    public String getText() {
        return text;
    }

    @Override
    public XmlStringBuilder toXML() {
        return toXML(null);
    }

    public XmlStringBuilder toXML(String enclosingNamespace) {
        if (xmlCache != null) {
            return xmlCache;
        }
        XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
        for (Map.Entry<String, String> entry : attributes.entrySet()) {
            xml.attribute(entry.getKey(), entry.getValue());
        }
        xml.rightAngleBracket();

        xml.optEscape(text);

        if (elements != null) {
            for (Map.Entry<String, StandardExtensionElement> entry : elements.entrySet()) {
                xml.append(entry.getValue().toXML(getNamespace()));
            }
        }

        xml.closeElement(this);
        xmlCache = xml;
        return xml;
    }

    public static Builder builder(String name, String namespace) {
        return new Builder(name, namespace);
    }

    public static final class Builder {
        private final String name;
        private final String namespace;

        private Map<String, String> attributes;
        private String text;
        private MultiMap<String, StandardExtensionElement> elements;

        private Builder(String name, String namespace) {
            this.name = name;
            this.namespace = namespace;
        }

        public Builder addAttribute(String name, String value) {
            StringUtils.requireNotNullOrEmpty(name, "Attribute name must be set");
            Objects.requireNonNull(value, "Attribute value must be not null");
            if (attributes == null) {
                attributes = new LinkedHashMap<>();
            }
            attributes.put(name, value);
            return this;
        }

        public Builder addAttributes(Map<String, String> attributes) {
            if (this.attributes == null) {
                this.attributes = new LinkedHashMap<>(attributes.size());
            }
            this.attributes.putAll(attributes);
            return this;
        }

        public Builder setText(String text) {
            this.text = Objects.requireNonNull(text, "Text must be not null");
            return this;
        }

        public Builder addElement(StandardExtensionElement element) {
            Objects.requireNonNull(element, "Element must not be null");
            if (elements == null) {
                elements = new MultiMap<>();
            }
            String key = XmppStringUtils.generateKey(element.getElementName(), element.getNamespace());
            elements.put(key, element);
            return this;
        }

        public Builder addElement(String name, String textValue) {
            StandardExtensionElement element = StandardExtensionElement.builder(name, this.namespace).setText(
                            textValue).build();
            return addElement(element);
        }

        public StandardExtensionElement build() {
            return new StandardExtensionElement(name, namespace, attributes, text, elements);
        }
    }
}
