/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.karaf.features.internal.service;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;

import org.apache.felix.utils.manifest.Clause;
import org.apache.felix.utils.manifest.Parser;
import org.apache.felix.utils.version.VersionRange;
import org.apache.felix.utils.version.VersionTable;
import org.apache.karaf.features.internal.model.Bundle;
import org.apache.karaf.features.internal.model.Conditional;
import org.apache.karaf.features.internal.model.Feature;
import org.apache.karaf.features.internal.model.Features;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Helper class to deal with blacklisted features and bundles.
 */
public class Blacklist {

    public static final String BLACKLIST_URL = "url";
    public static final String BLACKLIST_RANGE = "range";
    public static final String BLACKLIST_TYPE = "type";
    public static final String TYPE_FEATURE = "feature";
    public static final String TYPE_BUNDLE = "bundle";
    public static final String TYPE_REPOSITORY = "repository";

    private static final Logger LOGGER = LoggerFactory.getLogger(Blacklist.class);

    private Blacklist() {
    }

    public static void blacklist(Features features, String blacklisted) {
        Set<String> blacklist = loadBlacklist(blacklisted);
        blacklist(features, blacklist);
    }

    public static void blacklist(Features features, Collection<String> blacklist) {
        if (!blacklist.isEmpty()) {
            Clause[] clauses = Parser.parseClauses(blacklist.toArray(new String[blacklist.size()]));
            blacklist(features, clauses);
        }
    }

    public static void blacklist(Features features, Clause[] clauses) {
        for (Iterator<Feature> iterator = features.getFeature().iterator(); iterator.hasNext(); ) {
            Feature feature = iterator.next();
            if (blacklist(feature, clauses)) {
                iterator.remove();
            }
        }
    }

    public static boolean blacklist(Feature feature, Clause[] clauses) {
        for (Clause clause : clauses) {
            // Check feature name
            if (clause.getName().equals(feature.getName())) {
                // Check feature version
                VersionRange range = VersionRange.ANY_VERSION;
                String vr = clause.getAttribute(BLACKLIST_RANGE);
                if (vr != null) {
                    range = new VersionRange(vr, true);
                }
                if (range.contains(VersionTable.getVersion(feature.getVersion()))) {
                    String type = clause.getAttribute(BLACKLIST_TYPE);
                    if (type == null || TYPE_FEATURE.equals(type)) {
                        return true;
                    }
                }
            }
            // Check bundles
            blacklist(feature.getBundle(), clauses);
            // Check conditional bundles
            for (Conditional cond : feature.getConditional()) {
                blacklist(cond.getBundle(), clauses);
            }
        }
        return false;
    }

    private static void blacklist(List<Bundle> bundles, Clause[] clauses) {
        for (Iterator<Bundle> iterator = bundles.iterator(); iterator.hasNext();) {
            Bundle info = iterator.next();
            for (Clause clause : clauses) {
                String url = clause.getName();
                if (clause.getAttribute(BLACKLIST_URL) != null) {
                    url = clause.getAttribute(BLACKLIST_URL);
                }
                if (info.getLocation().equals(url)) {
                    String type = clause.getAttribute(BLACKLIST_TYPE);
                    if (type == null || TYPE_BUNDLE.equals(type)) {
                        iterator.remove();
                        break;
                    }
                }
            }
        }
    }

    public static Set<String> loadBlacklist(String blacklistUrl) {
        Set<String> blacklist = new HashSet<>();
        try {
            if (blacklistUrl != null) {
                try (
                        InputStream is = new URL(blacklistUrl).openStream()
                ) {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                    String line;
                    while ((line = reader.readLine()) != null) {
                        line = line.trim();
                        if (!line.isEmpty() && !line.startsWith("#")) {
                            blacklist.add(line);
                        }
                    }
                }
            }
        } catch (FileNotFoundException e) {
            LOGGER.debug("Unable to load blacklist bundles list", e.toString());
        } catch (Exception e) {
            LOGGER.debug("Unable to load blacklist bundles list", e);
        }
        return blacklist;
    }

    public static boolean isFeatureBlacklisted(List<String> blacklist, String name, String version) {
        Clause[] clauses = Parser.parseClauses(blacklist.toArray(new String[blacklist.size()]));
        for (Clause clause : clauses) {
            if (clause.getName().equals(name)) {
                // Check feature version
                VersionRange range = VersionRange.ANY_VERSION;
                String vr = clause.getAttribute(BLACKLIST_RANGE);
                if (vr != null) {
                    range = new VersionRange(vr, true);
                }
                if (range.contains(VersionTable.getVersion(version))) {
                    String type = clause.getAttribute(BLACKLIST_TYPE);
                    if (type == null || TYPE_FEATURE.equals(type)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    public static boolean isBundleBlacklisted(List<String> blacklist, String uri) {
        return isBlacklisted(blacklist, uri, TYPE_BUNDLE);
    }

    public static boolean isBlacklisted(List<String> blacklist, String uri, String btype) {
        Clause[] clauses = Parser.parseClauses(blacklist.toArray(new String[blacklist.size()]));
        return isBlacklisted(clauses, uri, btype);
    }

    public static boolean isBlacklisted(Clause[] clauses, String uri, String btype) {
        for (Clause clause : clauses) {
            String url = clause.getName();
            if (clause.getAttribute(BLACKLIST_URL) != null) {
                url = clause.getAttribute(BLACKLIST_URL);
            }
            if (uri.equals(url)) {
                String type = clause.getAttribute(BLACKLIST_TYPE);
                if (type == null || btype.equals(type)) {
                    return true;
                }
            }
        }
        return false;
    }
}
