package org.jboss.windup.graph;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.inject.Inject;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.forge.arquillian.AddonDependencies;
import org.jboss.forge.arquillian.AddonDependency;
import org.jboss.forge.arquillian.archive.AddonArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.windup.config.KeepWorkDirsOption;
import org.jboss.windup.exec.WindupProcessor;
import org.jboss.windup.exec.configuration.WindupConfiguration;
import org.jboss.windup.graph.model.ArchiveModel;
import org.jboss.windup.graph.traversal.ProjectModelTraversal;
import org.jboss.windup.graph.model.WindupConfigurationModel;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.graph.service.WindupConfigurationService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 */
@RunWith(Arquillian.class)
public class ProjectModelTraversalTest
{
    @Inject
    private GraphContextFactory factory;
    @Inject
    private WindupProcessor processor;

    @Deployment
    @AddonDependencies({
                @AddonDependency(name = "org.jboss.windup.graph:windup-graph"),
                @AddonDependency(name = "org.jboss.windup.reporting:windup-reporting"),
                @AddonDependency(name = "org.jboss.windup.exec:windup-exec"),
                @AddonDependency(name = "org.jboss.windup.rules.apps:windup-rules-java"),
                @AddonDependency(name = "org.jboss.windup.utils:windup-utils"),
                @AddonDependency(name = "org.jboss.forge.furnace.container:cdi"),
    })
    public static AddonArchive getDeployment()
    {
        return ShrinkWrap.create(AddonArchive.class).addBeansXML();
    }

    private Path getTempDirectory()
    {
        return FileUtils.getTempDirectory()
                    .toPath()
                    .resolve("Windup")
                    .resolve("ProjectTraversalTest_" + RandomStringUtils.randomAlphanumeric(6));
    }

    private GraphContext createGraphContext(Path temporaryDirectory)
    {
        return factory.create(temporaryDirectory.resolve("output"));
    }

    @Test
    public void testProjectDuplicateTraversal() throws Exception
    {
        Path tempDirectory = getTempDirectory();

        try (GraphContext context = createGraphContext(tempDirectory))
        {
            final Path inputPath1 = Paths.get("../../test-files/jee-example-app-1.0.0.ear");
            final Path inputPath2 = tempDirectory.resolve("copy-of-ear.ear");
            FileUtils.copyFile(inputPath1.toFile(), inputPath2.toFile());

            runTest(context, Arrays.asList(inputPath1.toString(), inputPath2.toString()));

            final List<String> log4jPathList = new ArrayList<>();
            final List<String> migrationSupportPathList = new ArrayList<>();
            FileFoundCallback fileFoundCallback = new FileFoundCallback()
            {
                @Override
                public void fileFound(ProjectModelTraversal traversal, FileModel fileModel)
                {
                    if (fileModel.getFileName().contains("log4j-1.2.6.jar"))
                        log4jPathList.add(traversal.getFilePath(fileModel));
                    else if (fileModel.getFileName().contains("migration-support-1.0.0.jar"))
                        migrationSupportPathList.add(traversal.getFilePath(fileModel));
                }
            };

            WindupConfigurationModel windupConfiguration = WindupConfigurationService.getConfigurationModel(context);
            for (FileModel inputApplication : windupConfiguration.getInputPaths())
            {
                System.out.println("---------------------------------------------");
                System.out.println("Input App: " + inputApplication.getFileName() + ", project: " + inputApplication.getProjectModel().getName());
                traverse(new ProjectModelTraversal(inputApplication.getProjectModel()), 0, fileFoundCallback);
            }

            Assert.assertTrue(log4jPathList.contains("jee-example-app-1.0.0.ear/jee-example-web.war/WEB-INF/lib/log4j-1.2.6.jar"));
            Assert.assertTrue(log4jPathList.contains("copy-of-ear.ear/jee-example-web.war/WEB-INF/lib/log4j-1.2.6.jar"));
            Assert.assertTrue(log4jPathList.contains("shared-libs/jee-example-web.war/WEB-INF/lib/log4j-1.2.6.jar"));
            Assert.assertTrue(log4jPathList.contains("jee-example-app-1.0.0.ear/log4j-1.2.6.jar"));
            Assert.assertTrue(log4jPathList.contains("copy-of-ear.ear/log4j-1.2.6.jar"));
            Assert.assertTrue(log4jPathList.contains("shared-libs/log4j-1.2.6.jar"));

            Assert.assertTrue(migrationSupportPathList.contains("jee-example-app-1.0.0.ear/jee-example-web.war/WEB-INF/lib/migration-support-1.0.0.jar"));
            Assert.assertTrue(migrationSupportPathList.contains("copy-of-ear.ear/jee-example-web.war/WEB-INF/lib/migration-support-1.0.0.jar"));
            Assert.assertTrue(migrationSupportPathList.contains("shared-libs/jee-example-web.war/WEB-INF/lib/migration-support-1.0.0.jar"));
            Assert.assertTrue(migrationSupportPathList.contains("jee-example-app-1.0.0.ear/migration-support-1.0.0.jar"));
            Assert.assertTrue(migrationSupportPathList.contains("copy-of-ear.ear/migration-support-1.0.0.jar"));
            Assert.assertTrue(migrationSupportPathList.contains("shared-libs/migration-support-1.0.0.jar"));
            System.out.println("Done!");
        }
        finally
        {
            FileUtils.deleteDirectory(tempDirectory.toFile());
        }
    }

