# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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.

import os

from .command import Command, default_bin


class Maven(Command):
    def __init__(self, maven_bin=None):
        self.bin = default_bin(maven_bin, "mvn")


maven = Maven()


class MavenDefinition:
    """ MavenDefinition captures the maven invocation arguments.

    It allows creating build directories with the same definition, e.g.
    ```
    build_1 = maven_def.build("/tmp/build-1")
    build_2 = maven_def.build("/tmp/build-2")

    ...

    build1.install()
    build2.install()
    """

    def __init__(self, source, build_definitions=None,
                 benchmark_definitions=None, env=None):
        """ Initialize a MavenDefinition

        Parameters
        ----------
        source : str
                 Source directory where the top-level pom.xml is
                 located. This is usually the root of the project.
        build_definitions: list(str), optional
        benchmark_definitions: list(str), optional
        """
        self.source = os.path.abspath(source)
        self.build_definitions = build_definitions if build_definitions else []
        self.benchmark_definitions =\
            benchmark_definitions if benchmark_definitions else []
        self.env = env

    @property
    def build_arguments(self):
        """" Return the arguments to maven invocation for build. """
        arguments = self.build_definitions + [
            "-B", "-DskipTests", "-Drat.skip=true",
            "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer."
            "Slf4jMavenTransferListener=warn",
            "-T", "2C", "install"
        ]
        return arguments

    def build(self, build_dir, force=False, cmd_kwargs=None, **kwargs):
        """ Invoke maven into a build directory.

        Parameters
        ----------
        build_dir : str
                    Directory in which the Maven build will be instantiated.
        force : bool
                not used now
        """
        if os.path.exists(build_dir):
            # Extra safety to ensure we're deleting a build folder.
            if not MavenBuild.is_build_dir(build_dir):
                raise FileExistsError(
                    f"{build_dir} is not a maven build"
                )

        cmd_kwargs = cmd_kwargs if cmd_kwargs else {}
        assert MavenBuild.is_build_dir(build_dir)
        maven(*self.build_arguments, cwd=build_dir, env=self.env, **cmd_kwargs)
        return MavenBuild(build_dir, definition=self, **kwargs)

    @property
    def list_arguments(self):
        """" Return the arguments to maven invocation for list """
        arguments = [
            "-Dskip.perf.benchmarks=false", "-Dbenchmark.list=-lp", "install"
        ]
        return arguments

    @property
    def benchmark_arguments(self):
        """" Return the arguments to maven invocation for benchmark """
        arguments = self.benchmark_definitions + [
            "-Dskip.perf.benchmarks=false", "-Dbenchmark.fork=1",
            "-Dbenchmark.jvmargs=\"-Darrow.enable_null_check_for_get=false "
            "-Darrow.enable_unsafe_memory_access=true\"",
            "install"
        ]
        return arguments

    def __repr__(self):
        return f"MavenDefinition[source={self.source}]"


class MavenBuild(Maven):
    """ MavenBuild represents a build directory initialized by maven.

    The build instance can be used to build/test/install. It alleviates the
    user to know which generator is used.
    """

    def __init__(self, build_dir, definition=None):
        """ Initialize a MavenBuild.

        The caller must ensure that maven was invoked in the build directory.

        Parameters
        ----------
        definition : MavenDefinition
                     The definition to build from.
        build_dir : str
                    The build directory to setup into.
        """
        assert MavenBuild.is_build_dir(build_dir)
        super().__init__()
        self.build_dir = os.path.abspath(build_dir)
        self.definition = definition

    @property
    def binaries_dir(self):
        return self.build_dir

    def run(self, *argv, verbose=False, cwd=None, **kwargs):
        extra = []
        if verbose:
            extra.append("-X")
        if cwd is None:
            cwd = self.build_dir
        # Commands must be ran under the directory where pom.xml exists
        return super().run(*extra, *argv, **kwargs, cwd=cwd)

    def build(self, *argv, verbose=False, **kwargs):
        definition_args = self.definition.build_arguments
        cwd = self.binaries_dir
        return self.run(*argv, *definition_args, verbose=verbose, cwd=cwd,
                        env=self.definition.env, **kwargs)

    def list(self, *argv, verbose=False, **kwargs):
        definition_args = self.definition.list_arguments
        cwd = self.binaries_dir + "/performance"
        return self.run(*argv, *definition_args, verbose=verbose, cwd=cwd,
                        env=self.definition.env, **kwargs)

    def benchmark(self, *argv, verbose=False, **kwargs):
        definition_args = self.definition.benchmark_arguments
        cwd = self.binaries_dir + "/performance"
        return self.run(*argv, *definition_args, verbose=verbose, cwd=cwd,
                        env=self.definition.env, **kwargs)

    @staticmethod
    def is_build_dir(path):
        """ Indicate if a path is Maven top directory.

        This method only checks for the existence of paths and does not do any
        validation whatsoever.
        """
        pom_xml = os.path.join(path, "pom.xml")
        performance_dir = os.path.join(path, "performance")
        return os.path.exists(pom_xml) and os.path.isdir(performance_dir)

    @staticmethod
    def from_path(path):
        """ Instantiate a Maven from a path.

        This is used to recover from an existing physical directory (created
        with or without Maven).

        Note that this method is not idempotent as the original definition will
        be lost.
        """
        if not MavenBuild.is_build_dir(path):
            raise ValueError(f"Not a valid MavenBuild path: {path}")

        return MavenBuild(path, definition=None)

    def __repr__(self):
        return ("MavenBuild["
                f"build = {self.build_dir},"
                f"definition = {self.definition}]")
