package org.javamodularity.moduleplugin.extensions;

import java.util.List;
import org.gradle.api.JavaVersion;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.tasks.compile.JavaCompile;
import org.javamodularity.moduleplugin.JavaProjectHelper;
import org.javamodularity.moduleplugin.tasks.ClasspathFile;

public class DefaultModularityExtension implements ModularityExtension {

    private final Project project;
    private final OptionContainer optionContainer = new OptionContainer();

    public DefaultModularityExtension(Project project) {
        this.project = project;
    }

    @Override
    public void standardJavaRelease(int mainJavaRelease) {
        if (mainJavaRelease < 9) {
            throw new IllegalArgumentException(String.format(
                    "Invalid main --release value: %d. Use 'mixedJavaRelease' instead.", mainJavaRelease
            ));
        }
        project.afterEvaluate(p -> configureStandardJavaRelease(mainJavaRelease));
    }

    private void configureStandardJavaRelease(int mainJavaRelease) {
        JavaCompile compileJava = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME);
        setJavaRelease(compileJava, mainJavaRelease);
    }

    @Override
    public void mixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease) {
        validateMixedJavaReleaseArgs(mainJavaRelease, moduleInfoJavaRelease);

        CompileModuleOptions moduleOptions = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME)
                .getExtensions().getByType(CompileModuleOptions.class);
        moduleOptions.setCompileModuleInfoSeparately(true);

        project.afterEvaluate(p -> configureMixedJavaRelease(mainJavaRelease, moduleInfoJavaRelease));
    }

    @Override
    public OptionContainer optionContainer() {
        return optionContainer;
    }

    private static void validateMixedJavaReleaseArgs(int mainJavaRelease, int moduleInfoJavaRelease) {
        if (mainJavaRelease < 6) {
            throw new IllegalArgumentException("Invalid main --release value: " + mainJavaRelease);
        }
        if (mainJavaRelease > 8) {
            throw new IllegalArgumentException(String.format(
                    "Invalid main --release value: %d. Use 'standardJavaRelease' instead.", mainJavaRelease
            ));
        }
        if (moduleInfoJavaRelease < 9) {
            throw new IllegalArgumentException("Invalid module-info --release value: " + moduleInfoJavaRelease);
        }
    }

    private void configureMixedJavaRelease(int mainJavaRelease, int moduleInfoJavaRelease) {
        var compileJava = helper().compileJavaTask(JavaPlugin.COMPILE_JAVA_TASK_NAME);
        setJavaRelease(compileJava, mainJavaRelease);

        var compileModuleInfoJava = helper().compileJavaTask(CompileModuleOptions.COMPILE_MODULE_INFO_TASK_NAME);
        setJavaRelease(compileModuleInfoJava, moduleInfoJavaRelease);
    }

    // TODO: Remove this method when Gradle supports it natively:  https://github.com/gradle/gradle/issues/2510
    private void setJavaRelease(JavaCompile javaCompile, int javaRelease) {
        String currentJavaVersion = JavaVersion.current().toString();
        if (!javaCompile.getSourceCompatibility().equals(currentJavaVersion)) {
            throw new IllegalStateException("sourceCompatibility should not be set together with --release option");
        }
        if (!javaCompile.getTargetCompatibility().equals(currentJavaVersion)) {
            throw new IllegalStateException("targetCompatibility should not be set together with --release option");
        }

        List<String> compilerArgs = javaCompile.getOptions().getCompilerArgs();
        if (compilerArgs.contains("--release")) {
            throw new IllegalStateException("--release option is already set in compiler args");
        }

        compilerArgs.add("--release");
        compilerArgs.add(String.valueOf(javaRelease));
    }

    private JavaProjectHelper helper() {
        return new JavaProjectHelper(project);
    }

    @Override
    public void improveEclipseClasspathFile() {
        project.afterEvaluate(p -> {
            helper().findTask("eclipseClasspath", Task.class)
                    .ifPresent(new ClasspathFile()::configure);

        });
    }
}