/*******************************************************************************
 * Copyright (c) 2011 SAP AG and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    SAP AG - initial API and implementation
 *******************************************************************************/
package org.eclipse.tycho.artifacts.configuration;

import java.util.ArrayList;
import java.util.List;

import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.tycho.artifacts.TargetPlatformFilter;
import org.eclipse.tycho.artifacts.TargetPlatformFilter.CapabilityPattern;
import org.eclipse.tycho.artifacts.TargetPlatformFilter.CapabilityType;
import org.eclipse.tycho.artifacts.TargetPlatformFilterSyntaxException;

@Component(role = TargetPlatformFilterConfigurationReader.class)
public class TargetPlatformFilterConfigurationReader {

    public List<TargetPlatformFilter> parseFilterConfiguration(Xpp3Dom filtersElement) {
        List<TargetPlatformFilter> result = new ArrayList<>();

        for (Xpp3Dom filterDom : filtersElement.getChildren()) {
            parseFilter(filterDom, result);
        }
        return result;
    }

    private void parseFilter(Xpp3Dom filterDom, List<TargetPlatformFilter> result) {
        CapabilityPattern scopePattern = parseScopePattern(filterDom);

        Xpp3Dom restrictToDom = getComplexValue(filterDom, "restrictTo");
        Xpp3Dom removeAllDom = getMarker(filterDom, "removeAll");

        if (removeAllDom == null && restrictToDom == null) {
            throw new TargetPlatformFilterSyntaxException(
                    "Filter action is required: specify either 'filters.filter.removeAll' or 'filters.filter.restrictTo'");
        } else if (removeAllDom != null && restrictToDom != null) {
            throw new TargetPlatformFilterSyntaxException(
                    "Only one filter action may be specified: either 'filters.filter.removeAll' or 'filters.filter.restrictTo'");
        }

        final TargetPlatformFilter filter;
        if (removeAllDom != null) {
            filter = TargetPlatformFilter.removeAllFilter(scopePattern);
        } else {
            CapabilityPattern restrictionPattern = parseRestrictionPattern(restrictToDom);
            filter = TargetPlatformFilter.restrictionFilter(scopePattern, restrictionPattern);
        }
        result.add(filter);
    }

    private CapabilityPattern parseScopePattern(Xpp3Dom filterDom) {
        PatternParser scopeParser = new PatternParser(filterDom, "filters.filter");

        scopeParser.readPatternType(true);
        scopeParser.readPatternId(true);
        scopeParser.readVersionOrVersionRange();

        return scopeParser.buildPattern();
    }

    private CapabilityPattern parseRestrictionPattern(Xpp3Dom restrictToDom) {
        PatternParser restrictionParser = new PatternParser(restrictToDom, "filters.filter.restrictTo");

        restrictionParser.readPatternType(false);
        restrictionParser.readPatternId(false);
        restrictionParser.readVersionOrVersionRange();

        return restrictionParser.buildPattern();
    }

    private static CapabilityType getTypeValue(Xpp3Dom dom) {
        String typeString = getSimpleValue(dom, "type");
        if (typeString == null)
            return null;
        return CapabilityType.parsePomValue(typeString);
    }

    private static String getSimpleValue(Xpp3Dom dom, String elementName) {
        // TODO disallow complex values (e.g. further nested elements)

        Xpp3Dom element = dom.getChild(elementName);
        if (element == null)
            return null;
        return element.getValue();
    }

    private static Xpp3Dom getComplexValue(Xpp3Dom dom, String elementName) {
        return dom.getChild(elementName);
    }

    private static Xpp3Dom getMarker(Xpp3Dom dom, String elementName) {
        // TODO disallow any children

        return dom.getChild(elementName);
    }

    private static class PatternParser {

        // source
        private final Xpp3Dom baseElement;

        // debug info
        private final String baseElementPath;

        // results
        private CapabilityType type;
        private String id;
        private String version;
        private String versionRange;

        PatternParser(Xpp3Dom baseElement, String baseElementPath) {
            this.baseElement = baseElement;
            this.baseElementPath = baseElementPath;
        }

        private void readPatternType(boolean required) {
            type = getTypeValue(baseElement);
            if (required && type == null)
                throw new TargetPlatformFilterSyntaxException("Attribute '" + baseElementPath + ".type' is required");
        }

        private void readPatternId(boolean required) {
            id = getSimpleValue(baseElement, "id");
            if (required && id == null)
                throw new TargetPlatformFilterSyntaxException("Attribute '" + baseElementPath + ".id' is required");
        }

        private void readVersionOrVersionRange() {
            version = getSimpleValue(baseElement, "version");
            versionRange = getSimpleValue(baseElement, "versionRange");

            if (version != null && versionRange != null) {
                throw new TargetPlatformFilterSyntaxException(
                        "Only one of the following attributes may be specified: '" + baseElementPath + ".version' or '"
                                + baseElementPath + ".versionRange'");
            }
        }

        private CapabilityPattern buildPattern() {
            if (version == null)
                return CapabilityPattern.patternWithVersionRange(type, id, versionRange);
            else
                return CapabilityPattern.patternWithVersion(type, id, version);
        }
    }

}
