/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.pojo.descriptor;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;

import org.jboss.as.controller.ModuleIdentifierUtil;
import org.jboss.as.pojo.BeanState;
import org.jboss.as.pojo.ParseResult;
import org.jboss.as.pojo.logging.PojoLogger;
import org.jboss.staxmapper.XMLElementReader;
import org.jboss.staxmapper.XMLExtendedStreamReader;

import static org.jboss.as.controller.parsing.ParseUtils.missingRequired;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedAttribute;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement;

/**
 * Parse Microcontainer jboss-beans.xml.
 *
 * @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
 */
public class KernelDeploymentXmlDescriptorParser implements XMLElementReader<ParseResult<KernelDeploymentXmlDescriptor>>, XMLStreamConstants {

    private enum Element {
        BEAN("bean"),
        BEAN_FACTORY("bean-factory"),
        CLASSLOADER("classloader"),
        CONSTRUCTOR("constructor"),
        FACTORY("factory"),
        PROPERTY("property"),
        VALUE("value"),
        INJECT("inject"),
        VALUE_FACTORY("value-factory"),
        PARAMETER("parameter"),
        DEPENDS("depends"),
        ALIAS("alias"),
        ANNOTATION("annotation"),
        CREATE("create"),
        START("start"),
        STOP("stop"),
        DESTROY("destroy"),
        INSTALL("install"),
        UNINSTALL("uninstall"),
        INCALLBACK("incallback"),
        UNCALLBACK("uncallback"),
        LIST("list"),
        SET("set"),
        MAP("map"),
        ENTRY("entry"),
        KEY("key"),
        UNKNOWN(null);

        private final String localPart;

        private static final Map<String, Element> QNAME_MAP = new HashMap<String, Element>();

        static {
            for (Element element : Element.values()) {
                QNAME_MAP.put(element.localPart, element);
            }
        }

        private Element(final String localPart) {
            this.localPart = localPart;
        }

        static Element of(String localPart) {
            final Element element = QNAME_MAP.get(localPart);
            return element == null ? UNKNOWN : element;
        }
    }

    private enum Attribute {
        MODE("mode"),
        NAME("name"),
        TYPE("type"),
        VALUE("value"),
        TRIM("trim"),
        REPLACE("replace"),
        BEAN("bean"),
        SERVICE("service"),
        PROPERTY("property"),
        CLASS("class"),
        ELEMENT("elementClass"),
        KEY_ELEMENT("keyClass"),
        VALUE_ELEMENT("valueClass"),
        METHOD("method"),
        IGNORED("ignored"),
        SIGNATURE("signature"),
        FACTORY_CLASS("factory-class"),
        FACTORY_METHOD("factory-method"),
        FACTORY_CLASS_LEGACY("factoryClass"),
        FACTORY_METHOD_LEGACY("factoryMethod"),
        STATE("state"),
        TARGET_STATE("targetState"),
        UNKNOWN(null);

        private final String localPart;

        private static final Map<String, Attribute> QNAME_MAP = new HashMap<String, Attribute>();

        static {
            for (Attribute attribute : Attribute.values()) {
                QNAME_MAP.put(attribute.localPart, attribute);
            }
        }

        private Attribute(final String localPart) {
            this.localPart = localPart;
        }

        static Attribute of(String localPart) {
            final Attribute attribute = QNAME_MAP.get(localPart);
            return attribute == null ? UNKNOWN : attribute;
        }
    }

    private final BeanDeploymentSchema schema;

    public KernelDeploymentXmlDescriptorParser(BeanDeploymentSchema schema) {
        this.schema = schema;
    }

