/*
 * JBoss, Home of Professional Open Source
 * Copyright 2016, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * 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.jboss.modules;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URI;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

/**
 * Test absolute and relative paths in resource-roots' path attribute (MODULES-218)
 * @author Martin Simka
 */
public class ResourceRootPathsTest extends AbstractModuleTestCase {

    @Rule
    public TemporaryFolder tmpDir = new TemporaryFolder();
    private File repoRoot;
    private File testModuleRoot;
    private File fileResourceRoot;

    private static final String MODULE_XML_TEMPLATE =
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
            "<module xmlns=\"urn:jboss:module:1.5\" name=\"test.test\">\n" +
            "    <resources>\n" +
            "        <resource-root name=\"relativejar\" path=\"relative.jar\" />\n" +
            "        <resource-root name=\"absolutejar\" path=\"@absolutejar@\" />\n" +
            "        <resource-root name=\"relativedir\" path=\"relativedir\" />\n" +
            "        <resource-root name=\"absolutedir\" path=\"@absolutedir@\" />\n" +
            "    </resources>\n" +
            "</module>";

    private ModuleLoader moduleLoader;

    @Before
    public void setupModuleLoader() throws Exception {
        repoRoot = tmpDir.newFolder("repo");
        testModuleRoot = new File(repoRoot, "test/test/main/");

        // Build a jar in module
        copyResource("org/jboss/modules/test/TestClass.class", "test/fileresourceloader", "org/jboss/modules/test");
        fileResourceRoot = getResource("test/fileresourceloader");
        final File jarInModule = new File(testModuleRoot, "relative.jar");
        jarInModule.getParentFile().mkdirs();
        JarResourceLoaderTest.buildJar(fileResourceRoot, jarInModule);

        // Build a jar outside of module
        final File jarOutsideModule = new File(repoRoot, "relative.jar");
        JarResourceLoaderTest.buildJar(fileResourceRoot, jarOutsideModule);

        // copy resource dir to module
        copyDir(fileResourceRoot, new File(testModuleRoot, "relativedir"));

        //create module.xml
        final File moduleXml = new File(testModuleRoot, "module.xml");
        String moduleXmlContent = MODULE_XML_TEMPLATE.replaceAll("@absolutejar@", jarOutsideModule.getCanonicalPath().replace(File.separatorChar, '/'));
        moduleXmlContent = moduleXmlContent.replaceAll("@absolutedir@", fileResourceRoot.getCanonicalPath().replace(File.separatorChar, '/'));
        Files.write(moduleXml.toPath(), moduleXmlContent.getBytes(), StandardOpenOption.CREATE_NEW);

        moduleLoader = new LocalModuleLoader(new File[] {repoRoot});
    }

    @Test
    public void testPaths() throws Exception {
        ConcreteModuleSpec moduleSpec = (ConcreteModuleSpec) moduleLoader.findModule("test.test");
        ResourceLoaderSpec[] resourceLoaders = moduleSpec.getResourceLoaders();
        int checkCount = 0;
        for (ResourceLoaderSpec r : resourceLoaders) {
            ResourceLoader resourceLoader = r.getResourceLoader();
            if (resourceLoader instanceof JarFileResourceLoader) {
                // validate jar with relative path
                if(resourceLoader.getRootName().equals("relativejar")) {
                    checkCount++;
                    final File relativeJar = getFileFromJarUri(resourceLoader.getLocation());
                    Assert.assertEquals(testModuleRoot, relativeJar.getParentFile());
                    continue;
                }

                // validate jar with absolute path
                if(resourceLoader.getRootName().equals("absolutejar")) {
                    checkCount++;
                    final File absoluteJar = getFileFromJarUri(resourceLoader.getLocation());
                    Assert.assertEquals(repoRoot, absoluteJar.getParentFile());
                    continue;
                }
            } else if (resourceLoader instanceof PathResourceLoader) {

                // validate dir with relative path
                if(resourceLoader.getRootName().equals("relativedir")) {
                    checkCount++;
                    final File relativeDir = new File(resourceLoader.getLocation());
                    Assert.assertEquals(testModuleRoot, relativeDir.getParentFile());
                    continue;
                }

                // validate dir with absolute path
                if(resourceLoader.getRootName().equals("absolutedir")) {
                    checkCount++;
                    final File absoluteDir = new File(resourceLoader.getLocation());
                    Assert.assertEquals(fileResourceRoot.getParentFile(), absoluteDir.getParentFile());
                    continue;
                }
            }
        }
        Assert.assertEquals("Test should have checked 4 ResourceLoaders", 4, checkCount);
    }

    private File getFileFromJarUri(URI uri) throws Exception {
        JarURLConnection connection = (JarURLConnection) uri.toURL().openConnection();
        File file = new File(connection.getJarFileURL().toURI());
        return file;
    }

    static void copyDir(File sourceDir, File targetDir) throws Exception {

        abstract class CopyFileVisitor implements FileVisitor<Path> {
            boolean isFirst = true;
            Path ptr;
        }

        CopyFileVisitor copyVisitor = new CopyFileVisitor() {

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                if (!isFirst) {
                    Path target = ptr.resolve(dir.getName(dir.getNameCount() - 1));
                    ptr = target;
                }
                Files.copy(dir, ptr, StandardCopyOption.COPY_ATTRIBUTES);
                isFirst = false;
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Path target = ptr.resolve(file.getFileName());
                Files.copy(file, target, StandardCopyOption.COPY_ATTRIBUTES);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                throw exc;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                Path target = ptr.getParent();
                ptr = target;
                return FileVisitResult.CONTINUE;
            }
        };

        copyVisitor.ptr = targetDir.toPath();
        Files.walkFileTree(sourceDir.toPath(), copyVisitor);
    }
}