    @Test
    public void testDuplicateFilesWithDifferingNames() throws Exception
    {
        Path tempDirectory = getTempDirectory();
        try (GraphContext context = createGraphContext(tempDirectory))
        {
            final Path inputPath1 = Paths.get("src/test/resources/project_model_traversal/app.ear");

            runTest(context, Collections.singleton(inputPath1.toString()));

            final List<String> pathList = new ArrayList<>();
            FileFoundCallback fileFoundCallback = new FileFoundCallback()
            {
                @Override
                public void fileFound(ProjectModelTraversal traversal, FileModel fileModel)
                {
                    if (!(fileModel instanceof ArchiveModel))
                        return;

                    String path = traversal.getFilePath(fileModel);

                    pathList.add(path);
                }
            };

            WindupConfigurationModel windupConfiguration = WindupConfigurationService.getConfigurationModel(context);
            for (FileModel inputApplication : windupConfiguration.getInputPaths())
            {
                System.out.println("---------------------------------------------");
                System.out.println("Input App: " + inputApplication.getFileName() + ", project: " + inputApplication.getProjectModel().getName());
                traverseRoots(new ProjectModelTraversal(inputApplication.getProjectModel()), fileFoundCallback);
            }

            Assert.assertTrue(pathList.contains("app.ear/xercesImpl-2.11.0.jar"));
            Assert.assertTrue(pathList.contains("app.ear/xercesImpl-other.jar"));
            System.out.println("Done!");
        }
        finally
        {
            FileUtils.deleteDirectory(tempDirectory.toFile());
        }
    }

    private void traverseRoots(ProjectModelTraversal traversal, FileFoundCallback callback)
    {
        callback.fileFound(traversal, traversal.getCurrent().getRootFileModel());

        for (ProjectModelTraversal child : traversal.getChildren())
        {
            traverseRoots(child, callback);
        }
    }

    private void traverse(ProjectModelTraversal traversal, int indentLevel, FileFoundCallback callback)
    {
        String indent = StringUtils.repeat(" ", indentLevel * 3);

        System.out.println(indent + "Project: " + traversal.getCanonicalProject().getName());
        System.out.println("Root file: " + traversal.getFilePath(traversal.getCurrent().getRootFileModel()));
        System.out.println(indent + "Files: ");
        for (FileModel fileModel : traversal.getCanonicalProject().getFileModels())
        {
            System.out.println(indent + "   " + traversal.getFilePath(fileModel));
            callback.fileFound(traversal, fileModel);
        }

        for (ProjectModelTraversal child : traversal.getChildren())
        {
            traverse(child, indentLevel + 1, callback);
        }
    }

    void runTest(final GraphContext graphContext, Iterable<String> inputPaths) throws Exception
    {
        WindupConfiguration windupConfiguration = new WindupConfiguration().setGraphContext(graphContext);
        windupConfiguration.setAlwaysHaltOnException(true);
        windupConfiguration.setOptionValue(KeepWorkDirsOption.NAME, true);
        for (String inputPath : inputPaths)
        {
            windupConfiguration.addInputPath(Paths.get(inputPath));
        }
        windupConfiguration.setOutputDirectory(graphContext.getGraphDirectory());

        processor.execute(windupConfiguration);
    }

    private interface FileFoundCallback
    {
        void fileFound(ProjectModelTraversal traversal, FileModel fileModel);
    }
}
