package org.jboss.windup.rules.apps.java.archives.ignore;

import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.jboss.forge.addon.dependencies.Coordinate;
import org.jboss.forge.addon.dependencies.builder.CoordinateBuilder;
import org.jboss.forge.furnace.util.Assert;
import org.jboss.forge.furnace.versions.EmptyVersionRange;
import org.jboss.forge.furnace.versions.SingleVersion;
import org.jboss.forge.furnace.versions.SingleVersionRange;
import org.jboss.forge.furnace.versions.VersionRange;
import org.jboss.forge.furnace.versions.Versions;
import org.jboss.windup.rules.apps.java.archives.model.ArchiveCoordinateModel;
import org.jboss.windup.util.exception.WindupException;

/**
 * A service class keeping the set of skipped archives and handling their lookup.
 * <p>
 * Data file format: GROUP_ID:ARTIFACT_ID:VERSION_OR_RANGE[:CLASSIFIER]
 * <p>
 * Examples: org.apache.commons.*:*:* org.hibernate.*:hibernate-core:(3.2,4.0]:jar
 *
 * @author <a href="mailto:ozizka@redhat.com">Ondrej Zizka</a>
 * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
 */
public class SkippedArchives {
    private static final Map<Coordinate, VersionRange> map = new HashMap<>();

    /**
     * Load the given configuration file.
     */
    public static void load(File file) {
        try (FileInputStream inputStream = new FileInputStream(file)) {
            LineIterator it = IOUtils.lineIterator(inputStream, "UTF-8");
            while (it.hasNext()) {
                String line = it.next();
                if (!line.startsWith("#") && !line.trim().isEmpty()) {
                    add(line);
                }
            }
        } catch (Exception e) {
            throw new WindupException("Failed loading archive ignore patterns from [" + file.toString() + "]", e);
        }
    }

    public static void unload() {
        map.clear();
    }

    public static void add(String line) {
        Assert.notNull(line, "Archive coordinate pattern must not be null.");
        CoordinatePattern pattern = fromCoordinatePattern(line);
        map.put(pattern.getCoordinate(), pattern.getVersion());
    }

    static CoordinatePattern fromCoordinatePattern(String coordinates) {
        String[] parts = coordinates.split("\\s*:\\s*");
        if (parts.length < 3)
            throw new IllegalArgumentException("Expected GAV definition format is 'GROUP_ID:ARTIFACT_ID:VERSION_OR_RANGE[:CLASSIFIER]', was: "
                    + coordinates);

        CoordinateBuilder coordinate = CoordinateBuilder.create()
                .setGroupId(parts[0])
                .setArtifactId(parts[1]);

        VersionRange version = null;

        if (parts[2].equals("*"))
            version = new EmptyVersionRange();
            //  Range - (1.0,2.0]  or [1.0,2.0) etc.
        else if (parts[2].matches("^(\\[|\\()[^,]+(,[^,]+)?(\\]|\\))$"))
            version = Versions.parseMultipleVersionRange(parts[2]);
        else
            version = new SingleVersionRange(new SingleVersion(parts[2]));

        if (parts.length >= 4)
            coordinate.setClassifier(parts[3]);

        return new CoordinatePattern(coordinate, version);
    }

    public static boolean isSkipped(ArchiveCoordinateModel coordinate) {
        return isSkipped(CoordinateBuilder.create()
                .setArtifactId(coordinate.getArtifactId())
                .setGroupId(coordinate.getGroupId())
                .setClassifier(coordinate.getClassifier())
                .setVersion(coordinate.getVersion()));
    }

    /*
     * Public for testing purposes
     */
    public static boolean isSkipped(Coordinate coordinate) {
        for (Entry<Coordinate, VersionRange> entry : map.entrySet()) {
            Coordinate pattern = entry.getKey();
            VersionRange range = entry.getValue();

            if (isPatternMatch(pattern.getGroupId(), coordinate.getGroupId())
                    && isPatternMatch(pattern.getArtifactId(), coordinate.getArtifactId())
                    && isPatternMatch(pattern.getClassifier(), coordinate.getClassifier())) {
                if (range.isEmpty() || range.includes(new SingleVersion(coordinate.getVersion())))
                    return true;
            }
        }
        return false;
    }

    private static boolean isPatternMatch(String pattern, String value) {
        if ("*".equals(pattern))
            return true;

        if (pattern == value)
            return true;

        if (pattern != null && pattern.equals(value))
            return true;

        if (pattern != null && pattern.endsWith("*"))
            if (value != null && value.startsWith(pattern.substring(0, pattern.length() - 1)))
                return true;

        return false;
    }

    /*
     * Public for testing purposes.
     */
    public static int getCount() {
        return map.size();
    }

    private static class CoordinatePattern {

        private final CoordinateBuilder coordinate;
        private final VersionRange version;

        public CoordinatePattern(CoordinateBuilder coordinate, VersionRange version) {
            this.coordinate = coordinate;
            this.version = version;
        }

        public CoordinateBuilder getCoordinate() {
            return coordinate;
        }

        public VersionRange getVersion() {
            return version;
        }

    }
}
