/*
 * Copyright 2016 the original author or authors.
 *
 * Licensed 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.gradle.api.internal.resolve;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import org.gradle.api.artifacts.component.LibraryComponentSelector;
import org.gradle.platform.base.Binary;
import org.gradle.platform.base.VariantComponent;

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

/**
 * Intermediate data structure used to store the result of a resolution and help at building an understandable error message in case resolution fails.
 */
public class LibraryResolutionResult {
    public static final Function<String, String> QUOTE_TRANSFORMER = new Function<String, String>() {
        @Override
        public String apply(String input) {
            return "'" + input + "'";
        }
    };
    private final Map<String, VariantComponent> libsMatchingRequirements;
    private final Map<String, VariantComponent> libsNotMatchingRequirements;
    private final Class<? extends Binary> binaryType;

    private boolean projectNotFound;

    private VariantComponent selectedLibrary;
    private VariantComponent nonMatchingLibrary;

    private LibraryResolutionResult(Class<? extends Binary> binaryType) {
        this.binaryType = binaryType;
        this.libsMatchingRequirements = new HashMap<>();
        this.libsNotMatchingRequirements = new HashMap<>();
    }

    private VariantComponent getSingleMatchingLibrary() {
        if (libsMatchingRequirements.size() == 1) {
            return libsMatchingRequirements.values().iterator().next();
        }
        return null;
    }

    private void resolve(String libraryName) {
        if (libraryName == null) {
            VariantComponent singleMatchingLibrary = getSingleMatchingLibrary();
            if (singleMatchingLibrary == null) {
                return;
            }
            libraryName = singleMatchingLibrary.getName();
        }

        selectedLibrary = libsMatchingRequirements.get(libraryName);
        nonMatchingLibrary = libsNotMatchingRequirements.get(libraryName);
    }

    public boolean isProjectNotFound() {
        return projectNotFound;
    }

    public boolean hasLibraries() {
        return !libsMatchingRequirements.isEmpty() || !libsNotMatchingRequirements.isEmpty();
    }

    public VariantComponent getSelectedLibrary() {
        return selectedLibrary;
    }

    public VariantComponent getNonMatchingLibrary() {
        return nonMatchingLibrary;
    }

    public List<String> getCandidateLibraries() {
        return Lists.newArrayList(libsMatchingRequirements.keySet());
    }

    public String toResolutionErrorMessage(
            LibraryComponentSelector selector) {
        List<String> candidateLibraries = formatLibraryNames(getCandidateLibraries());
        String projectPath = selector.getProjectPath();
        String libraryName = selector.getLibraryName();

        StringBuilder sb = new StringBuilder("Project '").append(projectPath).append("'");
        if (libraryName == null || !hasLibraries()) {
            if (isProjectNotFound()) {
                sb.append(" not found.");
            } else if (!hasLibraries()) {
                sb.append(" doesn't define any library.");
            } else {
                sb.append(" contains more than one library. Please select one of ");
                Joiner.on(", ").appendTo(sb, candidateLibraries);
            }
        } else {
            VariantComponent notMatchingRequirements = getNonMatchingLibrary();
            if (notMatchingRequirements != null) {
                sb.append(" contains a library named '").append(libraryName)
                    .append("' but it doesn't have any binary of type ")
                    .append(binaryType.getSimpleName());
            } else {
                sb.append(" does not contain library '").append(libraryName).append("'. Did you want to use ");
                if (candidateLibraries.size() == 1) {
                    sb.append(candidateLibraries.get(0));
                } else {
                    sb.append("one of ");
                    Joiner.on(", ").appendTo(sb, candidateLibraries);
                }
                sb.append("?");
            }
        }
        return sb.toString();
    }

    public static LibraryResolutionResult of(Class<? extends Binary> binaryType, Collection<? extends VariantComponent> libraries, String libraryName, Predicate<? super VariantComponent> libraryFilter) {
        LibraryResolutionResult result = new LibraryResolutionResult(binaryType);
        for (VariantComponent librarySpec : libraries) {
            if (libraryFilter.apply(librarySpec)) {
                result.libsMatchingRequirements.put(librarySpec.getName(), librarySpec);
            } else {
                result.libsNotMatchingRequirements.put(librarySpec.getName(), librarySpec);
            }
        }
        result.resolve(libraryName);
        return result;
    }

    public static LibraryResolutionResult projectNotFound(Class<? extends Binary> binaryType) {
        LibraryResolutionResult projectNotFoundResult = new LibraryResolutionResult(binaryType);
        projectNotFoundResult.projectNotFound = true;
        return projectNotFoundResult;
    }

    public static LibraryResolutionResult emptyResolutionResult(Class<? extends Binary> binaryType) {
        return new LibraryResolutionResult(binaryType);
    }

    private static List<String> formatLibraryNames(List<String> libs) {
        List<String> list = Lists.transform(libs, QUOTE_TRANSFORMER);
        return Ordering.natural().sortedCopy(list);
    }
}
