"""A command line interface that exposes softlearning examples to user.

This package exposes the functions in examples.instrument module to the user
through a cli, which allows seamless runs of examples in different modes (e.g.
locally, in google compute engine, or ec2).


There are two types of cli commands in this file (each have their corresponding
function in examples.instrument):
1. run_example_* methods, which run the experiments by invoking
    `tune.run_experiments` function.
2. launch_example_* methods, which are helpers function to submit an
    example to be run in the cloud. In practice, these launch a cluster,
    and then run the `run_example_cluster` method with the provided
    arguments and options.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import logging

import click

from examples.instrument import (
    run_example_dry,
    run_example_local,
    run_example_debug,
    run_example_cluster,
    launch_example_cluster,
    launch_example_gce,
    launch_example_ec2)


logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)


def add_options(options):
    def decorator(f):
        for option in options[::-1]:
            click.decorators._param_memo(f, option)
        return f
    return decorator


@click.group()
def cli():
    pass


@cli.command(
    name='run_example_dry',
    context_settings={'ignore_unknown_options': True})
@click.argument("example_module_name", required=True, type=str)
@click.argument('example_argv', nargs=-1, type=click.UNPROCESSED)
def run_example_dry_cmd(example_module_name, example_argv):
    """Print the variant spec and related information of an example."""
    return run_example_dry(example_module_name, example_argv)


@cli.command(
    name='run_local',
    context_settings={'ignore_unknown_options': True})
@click.argument("example_module_name", required=True, type=str)
@click.argument('example_argv', nargs=-1, type=click.UNPROCESSED)
def run_example_local_cmd(example_module_name, example_argv):
    """Run example locally, potentially parallelizing across cpus/gpus."""
    return run_example_local(example_module_name, example_argv)


@cli.command(
    name='run_example_debug',
    context_settings={'ignore_unknown_options': True})
@click.argument("example_module_name", required=True, type=str)
@click.argument('example_argv', nargs=-1, type=click.UNPROCESSED)
def run_example_debug_cmd(example_module_name, example_argv):
    """The debug mode limits tune trial runs to enable use of debugger."""
    return run_example_debug(example_module_name, example_argv)


@cli.command(
    name='run_example_cluster',
    context_settings={'ignore_unknown_options': True})
@click.argument("example_module_name", required=True, type=str)
@click.argument('example_argv', nargs=-1, type=click.UNPROCESSED)
def run_example_cluster_cmd(example_module_name, example_argv):
    """Run example on cluster mode.

    This functions is very similar to the local mode, except that it
    correctly sets the redis address to make ray/tune work on a cluster.
    """
    run_example_cluster(example_module_name, example_argv)


@cli.command(
    name='launch_example_cluster',
    context_settings={
        'allow_extra_args': True,
        'ignore_unknown_options': True
    })
@click.argument("example_module_name", required=True, type=str)
@click.argument('example_argv', nargs=-1, type=click.UNPROCESSED)
@click.option(
    "--config_file",
    required=False,
    type=str)
@click.option(
    "--stop/--no-stop",
    is_flag=True,
    default=True,
    help="Stop the cluster after the command finishes running.")
@click.option(
    "--start/--no-start",
    is_flag=True,
    default=True,
    help="Start the cluster if needed.")
@click.option(
    "--screen/--no-screen",
    is_flag=True,
    default=False,
    help="Run the command in a screen.")
@click.option(
    "--tmux/--no-tmux",
    is_flag=True,
    default=True,
    help="Run the command in tmux.")
@click.option(
    "--override-cluster-name",
    required=False,
    type=str,
    help="Override the configured cluster name.")
@click.option(
    "--port-forward", required=False, type=int, help="Port to forward.")
def launch_example_cluster_cmd(*args, **kwargs):
    """Launches the example on autoscaled ray cluster through ray exec_cmd.

    This handles basic validation and sanity checks for the experiment, and
    then executes the command on autoscaled ray cluster. If necessary, it will
    also fill in more useful defaults for our workflow (i.e. for tmux and
    override_cluster_name).
    """
    return launch_example_cluster(*args, **kwargs)


@cli.command(
    name='launch_example_gce',
    context_settings={
        'allow_extra_args': True,
        'ignore_unknown_options': True
    })
@add_options(launch_example_cluster_cmd.params)
def launch_example_gce_cmd(*args, **kwargs):
    """Forwards call to `launch_example_cluster` after adding gce defaults.

    This optionally sets the ray autoscaler configuration file to the default
    gce configuration file, and then calls `launch_example_cluster` to
    execute the original command on autoscaled gce cluster by parsing the args.

    See `launch_example_cluster` for further details.
    """
    return launch_example_gce(*args, **kwargs)


@cli.command(
    name='launch_example_ec2',
    context_settings={
        'allow_extra_args': True,
        'ignore_unknown_options': True
    })
@add_options(launch_example_cluster_cmd.params)
def launch_example_ec2_cmd(*args, **kwargs):
    """Forwards call to `launch_example_cluster` after adding ec2 defaults.

    This optionally sets the ray autoscaler configuration file to the default
    ec2 configuration file, and then calls `launch_example_cluster` to
    execute the original command on autoscaled ec2 cluster by parsing the args.

    See `launch_example_cluster` for further details.
    """
    return launch_example_ec2(*args, **kwargs)


cli.add_command(run_example_local_cmd)
cli.add_command(run_example_dry_cmd)
cli.add_command(run_example_cluster_cmd)

# Alias for run_example_local
cli.add_command(run_example_local_cmd, name='launch_example_local')
# Alias for run_example_dry
cli.add_command(run_example_dry_cmd, name='launch_example_dry')
# Alias for run_example_debug
cli.add_command(run_example_debug_cmd, name='launch_example_debug')
cli.add_command(launch_example_cluster_cmd)
cli.add_command(launch_example_gce_cmd)
cli.add_command(launch_example_ec2_cmd)


def main():
    return cli()


if __name__ == "__main__":
    main()