    @Override
    public void readElement(XMLExtendedStreamReader reader, ParseResult<KernelDeploymentXmlDescriptor> value) throws XMLStreamException {
        final KernelDeploymentXmlDescriptor kernelDeploymentXmlDescriptor = new KernelDeploymentXmlDescriptor();
        final List<BeanMetaDataConfig> beansConfigs = new ArrayList<BeanMetaDataConfig>();
        kernelDeploymentXmlDescriptor.setBeans(beansConfigs);
        value.setResult(kernelDeploymentXmlDescriptor);

        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final String attributeName = reader.getAttributeLocalName(i);
            final Attribute attribute = Attribute.of(attributeName);
            final String attributeValue = reader.getAttributeValue(i);
            switch (attribute) {
                case MODE:
                    kernelDeploymentXmlDescriptor.setMode(ModeConfig.of(attributeValue));
                    break;
            }
        }

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case COMMENT:
                    break;
                case END_ELEMENT:
                    return;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case BEAN:
                            beansConfigs.add(parseBean(reader));
                            break;
                        case BEAN_FACTORY:
                            BeanMetaDataConfig bean = parseBean(reader);
                            beansConfigs.add(toBeanFactory(bean));
                            kernelDeploymentXmlDescriptor.incrementBeanFactoryCount();
                            break;
                        case UNKNOWN:
                            throw unexpectedElement(reader);
                    }
                    break;
            }
        }
    }

    private BeanMetaDataConfig toBeanFactory(final BeanMetaDataConfig bean) {
        return new BeanFactoryMetaDataConfig(bean);
    }

    private BeanMetaDataConfig parseBean(final XMLExtendedStreamReader reader) throws XMLStreamException {
        BeanMetaDataConfig beanConfig = new BeanMetaDataConfig();

        final int count = reader.getAttributeCount();
        final Set<Attribute> required = EnumSet.of(Attribute.NAME);
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            required.remove(attribute);
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case NAME:
                    beanConfig.setName(attributeValue);
                    break;
                case CLASS:
                    beanConfig.setBeanClass(attributeValue);
                    break;
                case MODE:
                    beanConfig.setMode(ModeConfig.of(attributeValue));
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (required.isEmpty() == false) {
            throw missingRequired(reader, required);
        }

        while (reader.hasNext()) {
            switch (reader.nextTag()) {
                case COMMENT:
                    break;
                case END_ELEMENT:
                    return beanConfig;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case ALIAS:
                            Set<String> aliases = beanConfig.getAliases();
                            if (aliases == null) {
                                aliases = new HashSet<String>();
                                beanConfig.setAliases(aliases);
                            }
                            aliases.add(parseAlias(reader));
                            break;
                        case CLASSLOADER:
                            beanConfig.setModule(parseModuleConfig(reader));
                            break;
                        case CONSTRUCTOR:
                            beanConfig.setConstructor(parseConstructor(reader));
                            break;
                        case PROPERTY:
                            Set<PropertyConfig> properties = beanConfig.getProperties();
                            if (properties == null) {
                                properties = new HashSet<PropertyConfig>();
                                beanConfig.setProperties(properties);
                            }
                            properties.add(parseProperty(reader));
                            break;
                        case INSTALL:
                            List<InstallConfig> installs = beanConfig.getInstalls();
                            if (installs == null) {
                                installs = new ArrayList<InstallConfig>();
                                beanConfig.setInstalls(installs);
                            }
                            installs.add(parseInstall(reader));
                            break;
                        case UNINSTALL:
                            List<InstallConfig> uninstalls = beanConfig.getUninstalls();
                            if (uninstalls == null) {
                                uninstalls = new ArrayList<InstallConfig>();
                                beanConfig.setUninstalls(uninstalls);
                            }
                            uninstalls.add(parseInstall(reader));
                            break;
                        case INCALLBACK:
                            List<CallbackConfig> incallbacks = beanConfig.getIncallbacks();
                            if (incallbacks == null) {
                                incallbacks = new ArrayList<CallbackConfig>();
                                beanConfig.setIncallbacks(incallbacks);
                            }
                            incallbacks.add(parseCallback(reader));
                            break;
                        case UNCALLBACK:
                            List<CallbackConfig> uncallbacks = beanConfig.getUncallbacks();
                            if (uncallbacks == null) {
                                uncallbacks = new ArrayList<CallbackConfig>();
                                beanConfig.setUncallbacks(uncallbacks);
                            }
                            uncallbacks.add(parseCallback(reader));
                            break;
                        case DEPENDS:
                            Set<DependsConfig> depends = beanConfig.getDepends();
                            if (depends == null) {
                                depends = new HashSet<DependsConfig>();
                                beanConfig.setDepends(depends);
                            }
                            depends.add(parseDepends(reader));
                            break;
                        case CREATE:
                            beanConfig.setCreate(parseLifecycle(reader, "create"));
                            break;
                        case START:
                            beanConfig.setStart(parseLifecycle(reader, "start"));
                            break;
                        case STOP:
                            beanConfig.setStop(parseLifecycle(reader, "stop"));
                            break;
                        case DESTROY:
                            beanConfig.setDestroy(parseLifecycle(reader, "destroy"));
                            break;
                        case UNKNOWN:
                            throw unexpectedElement(reader);
                    }
                    break;
            }
        }
        throw unexpectedElement(reader);
    }

    private String parseAlias(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final String alias = parseTextElement(reader);
        if (alias == null || alias.trim().length() == 0)
            throw PojoLogger.ROOT_LOGGER.nullOrEmptyAlias();
        return alias;
    }

    private ConstructorConfig parseConstructor(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final ConstructorConfig ctorConfig = new ConstructorConfig();
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case FACTORY_CLASS:
                case FACTORY_CLASS_LEGACY:
                    ctorConfig.setFactoryClass(attributeValue);
                    break;
                case FACTORY_METHOD:
                case FACTORY_METHOD_LEGACY:
                    ctorConfig.setFactoryMethod(attributeValue);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        List<ValueConfig> parameters = new ArrayList<ValueConfig>();
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    ctorConfig.setParameters(parameters.toArray(new ValueConfig[parameters.size()]));
                    return ctorConfig;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case FACTORY:
                            ctorConfig.setFactory(parseFactory(reader));
                            break;
                        case PARAMETER:
                            ValueConfig p = parseParameter(reader);
                            p.setIndex(parameters.size());
                            parameters.add(p);
                            break;
                        default:
                            throw unexpectedElement(reader);
                    }
            }
        }
        throw unexpectedElement(reader);
    }

    private ModuleConfig parseModuleConfig(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final ModuleConfig moduleConfig = new ModuleConfig();
        final int count = reader.getAttributeCount();
        final Set<Attribute> required = EnumSet.of(Attribute.NAME);
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            required.remove(attribute);
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case NAME:
                    moduleConfig.setModuleName(ModuleIdentifierUtil.canonicalModuleIdentifier(attributeValue));
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (required.isEmpty() == false) {
            throw missingRequired(reader, required);
        }
        return moduleConfig;
    }

    private PropertyConfig parseProperty(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final PropertyConfig property = new PropertyConfig();
        final Set<Attribute> required = EnumSet.of(Attribute.NAME);
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            required.remove(attribute);
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case NAME:
                    property.setPropertyName(attributeValue);
                    break;
                case CLASS:
                    property.setType(attributeValue);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (required.isEmpty() == false) {
            throw missingRequired(reader, required);
        }

        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    return property;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case VALUE:
                            property.setValue(parseValue(reader));
                            break;
                        case INJECT:
                            property.setValue(parseInject(reader));
                            break;
                        case VALUE_FACTORY:
                            property.setValue(parseValueFactory(reader));
                            break;
                        case LIST:
                            property.setValue(parseList(reader));
                            break;
                        case SET:
                            property.setValue(parseSet(reader));
                            break;
                        case MAP:
                            property.setValue(parseMap(reader));
                            break;
                        default:
                            throw unexpectedElement(reader);
                    }
            }
        }
        throw unexpectedElement(reader);
    }

    private InstallConfig parseInstall(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final InstallConfig installConfig = new InstallConfig();
        final Set<Attribute> required = EnumSet.of(Attribute.METHOD);
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            required.remove(attribute);
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case STATE:
                    installConfig.setWhenRequired(BeanState.valueOf(attributeValue.toUpperCase(Locale.ENGLISH)));
                    break;
                case TARGET_STATE:
                    installConfig.setDependencyState(BeanState.valueOf(attributeValue.toUpperCase(Locale.ENGLISH)));
                    break;
                case BEAN:
                    installConfig.setDependency(attributeValue);
                    break;
                case METHOD:
                    installConfig.setMethodName(attributeValue);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (required.isEmpty() == false) {
            throw missingRequired(reader, required);
        }

        List<ValueConfig> parameters = new ArrayList<ValueConfig>();
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    installConfig.setParameters(parameters.toArray(new ValueConfig[parameters.size()]));
                    return installConfig;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case PARAMETER:
                            ValueConfig p = parseParameter(reader);
                            p.setIndex(parameters.size());
                            parameters.add(p);
                            break;
                        default:
                            throw unexpectedElement(reader);
                    }
            }
        }
        throw unexpectedElement(reader);
    }

    private CallbackConfig parseCallback(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final CallbackConfig callbackConfig = new CallbackConfig();
        final Set<Attribute> required = EnumSet.of(Attribute.METHOD);
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            required.remove(attribute);
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case STATE:
                    callbackConfig.setWhenRequired(BeanState.valueOf(attributeValue.toUpperCase(Locale.ENGLISH)));
                    break;
                case TARGET_STATE:
                    callbackConfig.setState(BeanState.valueOf(attributeValue.toUpperCase(Locale.ENGLISH)));
                    break;
                case METHOD:
                    callbackConfig.setMethodName(attributeValue);
                    break;
                case SIGNATURE:
                    callbackConfig.setSignature(attributeValue);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (required.isEmpty() == false) {
            throw missingRequired(reader, required);
        }
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    return callbackConfig;
            }
        }
        throw unexpectedElement(reader);
    }

    private DependsConfig parseDepends(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final DependsConfig dependsConfig = new DependsConfig();
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case STATE:
                    dependsConfig.setWhenRequired(BeanState.valueOf(attributeValue.toUpperCase(Locale.ENGLISH)));
                    break;
                case TARGET_STATE:
                    dependsConfig.setDependencyState(BeanState.valueOf(attributeValue.toUpperCase(Locale.ENGLISH)));
                    break;
                case SERVICE:
                    dependsConfig.setService(Boolean.parseBoolean(attributeValue));
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }

        String dependency = parseTextElement(reader);
        if (dependency == null || dependency.trim().length() == 0)
            throw PojoLogger.ROOT_LOGGER.nullOrEmptyDependency();
        dependsConfig.setDependency(dependency);

        return dependsConfig;
    }

    private LifecycleConfig parseLifecycle(final XMLExtendedStreamReader reader, String defaultMethodName) throws XMLStreamException {
        final LifecycleConfig lifecycleConfig = new LifecycleConfig();
        lifecycleConfig.setMethodName(defaultMethodName); // set default
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case METHOD:
                    lifecycleConfig.setMethodName(attributeValue);
                    break;
                case IGNORED:
                    lifecycleConfig.setIgnored(Boolean.parseBoolean(attributeValue));
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }

        List<ValueConfig> parameters = new ArrayList<ValueConfig>();
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    lifecycleConfig.setParameters(parameters.toArray(new ValueConfig[parameters.size()]));
                    return lifecycleConfig;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case PARAMETER:
                            ValueConfig p = parseParameter(reader);
                            p.setIndex(parameters.size());
                            parameters.add(p);
                            break;
                        default:
                            throw unexpectedElement(reader);
                    }
            }
        }
        throw unexpectedElement(reader);
    }

    private ValueConfig parseParameter(final XMLExtendedStreamReader reader) throws XMLStreamException {
        ValueConfig valueConfig = null;
        String type = null;
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case CLASS:
                    type = attributeValue;
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }

        while (reader.hasNext()) {
            switch (reader.next()) {
                case CHARACTERS:
                    final StringValueConfig svc = new StringValueConfig();
                    svc.setValue(reader.getText());
                    valueConfig = svc;
                    break;
                case END_ELEMENT:
                    if (valueConfig == null)
                        throw new XMLStreamException(PojoLogger.ROOT_LOGGER.missingValue(), reader.getLocation());
                    if (valueConfig.getType() == null)
                        valueConfig.setType(type);
                    return valueConfig;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case VALUE:
                            valueConfig = parseValue(reader);
                            break;
                        case INJECT:
                            valueConfig = parseInject(reader);
                            break;
                        case VALUE_FACTORY:
                            valueConfig = parseValueFactory(reader);
                            break;
                        case LIST:
                            valueConfig = parseList(reader);
                            break;
                        case SET:
                            valueConfig = parseSet(reader);
                            break;
                        case MAP:
                            valueConfig = parseMap(reader);
                            break;
                        default:
                            throw unexpectedElement(reader);
                    }
            }
        }
        throw unexpectedElement(reader);
    }

    private InjectedValueConfig parseInject(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final InjectedValueConfig injectedValueConfig = new InjectedValueConfig();
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case BEAN:
                    injectedValueConfig.setBean(attributeValue);
                    break;
                case STATE:
                    injectedValueConfig.setState(BeanState.valueOf(attributeValue.toUpperCase(Locale.ENGLISH)));
                    break;
                case SERVICE:
                    injectedValueConfig.setService(attributeValue);
                    break;
                case PROPERTY:
                    injectedValueConfig.setProperty(attributeValue);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    return injectedValueConfig;
            }
        }
        throw unexpectedElement(reader);
    }

    private FactoryConfig parseFactory(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final FactoryConfig factoryConfig = new FactoryConfig();
        final Set<Attribute> required = EnumSet.of(Attribute.BEAN);
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            required.remove(attribute);
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case BEAN:
                    factoryConfig.setBean(attributeValue);
                    break;
                case STATE:
                    factoryConfig.setState(BeanState.valueOf(attributeValue.toUpperCase(Locale.ENGLISH)));
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (required.isEmpty() == false) {
            throw missingRequired(reader, required);
        }
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    return factoryConfig;
            }
        }
        throw unexpectedElement(reader);
    }

    private ValueConfig parseList(final XMLExtendedStreamReader reader) throws XMLStreamException {
        return parseCollection(reader, new ListConfig());
    }

    private ValueConfig parseSet(final XMLExtendedStreamReader reader) throws XMLStreamException {
        return parseCollection(reader, new SetConfig());
    }

    private ValueConfig parseCollection(final XMLExtendedStreamReader reader, CollectionConfig config) throws XMLStreamException {
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case CLASS:
                    config.setType(attributeValue);
                    break;
                case ELEMENT:
                    config.setElementType(attributeValue);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    return config;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case VALUE:
                            config.addValue(parseValue(reader));
                            break;
                        case INJECT:
                            config.addValue(parseInject(reader));
                            break;
                        case VALUE_FACTORY:
                            config.addValue(parseValueFactory(reader));
                            break;
                        case LIST:
                            config.addValue(parseList(reader));
                            break;
                        case SET:
                            config.addValue(parseSet(reader));
                            break;
                        case MAP:
                            config.addValue(parseMap(reader));
                            break;
                        default:
                            throw unexpectedElement(reader);
                    }
            }
        }
        throw unexpectedElement(reader);
    }

    private ValueConfig parseMap(final XMLExtendedStreamReader reader) throws XMLStreamException {
        MapConfig mapConfig = new MapConfig();
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case CLASS:
                    mapConfig.setType(attributeValue);
                    break;
                case KEY_ELEMENT:
                    mapConfig.setKeyType(attributeValue);
                    break;
                case VALUE_ELEMENT:
                    mapConfig.setValueType(attributeValue);
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    return mapConfig;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case ENTRY:
                            ValueConfig[] entry = parseEntry(reader);
                            mapConfig.put(entry[0], entry[1]);
                            break;
                        default:
                            throw unexpectedElement(reader);
                    }
            }
        }
        throw unexpectedElement(reader);
    }

    private ValueConfig[] parseEntry(final XMLExtendedStreamReader reader) throws XMLStreamException {
        ValueConfig[] entry = new ValueConfig[2];
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    return entry;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case KEY:
                            entry[0] = parseValueValue(reader);
                            break;
                        case VALUE:
                            entry[1] = parseValueValue(reader);
                            break;
                        default:
                            throw unexpectedElement(reader);
                    }
            }
        }
        throw unexpectedElement(reader);
    }

    private ValueConfig parseValueValue(final XMLExtendedStreamReader reader) throws XMLStreamException {
        ValueConfig value = null;
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    if (value == null)
                        throw new XMLStreamException(PojoLogger.ROOT_LOGGER.missingValue(), reader.getLocation());
                    return value;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case VALUE:
                            value = parseValue(reader);
                            break;
                        case INJECT:
                            value = parseInject(reader);
                            break;
                        case VALUE_FACTORY:
                            value = parseValueFactory(reader);
                            break;
                        case LIST:
                            value = parseList(reader);
                            break;
                        case SET:
                            value = parseSet(reader);
                            break;
                        case MAP:
                            value = parseMap(reader);
                            break;
                        default:
                            throw unexpectedElement(reader);
                    }
                    break;
                case CHARACTERS:
                    StringValueConfig svc = new StringValueConfig();
                    svc.setValue(reader.getText());
                    value = svc;
                    break;
            }
        }
        throw unexpectedElement(reader);
    }

    private ValueFactoryConfig parseValueFactory(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final ValueFactoryConfig valueFactoryConfig = new ValueFactoryConfig();
        final Set<Attribute> required = EnumSet.of(Attribute.BEAN, Attribute.METHOD);
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            required.remove(attribute);
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case BEAN:
                    valueFactoryConfig.setBean(attributeValue);
                    break;
                case METHOD:
                    valueFactoryConfig.setMethod(attributeValue);
                    break;
                case STATE:
                    valueFactoryConfig.setState(BeanState.valueOf(attributeValue.toUpperCase(Locale.ENGLISH)));
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        if (required.isEmpty() == false) {
            throw missingRequired(reader, required);
        }

        List<ValueConfig> parameters = new ArrayList<ValueConfig>();
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    valueFactoryConfig.setParameters(parameters.toArray(new ValueConfig[parameters.size()]));
                    return valueFactoryConfig;
                case START_ELEMENT:
                    switch (Element.of(reader.getLocalName())) {
                        case PARAMETER:
                            ValueConfig p = parseParameter(reader);
                            p.setIndex(parameters.size());
                            parameters.add(p);
                            break;
                        default:
                            throw unexpectedElement(reader);
                    }
            }
        }
        throw unexpectedElement(reader);
    }

    private ValueConfig parseValue(final XMLExtendedStreamReader reader) throws XMLStreamException {
        final StringValueConfig valueConfig = new StringValueConfig();
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final Attribute attribute = Attribute.of(reader.getAttributeLocalName(i));
            final String attributeValue = reader.getAttributeValue(i);

            switch (attribute) {
                case CLASS:
                    valueConfig.setType(attributeValue);
                    break;
                case REPLACE:
                    valueConfig.setReplaceProperties(Boolean.parseBoolean(attributeValue));
                    break;
                case TRIM:
                    valueConfig.setTrim(Boolean.parseBoolean(attributeValue));
                    break;
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }
        valueConfig.setValue(parseTextElement(reader));
        return valueConfig;
    }

    private String parseTextElement(final XMLExtendedStreamReader reader) throws XMLStreamException {
        String value = null;
        while (reader.hasNext()) {
            switch (reader.next()) {
                case END_ELEMENT:
                    return value;
                case CHARACTERS:
                    value = reader.getText();
                    break;
                default:
                    throw unexpectedElement(reader);
            }
        }
        throw unexpectedElement(reader);
    }
}