/*
 *  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
 *
 *      https://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.ivy.core.module.id;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
import org.apache.ivy.plugins.matcher.MapMatcher;
import org.apache.ivy.plugins.matcher.PatternMatcher;

/**
 * This class targets to speed up lookup for exact pattern matcher by keys, which are created with
 * (organization, module) information. When exact pattern matcher is added, the key is created from
 * matcher's attributes. When matcher is looked up against specific module, the key is recreated
 * from module's attributes.
 * <p>
 * The lookup doesn't target to speed up lookup for non exact pattern matcher. All non exact
 * matchers are placed in non-keyed collection.
 * </p>
 * <p>
 * At lookup for matchers against specific module, all non exact pattern matchers are iterated to
 * match with module attributes, and exact pattern matchers binding to the same key will also
 * iterated to match with module attributes.
 * </p>
 * <p>
 * If there are much more exact pattern matchers than non exact pattern matchers, the matcher lookup
 * speed can benefit from this class significantly. A quick example could be user declares lots of
 * dependencyOverrides which are typically exact pattern matchers.
 * </p>
 * <p>
 * If there are balanced exact and non exact pattern matchers, the matcher lookup speed doesn't hurt
 * by this class.
 * </p>
 */
public class MatcherLookup {

    // private static final String FORMAT = "{org:%s, module:%s}";

    private static final String DEFAULT = "{org:" + "default" + ", module:" + "default" + "}";

    private Map<String, List<MapMatcher>> lookup = new HashMap<>();

    private List<MapMatcher> nonExactMatchers = new ArrayList<>();

    /**
     * Add matcher.
     *
     * If matcher is exact pattern matcher, it will be associated with a key and placed in keyed
     * collection.
     *
     * If matcher is not exact pattern matcher, it will be placed into non-keyed collection
     *
     * @param matcher MapMatcher
     */
    public void add(MapMatcher matcher) {
        if (!(matcher.getPatternMatcher() instanceof ExactPatternMatcher)) {
            nonExactMatchers.add(matcher);
            return;
        }
        String key = key(matcher.getAttributes());
        List<MapMatcher> exactMatchers = lookup.get(key);
        if (exactMatchers == null) {
            exactMatchers = new ArrayList<>();
            lookup.put(key, exactMatchers);
        }
        exactMatchers.add(matcher);
    }

    /**
     * @param attrs
     *            A map of attributes that matcher should match.
     *
     * @return a list of matchers that can apply to module withs specified attributes
     */
    public List<MapMatcher> get(Map<String, String> attrs) {
        List<MapMatcher> matchers = new ArrayList<>();
        // Step 1: find matchers from nonExactMatchers list
        if (!nonExactMatchers.isEmpty()) {
            for (MapMatcher matcher : nonExactMatchers) {
                if (matcher.matches(attrs)) {
                    matchers.add(matcher);
                }
            }
        }
        // Step 2: find matchers from exactMatchers list of key
        String key = key(attrs);
        List<MapMatcher> exactMatchers = lookup.get(key);
        if (exactMatchers != null) {
            for (MapMatcher matcher : exactMatchers) {
                if (matcher.matches(attrs)) {
                    matchers.add(matcher);
                }
            }
        }
        // Step 3: (iff key != DEFAULT) find matchers from exactMatchers of DEFAULT
        if (!DEFAULT.equals(key)) {
            List<MapMatcher> defaultExactMatchers = lookup.get(DEFAULT);
            if (defaultExactMatchers != null) {
                for (MapMatcher matcher : defaultExactMatchers) {
                    if (matcher.matches(attrs)) {
                        matchers.add(matcher);
                    }
                }
            }
        }
        return matchers;
    }

    /**
     * Create a key from specified attributes
     *
     * @param attrs
     *            A map of attributes
     * @return key object
     */
    private String key(Map<String, String> attrs) {
        String org = attrs.get(IvyPatternHelper.ORGANISATION_KEY);
        String module = attrs.get(IvyPatternHelper.MODULE_KEY);
        if (org == null || PatternMatcher.ANY_EXPRESSION.equals(org) || module == null
                || PatternMatcher.ANY_EXPRESSION.equals(module)) {
            return DEFAULT;
        }
        return "{org:" + org + ", module:" + module + "}";
    }

}
