/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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.shrinkwrap.impl.gradle.archive.importer.embedded;

import java.io.File;
import java.io.FilenameFilter;
import java.net.URI;

import org.gradle.tooling.BuildLauncher;
import org.gradle.tooling.GradleConnector;
import org.gradle.tooling.ProjectConnection;
import org.gradle.tooling.model.DomainObjectSet;
import org.gradle.tooling.model.GradleProject;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.Assignable;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.gradle.archive.importer.embedded.ConfigurationStage;
import org.jboss.shrinkwrap.api.gradle.archive.importer.embedded.DistributionConfigurationStage;
import org.jboss.shrinkwrap.api.gradle.archive.importer.embedded.EmbeddedGradleImporter;
import org.jboss.shrinkwrap.api.importer.ZipImporter;
import org.jboss.shrinkwrap.impl.base.Validate;

/**
 * @author <a href="mailto:mmatloka@gmail.com">Michal Matloka</a>
 */
public class EmbeddedGradleImporterImpl implements EmbeddedGradleImporter, DistributionConfigurationStage {

    private static final String SAX_PARSER_FACTORY_KEY = "javax.xml.parsers.SAXParserFactory";

    private final Archive<?> archive;

    private final GradleConnector connector = GradleConnector.newConnector();

    private String[] tasks = new String[] { "build" };
    private String[] arguments = new String[] { "--exclude-task", "test" };

    private BuildLauncher buildLauncher;

    private ProjectConnection projectConnection;

    private String projectName;

    private File buildResult;

    private BuildLauncher getBuildLauncher() {
        if (buildLauncher == null) {
            projectConnection = connector.connect();
            buildLauncher = projectConnection.newBuild();
        }
        return buildLauncher;
    }

    public EmbeddedGradleImporterImpl(Archive<?> archive) {
        this.archive = archive;
    }

    @Override
    public DistributionConfigurationStage forProjectDirectory(final File projectDir) {
        Validate.notNull(projectDir, "Project directory file can not be null!");

        final File absoluteFile = projectDir.getAbsoluteFile();
        if (!absoluteFile.exists()) {
            throw new IllegalArgumentException("Given project dir do not exist: " + absoluteFile);
        } else if (!absoluteFile.isDirectory()) {
            throw new IllegalArgumentException("Given project dir is not a directory" + absoluteFile);
        }

        projectName = absoluteFile.getName();
        connector.forProjectDirectory(absoluteFile);
        return this;
    }

    @Override
    public DistributionConfigurationStage forProjectDirectory(final String projectDir) {
        Validate.notNull(projectDir, "Project directory path can not be null or empty");

        return this.forProjectDirectory(new File(projectDir));
    }

    @Override
    public DistributionConfigurationStage forThisProjectDirectory() {
        return forProjectDirectory("");
    }

    @Override
    public <TYPE extends Assignable> TYPE as(final Class<TYPE> clazz) {
        final File result;
        if (buildResult == null) {
            result = importFromDefaultLibsDirectory();
        } else {
            result = importFromConfiguredPath();
        }
        return ShrinkWrap.create(ZipImporter.class, archive.getName()).importFrom(result).as(clazz);
    }

    private File importFromDefaultLibsDirectory() {
        final GradleProject currentGradleProject = findCurrentGradleProject();
        final File buildDir = currentGradleProject.getBuildDirectory();
        final File libsDir = new File(buildDir, "libs");
        final File[] results = libsDir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(final File dir, final String name) {
                return name.startsWith(currentGradleProject.getName());
            }
        });

        if (results == null || results.length == 0) {
            throw new IllegalArgumentException(
                "Wrong project directory is used. Tests have to be run from working directory which is a current sub-module directory.");
        }

        return results[0];
    }

    private File importFromConfiguredPath() {
        return buildResult;
    }

    private GradleProject findCurrentGradleProject() {
        final GradleProject rootGradleProject = projectConnection.getModel(GradleProject.class);
        if (!rootGradleProject.getName().equals(projectName)) {
            final GradleProject child = findChildProject(rootGradleProject, projectName);
            if (child != null) {
                return child;

            }
        }
        return rootGradleProject;
    }

    private GradleProject findChildProject(GradleProject gradleProject, String childProjectName) {
        final DomainObjectSet<? extends GradleProject> children = gradleProject.getChildren();
        for (GradleProject child : children) {
            if (child.getName().equals(childProjectName)) {
                return child;
            }
        }

        for (GradleProject child : children) {
            final GradleProject foundChild = findChildProject(child, childProjectName);
            if (foundChild != null) {
                return foundChild;
            }
        }

        return null;
    }

    @Override
    public ConfigurationStage useGradleVersion(final String version) {
        connector.useGradleVersion(version);
        return this;
    }

    @Override
    public ConfigurationStage useDistribution(final URI gradleDistribution) {
        connector.useDistribution(gradleDistribution);
        return this;
    }

    @Override
    public ConfigurationStage useInstallation(final File gradleHome) {
        connector.useInstallation(gradleHome);
        return this;
    }

    @Override
    public ConfigurationStage useDefaultDistribution() {
        return this;
    }

    @Override
    public ConfigurationStage useGradleUserHomeDir(final File gradleUserHomeDir) {
        connector.useGradleUserHomeDir(gradleUserHomeDir);
        return this;
    }

    @Override
    public ConfigurationStage forTasks(final String... tasks) {
        this.tasks = tasks;
        return this;
    }

    @Override
    public ConfigurationStage withArguments(final String... arguments) {
        this.arguments = arguments;
        return this;
    }

    @Override
    public ConfigurationStage setJavaHome(final File javaHome) {
        getBuildLauncher().setJavaHome(javaHome);
        return this;
    }

    @Override
    public ConfigurationStage setJvmArguments(final String... jvmArguments) {
        getBuildLauncher().setJvmArguments(jvmArguments);
        return this;
    }

    @Override
    public Assignable importBuildOutput() {
        final String oldValue = removeSAXParserFactoryProperty();
        getBuildLauncher().forTasks(tasks).withArguments(arguments).run();
        setSAXParserFactoryProperty(oldValue);
        return this;
    }

    @Override
    public Assignable importBuildOutput(File buildResult) {
        this.buildResult = buildResult.getAbsoluteFile();
        return importBuildOutput();
    }

    @Override
    public Assignable importBuildOutput(String buildResult) {
        return importBuildOutput(new File(buildResult));
    }

    private String removeSAXParserFactoryProperty() {
        // solution for https://issues.jboss.org/browse/SHRINKRES-212
        final Object value = System.getProperties().remove(SAX_PARSER_FACTORY_KEY);
        return value != null ? (String) value : null;
    }

    private void setSAXParserFactoryProperty(String oldValue) {
        if (oldValue != null) {
            System.setProperty(SAX_PARSER_FACTORY_KEY, oldValue);
        }
    }
}
