# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

import os
import sys

from mozprofile import MozProfileCLI

from .application import get_app_context
from .runners import runners
from .utils import findInPath

# Map of debugging programs to information about them
# from http://mxr.mozilla.org/mozilla-central/source/build/automationutils.py#59
DEBUGGERS = {
    "gdb": {
        "interactive": True,
        "args": ["-q", "--args"],
    },
    "valgrind": {"interactive": False, "args": ["--leak-check=full"]},
}


def debugger_arguments(debugger, arguments=None, interactive=None):
    """Finds debugger arguments from debugger given and defaults

    :param debugger: name or path to debugger
    :param arguments: arguments for the debugger, or None to use defaults
    :param interactive: whether the debugger should run in interactive mode

    """
    # find debugger executable if not a file
    executable = debugger
    if not os.path.exists(executable):
        executable = findInPath(debugger)
    if executable is None:
        raise Exception("Path to '%s' not found" % debugger)

    # if debugger not in dictionary of knowns return defaults
    dirname, debugger = os.path.split(debugger)
    if debugger not in DEBUGGERS:
        return ([executable] + (arguments or []), bool(interactive))

    # otherwise use the dictionary values for arguments unless specified
    if arguments is None:
        arguments = DEBUGGERS[debugger].get("args", [])
    if interactive is None:
        interactive = DEBUGGERS[debugger].get("interactive", False)
    return ([executable] + arguments, interactive)


class CLI(MozProfileCLI):
    """Command line interface"""

    module = "mozrunner"

    def __init__(self, args=sys.argv[1:]):
        MozProfileCLI.__init__(self, args=args)

        # choose appropriate runner and profile classes
        app = self.options.app
        try:
            self.runner_class = runners[app]
            self.profile_class = get_app_context(app).profile_class
        except KeyError:
            self.parser.error(
                'Application "%s" unknown (should be one of "%s")'
                % (app, ", ".join(runners.keys()))
            )

    def add_options(self, parser):
        """add options to the parser"""
        parser.description = (
            "Reliable start/stop/configuration of Mozilla"
            " Applications (Firefox, Thunderbird, etc.)"
        )

        # add profile options
        MozProfileCLI.add_options(self, parser)

        # add runner options
        parser.add_option(
            "-b",
            "--binary",
            dest="binary",
            help="Binary path.",
            metavar=None,
            default=None,
        )
        parser.add_option(
            "--app",
            dest="app",
            default="firefox",
            help="Application to use [DEFAULT: %default]",
        )
        parser.add_option(
            "--app-arg",
            dest="appArgs",
            default=[],
            action="append",
            help="provides an argument to the test application",
        )
        parser.add_option(
            "--debugger",
            dest="debugger",
            help="run under a debugger, e.g. gdb or valgrind",
        )
        parser.add_option(
            "--debugger-args",
            dest="debugger_args",
            action="store",
            help="arguments to the debugger",
        )
        parser.add_option(
            "--interactive",
            dest="interactive",
            action="store_true",
            help="run the program interactively",
        )

    # methods for running

    def command_args(self):
        """additional arguments for the mozilla application"""
        # pylint --py3k: W1636
        return list(map(os.path.expanduser, self.options.appArgs))

    def runner_args(self):
        """arguments to instantiate the runner class"""
        return dict(cmdargs=self.command_args(), binary=self.options.binary)

    def create_runner(self):
        profile = self.profile_class(**self.profile_args())
        return self.runner_class(profile=profile, **self.runner_args())

    def run(self):
        runner = self.create_runner()
        self.start(runner)
        runner.cleanup()

    def debugger_arguments(self):
        """Get the debugger arguments

        returns a 2-tuple of debugger arguments:
            (debugger_arguments, interactive)

        """
        debug_args = self.options.debugger_args
        if debug_args is not None:
            debug_args = debug_args.split()
        interactive = self.options.interactive
        if self.options.debugger:
            debug_args, interactive = debugger_arguments(
                self.options.debugger, debug_args, interactive
            )
        return debug_args, interactive

    def start(self, runner):
        """Starts the runner and waits for the application to exit

        It can also happen via a keyboard interrupt. It should be
        overwritten to provide custom running of the runner instance.

        """
        # attach a debugger if specified
        debug_args, interactive = self.debugger_arguments()
        runner.start(debug_args=debug_args, interactive=interactive)
        print("Starting: " + " ".join(runner.command))
        try:
            runner.wait()
        except KeyboardInterrupt:
            runner.stop()


def cli(args=sys.argv[1:]):
    CLI(args).run()


if __name__ == "__main__":
    cli()
