/*
 * Copyright 2020 the original author or authors.
 *
 * 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.gradle.internal.jvm.inspection;

import com.google.common.base.Joiner;
import com.google.common.io.Files;
import org.gradle.api.JavaVersion;
import org.gradle.internal.os.OperatingSystem;
import org.gradle.process.ExecResult;
import org.gradle.process.internal.ExecException;
import org.gradle.process.internal.ExecHandle;
import org.gradle.process.internal.ExecHandleBuilder;
import org.gradle.process.internal.ExecHandleFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.EnumMap;
import java.util.Map;

import static java.lang.String.format;



public class DefaultJvmMetadataDetector implements JvmMetadataDetector {

    private final ExecHandleFactory execHandleFactory;

    public DefaultJvmMetadataDetector(ExecHandleFactory execHandleFactory) {
        this.execHandleFactory = execHandleFactory;
    }

    @Override
    public JvmInstallationMetadata getMetadata(File javaHome) {
        if (!javaHome.exists()) {
            return JvmInstallationMetadata.failure(javaHome, "No such directory: " + javaHome);
        }
        EnumMap<ProbedSystemProperty, String> metadata = getMetadataFromInstallation(javaHome);
        return asMetadata(javaHome, metadata);
    }

    private JvmInstallationMetadata asMetadata(File javaHome, EnumMap<ProbedSystemProperty, String> metadata) {
        String implementationVersion = metadata.get(ProbedSystemProperty.VERSION);
        try {
            JavaVersion.toVersion(implementationVersion);
        } catch(IllegalArgumentException e) {
            return JvmInstallationMetadata.failure(javaHome, "Cannot parse version number: " + implementationVersion);
        }
        if (implementationVersion == null) {
            return JvmInstallationMetadata.failure(javaHome, metadata.get(ProbedSystemProperty.Z_ERROR));
        }
        String vendor = metadata.get(ProbedSystemProperty.VENDOR);
        String implementationName = metadata.get(ProbedSystemProperty.VM);
        return JvmInstallationMetadata.from(javaHome, implementationVersion, vendor, implementationName);
    }

    private EnumMap<ProbedSystemProperty, String> getMetadataFromInstallation(File jdkPath) {
        File workingDir = Files.createTempDir();
        File classfile = new MetadataProbe().writeClass(workingDir);
        ExecHandleBuilder exec = execHandleFactory.newExec();
        exec.setWorkingDir(workingDir);
        exec.executable(javaExecutable(jdkPath));
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ByteArrayOutputStream errorOutput = new ByteArrayOutputStream();
            exec.args(Files.getNameWithoutExtension(classfile.getName()));
            exec.setStandardOutput(out);
            exec.setErrorOutput(errorOutput);
            exec.setIgnoreExitValue(true);
            ExecHandle execHandle = exec.build();
            ExecResult result = execHandle.start().waitForFinish();

            // TODO [bm] remove
            Map<String, String> environment = execHandle.getEnvironment();
            System.out.println("Detector executed: " + execHandle.getCommand() + " " + Joiner.on(" ").join(execHandle.getArguments()));
            System.out.println("Classfile: " + classfile.getAbsolutePath());
            System.out.println("Working directory: " + execHandle.getDirectory());
            System.out.println("Environment vars:");
            System.out.println(format("    JAVA_HOME: %s", environment.get("JAVA_HOME")));
            System.out.println(format("    GRADLE_HOME: %s", environment.get("GRADLE_HOME")));
            System.out.println(format("    GRADLE_USER_HOME: %s", environment.get("GRADLE_USER_HOME")));
            System.out.println(format("    JAVA_OPTS: %s", environment.get("JAVA_OPTS")));
            System.out.println(format("    GRADLE_OPTS: %s", environment.get("GRADLE_OPTS")));


            int exitValue = result.getExitValue();
            if (exitValue == 0) {
                return parseExecOutput(out.toString());
            }
            return error("Command returned unexpected result code: " + exitValue + "\nError output:\n" + errorOutput);
        } catch (ExecException ex) {
            return error(ex.getMessage());
        } finally {
            classfile.deleteOnExit();
            workingDir.deleteOnExit();
        }
    }


    private static File javaExecutable(File jdkPath) {
        return new File(new File(jdkPath, "bin"), OperatingSystem.current().getExecutableName("java"));
    }

    private static EnumMap<ProbedSystemProperty, String> parseExecOutput(String probeResult) {
        String[] split = probeResult.split(System.getProperty("line.separator"));
        if (split.length != ProbedSystemProperty.values().length - 1) { // -1 because of Z_ERROR
            return error("Unexpected command output: \n" + probeResult);
        }
        EnumMap<ProbedSystemProperty, String> result = new EnumMap<>(ProbedSystemProperty.class);
        for (ProbedSystemProperty type : ProbedSystemProperty.values()) {
            if (type != ProbedSystemProperty.Z_ERROR) {
                result.put(type, split[type.ordinal()].trim());
            }
        }
        return result;
    }

    private static EnumMap<ProbedSystemProperty, String> error(String message) {
        EnumMap<ProbedSystemProperty, String> result = new EnumMap<>(ProbedSystemProperty.class);
        result.put(ProbedSystemProperty.Z_ERROR, message);
        return result;
    }

}

@@ -659,46 +659,8 @@
 n;%0A%0A
-import com.google.common.base.Joiner;%0A
 impo
@@ -690,16 +690,16 @@
 .Files;%0A
+
 import o
@@ -864,55 +864,8 @@
 on;%0A
-import org.gradle.process.internal.ExecHandle;%0A
 impo
@@ -1028,16 +1028,16 @@
 o.File;%0A
+
 import j
@@ -1058,71 +1058,8 @@
 ap;%0A
-import java.util.Map;%0A%0Aimport static java.lang.String.format;%0A%0A
 %0A%0Apu
@@ -3297,1045 +3297,61 @@
 Exec
-Handle execHandle = exec.build();%0A            ExecResult result = execHandle.start().waitForFinish();%0A%0A            // TODO %5Bbm%5D remove%0A            Map%3CString, String%3E environment = execHandle.getEnvironment();%0A            System.out.println(%22Detector executed: %22 + execHandle.getCommand() + %22 %22 + Joiner.on(%22 %22).join(execHandle.getArguments()));%0A            System.out.println(%22Classfile: %22 + classfile.getAbsolutePath());%0A            System.out.println(%22Working directory: %22 + execHandle.getDirectory());%0A            System.out.println(%22Environment vars:%22);%0A            System.out.println(format(%22    JAVA_HOME: %25s%22, environment.get(%22JAVA_HOME%22)));%0A            System.out.println(format(%22    GRADLE_HOME: %25s%22, environment.get(%22GRADLE_HOME%22)));%0A            System.out.println(format(%22    GRADLE_USER_HOME: %25s%22, environment.get(%22GRADLE_USER_HOME%22)));%0A            System.out.println(format(%22    JAVA_OPTS: %25s%22, environment.get(%22JAVA_OPTS%22)));%0A            System.out.println(format(%22    GRADLE_OPTS: %25s%22, environment.get(%22GRADLE_OPTS%22)));%0A%0A
+Result result = exec.build().start().waitForFinish();
 %0A   

Revert "Enable debug logging"