/*
 * 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.maven.model.building;

import javax.inject.Named;
import javax.inject.Singleton;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;

import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.Parent;

/**
 * ModelSourceTransformer for the build pom
 *
 * @since 4.0.0
 */
@Named
@Singleton
class BuildModelSourceTransformer implements ModelSourceTransformer {

    public static final String NAMESPACE_PREFIX = "http://maven.apache.org/POM/";

    @Override
    public void transform(Path pomFile, TransformerContext context, Model model) {
        handleModelVersion(model);
        handleParent(pomFile, context, model);
        handleReactorDependencies(context, model);
        handleCiFriendlyVersion(context, model);
    }

    //
    // Infer modelVersion from namespace URI
    //
    void handleModelVersion(Model model) {
        String namespace = model.getDelegate().getNamespaceUri();
        if (model.getModelVersion() == null && namespace != null && namespace.startsWith(NAMESPACE_PREFIX)) {
            model.setModelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
        }
    }

    //
    // Infer parent information
    //
    void handleParent(Path pomFile, TransformerContext context, Model model) {
        Parent parent = model.getParent();
        if (parent != null) {
            String version = parent.getVersion();
            String path = Optional.ofNullable(parent.getRelativePath()).orElse("..");
            if (version == null && !path.isEmpty()) {
                Optional<RelativeProject> resolvedParent = resolveRelativePath(
                        pomFile.getParent(), context, Paths.get(path), parent.getGroupId(), parent.getArtifactId());
                resolvedParent.ifPresent(relativeProject -> parent.setVersion(relativeProject.getVersion()));
            }
        }
    }

    //
    // CI friendly versions
    //
    void handleCiFriendlyVersion(TransformerContext context, Model model) {
        String version = model.getVersion();
        String modVersion = replaceCiFriendlyVersion(context, version);
        model.setVersion(modVersion);

        Parent parent = model.getParent();
        if (parent != null) {
            version = parent.getVersion();
            modVersion = replaceCiFriendlyVersion(context, version);
            parent.setVersion(modVersion);
        }
    }

    //
    // Infer inner reactor dependencies version
    //
    void handleReactorDependencies(TransformerContext context, Model model) {
        for (Dependency dep : model.getDependencies()) {
            if (dep.getVersion() == null) {
                Model depModel = context.getRawModel(dep.getGroupId(), dep.getArtifactId());
                if (depModel != null) {
                    String v = depModel.getVersion();
                    if (v == null && depModel.getParent() != null) {
                        v = depModel.getParent().getVersion();
                    }
                    dep.setVersion(v);
                }
            }
        }
    }

    protected String replaceCiFriendlyVersion(TransformerContext context, String version) {
        if (version != null) {
            for (String key : Arrays.asList("changelist", "revision", "sha1")) {
                String val = context.getUserProperty(key);
                if (val != null) {
                    version = version.replace("${" + key + "}", val);
                }
            }
        }
        return version;
    }

    protected Optional<RelativeProject> resolveRelativePath(
            Path projectPath, TransformerContext context, Path relativePath, String groupId, String artifactId) {
        Path pomPath = projectPath.resolve(relativePath).normalize();
        if (Files.isDirectory(pomPath)) {
            pomPath = context.locate(pomPath);
        }

        if (pomPath == null || !Files.isRegularFile(pomPath)) {
            return Optional.empty();
        }

        Optional<RelativeProject> mappedProject = Optional.ofNullable(context.getRawModel(pomPath.normalize()))
                .map(BuildModelSourceTransformer::toRelativeProject);

        if (mappedProject.isPresent()) {
            RelativeProject project = mappedProject.get();

            if (Objects.equals(groupId, project.getGroupId()) && Objects.equals(artifactId, project.getArtifactId())) {
                return mappedProject;
            }
        }
        return Optional.empty();
    }

    private static RelativeProject toRelativeProject(final org.apache.maven.model.Model m) {
        String groupId = m.getGroupId();
        if (groupId == null && m.getParent() != null) {
            groupId = m.getParent().getGroupId();
        }

        String version = m.getVersion();
        if (version == null && m.getParent() != null) {
            version = m.getParent().getVersion();
        }

        return new RelativeProject(groupId, m.getArtifactId(), version);
    }

    static class RelativeProject {
        private final String groupId;

        private final String artifactId;

        private final String version;

        RelativeProject(String groupId, String artifactId, String version) {
            this.groupId = groupId;
            this.artifactId = artifactId;
            this.version = version;
        }

        public String getGroupId() {
            return groupId;
        }

        public String getArtifactId() {
            return artifactId;
        }

        public String getVersion() {
            return version;
        }
    }
}
