/*
 * Copyright 2016 Red Hat, Inc.
 *
 * 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.wildfly.extension.batch.jberet;

import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.stream.XMLStreamException;

import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.AttributeParser;
import org.jboss.as.controller.parsing.ParseUtils;
import org.jboss.dmr.ModelNode;
import org.jboss.staxmapper.XMLExtendedStreamReader;

/**
 * Attribute parsing utilities.
 *
 * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
 */
class AttributeParsers {

    /**
     * An attribute parser for elements with a single {@code value} that don't allow any content within the element.
     */
    static final AttributeParser VALUE = new AttributeParser() {
        @Override
        public void parseElement(final AttributeDefinition attribute, final XMLExtendedStreamReader reader, final ModelNode operation) throws XMLStreamException {
            final ModelNode valueNode = ParseUtils.parseAttributeValue(readValueAttribute(reader), attribute.isAllowExpression(), attribute.getType());
            operation.get(attribute.getName()).set(valueNode);
            ParseUtils.requireNoContent(reader);
        }

        @Override
        public boolean isParseAsElement() {
            return true;
        }
    };

    /**
     * Reads a {@code name} attribute on an element.
     *
     * @param reader the reader used to read the attribute with
     *
     * @return the name attribute or {@code null} if the name attribute was not defined
     *
     * @throws XMLStreamException if an XML processing error occurs
     */
    static String readNameAttribute(final XMLExtendedStreamReader reader) throws XMLStreamException {
        return readRequiredAttributes(reader, EnumSet.of(Attribute.NAME)).get(Attribute.NAME);
    }

    /**
     * Reads a {@code value} attribute on an element.
     *
     * @param reader the reader used to read the attribute with
     *
     * @return the value attribute or {@code null} if the value attribute was not defined
     *
     * @throws XMLStreamException if an XML processing error occurs
     */
    static String readValueAttribute(final XMLExtendedStreamReader reader) throws XMLStreamException {
        return readRequiredAttributes(reader, EnumSet.of(Attribute.VALUE)).get(Attribute.VALUE);
    }

    /**
     * Reads the required attributes from an XML configuration.
     * <p>
     * The reader must be on an element with attributes.
     * </p>
     *
     * @param reader     the reader for the attributes
     * @param attributes the required attributes
     *
     * @return a map of the required attributes with the key being the attribute and the value being the value of the
     * attribute
     *
     * @throws XMLStreamException if an XML processing error occurs
     */
    static Map<Attribute, String> readRequiredAttributes(final XMLExtendedStreamReader reader, final Set<Attribute> attributes) throws XMLStreamException {
        final int attributeCount = reader.getAttributeCount();
        final Map<Attribute, String> result = new EnumMap<>(Attribute.class);
        for (int i = 0; i < attributeCount; i++) {
            final Attribute current = Attribute.forName(reader.getAttributeLocalName(i));
            if (attributes.contains(current)) {
                if (result.put(current, reader.getAttributeValue(i)) != null) {
                    throw ParseUtils.duplicateAttribute(reader, current.getLocalName());
                }
            } else {
                throw ParseUtils.unexpectedAttribute(reader, i, attributes.stream().map(Attribute::getLocalName).collect(Collectors.toSet()));
            }
        }
        if (result.isEmpty()) {
            throw ParseUtils.missingRequired(reader, attributes.stream().map(Attribute::getLocalName).collect(Collectors.toSet()));
        }
        return result;
    }

    static Map<Attribute, String> readAttributes(final XMLExtendedStreamReader reader, final Set<Attribute> attributes) {
        final Map<Attribute, String> result = new EnumMap<>(Attribute.class);
        for (int i = 0; i < reader.getAttributeCount(); i++) {
            final Attribute current = Attribute.forName(reader.getAttributeLocalName(i));
            if (attributes.contains(current)) {
                result.put(current, reader.getAttributeValue(i));
            }
        }
        return result;
    }
}