import os
import re

from osbenchmark.exceptions import SystemSetupError
from osbenchmark.utils import io


class JdkResolver:
    SYS_PROP_REGEX = r".*%s.*=\s?(.*)"

    def __init__(self, executor):
        self.executor = executor

    def resolve_jdk_path(self, host, majors):
        """
        Resolves the path to the JDK with the provided major version(s). It checks the versions in the same order specified in ``majors``
        and will return the first match. To achieve this, it first checks the major version x in the environment variable ``JAVAx_HOME``
        and falls back to ``JAVA_HOME``. It also ensures that the environment variable points to the right JDK version.

        If no appropriate version is found, a ``SystemSetupError`` is raised.

        :param host: The host on which to resolve the JDK path
        :param majors: Either a list of major versions to check or a single version as an ``int``.
        :return: A tuple of (major version, path to Java home directory).
        """
        if isinstance(majors, int):
            return majors, self._resolve_jdk_path(host, [majors])
        else:
            return majors, self._resolve_jdk_path(host, majors)

    def _resolve_jdk_path(self, host, majors):
        """
        Resolves the path to a JDK with one of the provided major versions.

        :param majors: The major versions to check.
        :return: The resolved path to the JDK
        """

        defined_env_vars = self._get_defined_env_vars(host)
        java_home_env_var_names = [f"JAVA{major}_HOME" for major in majors]
        java_home_env_var_names.append("JAVA_HOME")

        resolved_major_to_java_home_path = {}
        for java_home_env_var_name in java_home_env_var_names:
            if java_home_env_var_name in defined_env_vars:
                major_to_java_home_path = self._resolve_major_from_java_home(host, java_home_env_var_name,
                                                                             defined_env_vars[java_home_env_var_name])
                if major_to_java_home_path:
                    resolved_major_to_java_home_path.update(major_to_java_home_path)

        for major in majors:
            if major in resolved_major_to_java_home_path:
                return resolved_major_to_java_home_path[major]

        checked_env_vars = self._checked_env_vars(majors)
        raise SystemSetupError(f"Install a JDK with one of the versions {majors} and point to it with one of {checked_env_vars}.")

    def _get_defined_env_vars(self, host):
        env_vars_as_strings = self.executor.execute(host, "printenv", output=True)
        return dict(env_var_as_string.split("=") for env_var_as_string in env_vars_as_strings)

    def _resolve_major_from_java_home(self, host, java_home_env_var_name, java_home_env_var_value):
        if java_home_env_var_value:
            major_version = self._major_version(host, java_home_env_var_value)
            if java_home_env_var_name in ("JAVA_HOME", f"JAVA{major_version}_HOME"):
                return {major_version: java_home_env_var_value}

    def _major_version(self, host, java_home):
        """
        Determines the major version number of JDK available at the provided JAVA_HOME directory.

        :param java_home: The JAVA_HOME directory to check.
        :return: An int, representing the major version number of the JDK available at ``java_home``.
        """
        version = self._system_property(host, java_home, "java.vm.specification.version")
        # are we under the "old" (pre Java 9) or the new (Java 9+) version scheme?
        if version.startswith("1."):
            return int(version[2])
        else:
            return int(version)

    def _system_property(self, host, java_home, system_property_name):
        lines = self.executor.execute(host, f"{self._java(java_home)} -XshowSettings:properties -version", output=True)
        # matches e.g. "    java.runtime.version = 1.8.0_121-b13" and captures "1.8.0_121-b13"
        sys_prop_pattern = re.compile(JdkResolver.SYS_PROP_REGEX % system_property_name)
        for line in lines:
            m = sys_prop_pattern.match(line)
            if m:
                return m.group(1)

        return None

    def _java(self, java_home):
        return io.escape_path(os.path.join(java_home, "bin", "java"))

    def _checked_env_vars(self, majors):
        """
        Provides a list of environment variables that are checked for the given list of major versions.

        :param majors: A list of major versions.
        :return: A list of checked environment variables.
        """
        checked = [f"JAVA{major}_HOME" for major in majors]
        checked.append("JAVA_HOME")
        return checked