/*
 * 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.camel.maven.packaging;

import java.io.File;
import java.io.IOError;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.function.Supplier;

import org.apache.camel.tooling.util.FileUtil;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.sonatype.plexus.build.incremental.BuildContext;

public abstract class AbstractGeneratorMojo extends AbstractMojo {

    public static final String GENERATED_MSG = "Generated by camel build tools - do NOT edit this file!";
    public static final String NL = "\n";

    /**
     * The maven project.
     */
    @Parameter(property = "project", required = true, readonly = true)
    protected MavenProject project;

    /**
     * Maven ProjectHelper.
     */
    @Component
    protected MavenProjectHelper projectHelper;

    /**
     * build context to check changed files and mark them for refresh (used for m2e compatibility)
     */
    @Component
    protected BuildContext buildContext;

    private DynamicClassLoader projectClassLoader;

    public void execute(
            MavenProject project,
            MavenProjectHelper projectHelper,
            BuildContext buildContext)
            throws MojoFailureException, MojoExecutionException {
        this.project = project;
        this.projectHelper = projectHelper;
        this.buildContext = buildContext;
        execute();
    }

    protected void addResourceDirectory(Path path) {
        projectHelper.addResource(project, path.toString(), Collections.singletonList("**/*"), Collections.emptyList());
    }

    public void refresh(Path file) {
        refresh(buildContext, file);
    }

    protected boolean updateResource(Path dir, String fileName, String data) {
        boolean updated;
        updated = updateResource(buildContext, dir.resolve(fileName), data);
        if (!fileName.endsWith(".java")) {
            Path outputDir = Paths.get(project.getBuild().getOutputDirectory());
            updated |= updateResource(buildContext, outputDir.resolve(fileName), data);
        }
        return updated;
    }

    protected String createProperties(String key, String val) {
        return createProperties(project, key, val);
    }

    public static String createProperties(MavenProject project, String key, String val) {
        String data;
        StringBuilder properties = new StringBuilder();
        properties.append("# " + GENERATED_MSG + NL);
        properties.append(key).append("=").append(val).append(NL);
        properties.append("groupId=").append(project.getGroupId()).append(NL);
        properties.append("artifactId=").append(project.getArtifactId()).append(NL);
        properties.append("version=").append(project.getVersion()).append(NL);
        properties.append("projectName=").append(project.getName()).append(NL);
        if (project.getDescription() != null) {
            properties.append("projectDescription=").append(project.getDescription()).append(NL);
        }
        data = properties.toString();
        return data;
    }

    public static void refresh(BuildContext buildContext, Path file) {
        if (buildContext != null) {
            buildContext.refresh(file.toFile());
        }
    }

    public static boolean updateResource(BuildContext buildContext, Path out, String data) {
        try {
            if (FileUtil.updateFile(out, data)) {
                refresh(buildContext, out);
                return true;
            }
        } catch (IOException e) {
            throw new IOError(e);
        }
        return false;
    }

    public static boolean haveResourcesChanged(Log log, MavenProject project, BuildContext buildContext, String suffix) {
        String baseDir = project.getBasedir().getAbsolutePath();
        for (Resource r : project.getBuild().getResources()) {
            File file = new File(r.getDirectory());
            if (file.isAbsolute()) {
                file = new File(r.getDirectory().substring(baseDir.length() + 1));
            }
            String path = file.getPath() + "/" + suffix;
            if (log.isDebugEnabled()) {
                log.debug("Checking  if " + path + " (" + r.getDirectory() + "/" + suffix + ") has changed.");
            }
            if (buildContext.hasDelta(path)) {
                log.debug("Indeed " + suffix + " has changed.");
                return true;
            }
        }
        return false;
    }

    protected static <T> Supplier<T> cache(Supplier<T> supplier) {
        return new Supplier<T>() {
            T value;

            @Override
            public T get() {
                if (value == null) {
                    value = supplier.get();
                }
                return value;
            }
        };
    }

    protected Class<?> loadClass(String loadClassName) {
        Class<?> optionClass;
        String org = loadClassName;
        while (true) {
            try {
                optionClass = getProjectClassLoader().loadClass(loadClassName);
                break;
            } catch (ClassNotFoundException e) {
                int dotIndex = loadClassName.lastIndexOf('.');
                if (dotIndex == -1) {
                    throw new NoClassDefFoundError(org);
                } else {
                    loadClassName = loadClassName.substring(0, dotIndex) + "$" + loadClassName.substring(dotIndex + 1);
                }
            }
        }
        return optionClass;
    }

    protected final DynamicClassLoader getProjectClassLoader() {
        if (projectClassLoader == null) {
            try {
                projectClassLoader = DynamicClassLoader.createDynamicClassLoader(project.getCompileClasspathElements());
            } catch (DependencyResolutionRequiredException e) {
                throw new RuntimeException("Unable to create project classloader", e);
            }
        }
        return projectClassLoader;
    }
}
