/**
 * Copyright 2015 Emmanuel Bourg
 *
 * 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.debian.gradle;

import java.io.File;

import org.debian.maven.cliargs.ArgumentsIterable;
import org.debian.maven.cliargs.ArgumentsMap;
import org.debian.maven.repo.Dependency;
import org.debian.maven.repo.DependencyRule;
import org.debian.maven.repo.DependencyRuleSetFiles;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleComponentRepository;
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradlePomModuleDescriptorParser;
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser;
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.DefaultVersionComparator;
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.DefaultVersionSelectorScheme;
import org.gradle.api.internal.artifacts.repositories.AbstractArtifactRepository;
import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver;
import org.gradle.api.internal.filestore.ivy.ArtifactIdentifierFileStore;
import org.gradle.internal.component.external.model.DefaultMavenModuleResolveMetaData;
import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier;
import org.gradle.internal.component.external.model.ModuleComponentArtifactMetaData;
import org.gradle.internal.component.model.ComponentOverrideMetadata;
import org.gradle.internal.component.model.DefaultComponentOverrideMetadata;
import org.gradle.internal.component.model.DefaultIvyArtifactName;
import org.gradle.internal.component.model.IvyArtifactName;
import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult;
import org.gradle.internal.resource.local.LocallyAvailableResourceFinder;
import org.gradle.internal.resource.local.LocallyAvailableResourceFinderSearchableFileStoreAdapter;
import org.gradle.internal.resource.local.PathKeyFileStore;
import org.gradle.internal.resource.transport.file.FileTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Artifact repository resolving the dependencies against the system repository under /usr/share/maven-repo.
 */
public class MavenDebianArtifactRepository extends AbstractArtifactRepository implements ResolutionAwareRepository {

    private final Logger log = LoggerFactory.getLogger(MavenDebianArtifactRepository.class);

    private final LocallyAvailableResourceFinder<ModuleComponentArtifactMetaData> locallyAvailableResourceFinder;
    private final ArtifactIdentifierFileStore artifactFileStore;
    private final MetaDataParser<DefaultMavenModuleResolveMetaData> pomParser;
    
    /** The substitution rules for the Maven dependencies */
    private DependencyRuleSetFiles rulesets;

    /** The path to the local Maven repository */
    private File repositoryPath = new File("/usr/share/maven-repo");
    
    /** The placeholder used for ignored dependencies */
    private static final ModuleComponentIdentifier IGNORED_DEPENDENCY_PLACEHOLDER = new DefaultModuleComponentIdentifier("org.debian.gradle", "gradle-dependency-placeholder", "1.0");

    public MavenDebianArtifactRepository() {
        this.pomParser = new GradlePomModuleDescriptorParser(new DefaultVersionSelectorScheme(new DefaultVersionComparator()));
        this.artifactFileStore = new ArtifactIdentifierFileStore(new PathKeyFileStore(repositoryPath), new DebianTemporaryFileProvider());
        this.locallyAvailableResourceFinder = new LocallyAvailableResourceFinderSearchableFileStoreAdapter<>(artifactFileStore);

        log.info("\tLoading the Maven rules...");
        ArgumentsMap args = new ArgumentsMap(new ArgumentsIterable(new String[] { "--rules=debian/maven.rules", "--ignore-rules=debian/maven.ignoreRules"}));
        rulesets = DependencyRuleSetFiles.fromCLIArguments(args, false);
        rulesets.addDefaultRules();
    }

    @Override
    public String getName() {
        return "Debian Maven Repository";
    }

    public ConfiguredModuleComponentRepository createResolver() {
        return new MavenResolver(getName(), repositoryPath.toURI(), new FileTransport("debian"), locallyAvailableResourceFinder, artifactFileStore, pomParser) {

            @Override
            protected void doResolveComponentMetaData(ModuleComponentIdentifier identifier, ComponentOverrideMetadata metadata, BuildableModuleComponentMetaDataResolveResult result) {
                Dependency dependency = toDependency(identifier, metadata);
                Dependency resolved = resolve(dependency);
                
                if (resolved == null) {
                    log.info("\tIgnoring " + format(dependency));
                    identifier = IGNORED_DEPENDENCY_PLACEHOLDER;

                } else if (dependency == resolved) {
                    log.info("\tPassing through " + format(dependency));
                } else {
                    log.info("\tReplacing " + format(dependency) + "  ->  " + format(resolved));
                    identifier = new DefaultModuleComponentIdentifier(resolved.getGroupId(), resolved.getArtifactId(), resolved.getVersion());
                    metadata = new DefaultComponentOverrideMetadata();
                    metadata.getArtifacts().add(new DefaultIvyArtifactName(resolved.getArtifactId(), resolved.getType(), resolved.getType(), resolved.getClassifier()));
                }
                
                super.doResolveComponentMetaData(identifier, metadata, result);
            }
        };
    }

    /**
     * Apply the Maven rules to the specified dependency.
     * 
     * @param dependency the resolved dependency, or null if the dependency is ignored.
     */
    private Dependency resolve(Dependency dependency) {
        // check if the dependency is ignored
        for (DependencyRule rule : rulesets.get(DependencyRuleSetFiles.RulesType.IGNORE).getRules()) {
            if (rule.matches(dependency)) {
                return null;
            }
        }
        
        /**
         * The transitive dependencies are also resolved but unfortunately there is no way to detect them as such.
         * This means that a transitive dependency on asm:4.x for example would be transformed into asm:debian unless
         * its rule is copied into debian/maven.rules in order to preserve the generic '4.x' version. To mitigate this
         * issue the '.x' generic versions are detected and passed through. Artifacts with no generic version are still
         * affected though, and their rules have to be added to debian/maven.rules.
         */
        if (!dependency.getVersion().endsWith(".x") && !dependency.getVersion().equals("debian")) {
            // apply the first rule that matches
            for (DependencyRule rule : rulesets.get(DependencyRuleSetFiles.RulesType.RULES).getRules()) {
                if (rule.matches(dependency)) {
                    return rule.apply(dependency);
                }
            }
        }
        
        return dependency;
    }

    /**
     * Converts a Gradle dependency into a dependency object as handled by maven-repo-helper.
     */
    private Dependency toDependency(ModuleComponentIdentifier identifier, ComponentOverrideMetadata metadata) {
        String classifier = null;
        String type = "jar";
        if (!metadata.getArtifacts().isEmpty()) {
            IvyArtifactName ivyArtifactName = metadata.getArtifacts().iterator().next();
            classifier = ivyArtifactName.getClassifier();
            type = ivyArtifactName.getType();
        }
        
        return new Dependency(identifier.getGroup(), identifier.getModule(), type, identifier.getVersion(), "compile", false, classifier, null);
    }

    /**
     * Format a dependency for display (slightly more compact than dependency.toString())
     */
    private String format(Dependency dependency) {
        StringBuilder builder = new StringBuilder();
        builder.append(dependency.getGroupId());
        builder.append(":");
        builder.append(dependency.getArtifactId());
        builder.append(":");
        builder.append(dependency.getType());
        builder.append(":");
        builder.append(dependency.getVersion());
        if (dependency.getClassifier() != null && dependency.getClassifier().trim().length() > 0) {
            builder.append(":");
            builder.append(dependency.getClassifier());
        }
        
        return builder.toString();
    }
}
