#!/usr/bin/env python3
import ast
import os
from pathlib import Path
import click
import shutil
from crontab import CronTab

from wrftamer.main import project, list_projects
from wrftamer.wrftamer_paths import wrftamer_paths
import wrftamer.wrftamer_functions as wtfun

home_path, db_path, run_path, archive_path, disc = wrftamer_paths()


class PythonLiteralOption(click.Option):
    """
    Helper class to use list as input option (instead of string)
    """

    def type_cast_value(self, ctx, value):
        value = str(value)
        try:
            return ast.literal_eval(value)
        except Exception as e:
            raise click.BadParameter(value)


@click.group(name="wrftamer")
def cli():
    pass


@cli.command(
    name="first_steps",
    short_help="create default dir structure",
    help="Creates a directory structure, copies"
    "executables from WRF and WPS "
    "directories, "
    "copies essential data.",
)
@click.argument("wrf_and_wsp_parent_dir", type=click.Path(exists=True))
@click.option(
    "--exe_dir",
    help="the name of the executables directory [default: $HOME/wrftamer/bin/wrf_executables]",
)
@click.option(
    "--essentials_dir",
    help="the name of the essentials directory [default: $HOME/wrftamer/src/wrf_nonessentials]",
)
@click.option(
    "--non_essentials_dir",
    help="the name of the non_essentials directory [default: $HOME/wrftamer/src/wrf_nonessentials]",
)
@click.option(
    "--vtable", help="The name of the Vtable to be copied [default: Vtable.ECMWF]"
)
def cli_first_steps(
    wrf_and_wsp_parent_dir,
    exe_dir=None,
    essentials_dir=None,
    non_essentials_dir=None,
    vtable=None,
):
    """
    Creates a floder structure for the executables, essentials and non-essential files. Copies executalbes
    Using this script should help a user to get started. An advanced user may use different scripts later on.
    Do not use options to use defaults paths (modified by environmental variables)

    Args:
        wrf_and_wsp_parent_dir: WRF and WPS parent folder.
        exe_dir: directiory of executables (will be created)
        essentials_dir: directiory of wrf_essentials (will be created)
        non_essentials_dir: directiory of wrf_nonessentials (will be created)
        vtable: the name of the Vtable to be copied.

    Returns: None

    """

    if exe_dir is None:
        exe_dir = home_path / "bin/wrf_executables"

    if essentials_dir is None:
        essentials_dir = home_path / "src/wrf_essentials"

    if non_essentials_dir is None:
        non_essentials_dir = home_path / "src/wrf_nonessentials"

    if vtable is None:
        vtable = "Vtable.ECMWF"

    click.echo("Creating directory %s" % exe_dir)
    click.echo("Copying relevant .exe files to %s" % exe_dir)
    click.echo(
        "The executables were copied from subdirectories of %s" % wrf_and_wsp_parent_dir
    )
    click.echo("")
    click.echo("Creating directory %s" % essentials_dir)
    click.echo("Copying relevant files to %s" % essentials_dir)
    click.echo(
        "The files were copied from subdirectories of %s" % wrf_and_wsp_parent_dir
    )
    click.echo("Vtable: %s", vtable)
    click.echo("")
    click.echo("Creating directory %s" % non_essentials_dir)
    click.echo("")

    wtfun.make_executable_dir(exe_dir, wrf_and_wsp_parent_dir)
    wtfun.make_essential_data_dir(wrf_and_wsp_parent_dir, essentials_dir, vtable)
    wtfun.make_non_essential_data_dir(non_essentials_dir)


@cli.command(
    name="make_executable_dir",
    short_help="copy executables",
    help="Copy wrf and wsp executables from" " <WRF_and_WPS_parent_dir> to a targetdir",
)
@click.argument("wrf_and_wsp_parent_dir", type=click.Path(exists=True))
@click.argument("exe_dir", type=click.Path())
def cli_make_executable_dir(wrf_and_wsp_parent_dir, exe_dir):
    click.echo("Creating directory %s" % exe_dir)
    click.echo("Copying relevant .exe files to %s" % exe_dir)
    click.echo(
        "The executables were copied from subdirectories of %s" % wrf_and_wsp_parent_dir
    )

    wtfun.make_executable_dir(exe_dir, wrf_and_wsp_parent_dir)


@cli.command(
    name="make_essential_data_dir",
    short_help="copy constant data",
    help="Copy constant WRF-data and tables after compilation to a targetdir. Provide Vtable name.",
)
@click.argument("wrf_and_wsp_parent_dir", type=click.Path(exists=True))
@click.argument("essentials_dir", type=click.Path())
@click.argument("vtable", nargs=1)
def cli_make_essential_data_dir(wrf_and_wsp_parent_dir, essentials_dir, vtable):
    click.echo("Creating directory %s" % essentials_dir)
    click.echo("Copying relevant files to %s" % essentials_dir)
    click.echo(
        "The files were copied from subdirectories of %s" % wrf_and_wsp_parent_dir
    )
    click.echo("Vtable: %s", vtable)

    wtfun.make_essential_data_dir(wrf_and_wsp_parent_dir, essentials_dir, vtable)


@cli.command(
    name="create",
    short_help="Create the wrf run directory",
    help="Create the wrf run directory from a config-file. Default is configure.yaml",
)
@click.argument("configfile", default="configure.yaml", type=click.Path(exists=True))
@click.option(
    "--namelisttemplate", help="Namelist template file [default: None (== built-in)]"
)
@click.option("--run_wps", help="Set to True if you want wps to be executed as well")
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
@click.option(
    "--comment", help="(string) A short description of what the experiment should do."
)
def cli_create(
    configfile, namelisttemplate=None, run_wps=False, proj_name=None, comment=""
):
    """

    Create an experiment and, optinally, a project as well. A configure file is required. The namelist template
    can be selected. If left blank, the built-in default is used.

    Args:
        configfile: configure file of the experiment to create
        namelisttemplate: the template of the namelist.
        proj_name: the name of the project. The project feature is not used if this variable is not used.
        run_wps: set to True if you want to run wps (geogrid, ungrib, metgrid) as part of experiment creation.
        comment: A short description of the experiment. Only used with project management feature

    Returns: None

    """

    if not isinstance(run_wps, bool):
        run_wps = run_wps == "True"

    exp_name = Path(
        configfile
    ).stem  # The file name of the conf file is ALWAYS the experiment name

    # Create Project on demand
    if proj_name is not None:
        proj = project(proj_name)
        if not os.path.isdir(proj.proj_path):
            print("This project does not yet exist.")
            val = input("Do you want to create it? Yes/[No]")
            if val in ["y", "Y", "yes", "Yes"]:
                proj.create()
            else:
                return

    click.echo("Your configure file: %s" % configfile)
    click.echo("Name of the experiment: %s" % exp_name)
    if proj_name is not None:
        click.echo("Name of the project: %s" % proj_name)
    else:
        click.echo("This experiment is not associated with any project.")

    proj = project(proj_name)  # works even for proj_name=None > unassociated.
    try:
        proj.exp_create(
            exp_name,
            comment,
            configfile=configfile,
            namelisttemplate=namelisttemplate,
            verbose=False,
        )

    except FileExistsError:
        print(
            "An experiment with this name already exists. Use a different name or remove the directory first."
        )

    if run_wps:
        proj.exp_run_wps(exp_name)


@cli.command(
    name="run_wps",
    short_help="run wps for an experiment that already has been created",
    help='run wps for an experiment that already has been created using "wt create".',
)
@click.argument("exp_name", type=str)
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
def cli_run_wps(exp_name, proj_name=None):
    """

    Args:
        exp_name: the name of the experiment for which wps is run
        proj_name: the name of the project. The project feature is not used if this variable is not used.

    """

    proj = project(proj_name)
    proj.exp_run_wps(exp_name)


@cli.command(
    name="remove",
    short_help="remove experiment <exp_name> in a clean way",
    help="Remove an experiment. If a project name is used, delete the entry from the database as well.",
)
@click.argument("exp_name", type=str)
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
def cli_remove(exp_name, proj_name=None):
    proj = project(proj_name)
    proj.exp_remove(exp_name)


@cli.command(
    name="copy",
    short_help="Use old wrf run directory",
    help="Create a new run by copying/linking old files up to real.exe. "
    "This is needed e.g. when changes in the namelist, that do not affect the domain, should be made."
    " Specify an experiment to be copied and the name of a new experiment.",
)
@click.argument("exp_name", type=str)
@click.argument("new_exp_name", type=str)
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
@click.option(
    "--comment", help="(string) A short description of what the experiemnt should do."
)
def cli_copy(exp_name, new_exp_name, proj_name=None, comment=""):
    """

    Args:

        This methods copies an entire experiment, but large files as the met_em files are just linked.
        That way, one does not have to re-run wps if a run with very minor changes is desired.
        Be aware that any changes in the namelist file should also be done in the .conf file for consistency.

        exp_name: the name of the experiment that is copied
        new_exp_name: the name of the new experiment
        proj_name: the name of the project. The project feature is not used if this variable is not used.
        comment: (string) a short destcription of the new experiment

    Returns: None

    """
    click.echo(
        "You chose this experiment: %s and the new experiment: %s"
        % (exp_name, new_exp_name)
    )
    proj = project(proj_name)

    try:
        proj.exp_copy(exp_name, new_exp_name, comment)

    except FileExistsError:
        print(f"Experiment {new_exp_name} alreay exists.")
    except FileNotFoundError:
        print(f"Experiment {exp_name} does not exist.")


@cli.command(
    name="rename",
    short_help="Rename a wrf-folder",
    help="Rename a WRF-Folder an update the config and submit-files. Specify old and new path.",
)
@click.argument("exp_name", type=str)
@click.argument("new_exp_name", type=str)
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
def cli_rename(exp_name, new_exp_name, proj_name=None):
    """
    Renaming of an experiment. Wherever the name is used, it is changed as well.

    Args:
        exp_name: the old name
        new_exp_name: the new name
        proj_name: the name of the project. The project feature is not used if this variable is not used.

    Returns:

    """
    click.echo("Rename %s to %s" % (exp_name, new_exp_name))

    proj = project(proj_name)

    try:
        proj.exp_rename(exp_name, new_exp_name, verbose=False)
    except FileExistsError:
        print(f"Experiment {new_exp_name} already exists.")
    except FileNotFoundError:
        print(f"Experiment {exp_name} does not exist.")


@cli.command(
    name="restart",
    short_help="Restart the WRF Run",
    help="Restart the WRF Run from a given restart-file. "
    "It is assumed that the restart-file is located "
    "in exp_path/out or exp_path/wrf. If not please "
    "specify templatefile. "
    "If applicable: move tsfiles before.",
)
@click.argument("restartfile", type=click.Path(exists=True))
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
def cli_restart(restartfile, proj_name=None):
    """
    Restart an experiment using the <restartfile>
    Args:
        restartfile: a wrfrst file. Must still contain the correct timestamp.
        proj_name: the name of the project. The project feature is not used if this variable is not used.

    Returns: None

    """

    click.echo("Move WRF-Output")
    click.echo("Create the Restart-Run")

    exp_name = Path(restartfile).parents[1].stem

    proj = project(proj_name)
    proj.exp_restart(exp_name, restartfile)  # moves output as well.


@cli.command(
    name="move",
    short_help="Move the WRF Output",
    help="Move the WRF Output. " "Please specify an experiment name",
)
@click.argument("exp_name", type=str)
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
def cli_move(exp_name, proj_name=None):
    """
    Moving mode output into the /out directory after a run is finished. Do the same for the log files.
    Args:
        exp_name: the name of the experiment
        proj_name: the name of the project. The project feature is not used if this variable is not used.

    Returns: None

    """
    click.echo("Move WRF-Output")

    proj = project(proj_name)
    proj.exp_move(exp_name)


@cli.command(
    name="process_tslists",
    short_help="Merge ts-lists to ncdf (raw) and average.",
    help="Specify directory(s) where to find ts-files",
)
@click.argument("exp_name", type=str)
@click.option(
    "--location",
    help="ts-lists for this location are processed, i.e. --location=Myplace",
)
@click.option("--domain", help="name of domain, i.e. --domain=d01")
@click.option(
    "--timeavg",
    help="time in minutes to compute averages. "
    "Specify a list and do not use space between numbers, i.e. --timeavg=[10,20]",
    cls=PythonLiteralOption,
    default="[ ]",
)
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
def cli_tslist(exp_name, location, domain, timeavg, proj_name=None):
    """
    Postprocessing of a run (if tslists are generated). tslists are read and merged into a single file.
    Averaging is done if desired.
    Args:
        exp_name: the name of the experiment
        location: the location (name) of the tslist files to be processed. May be None to process all.
        domain: the domain of the tslist files to be processed. May be None to process all.
        timeavg: a list of averaging intervals i.e. [5,10,20].
        proj_name: the name of the project. The project feature is not used if this variable is not used.

    Returns: None

    """
    click.echo("process ts-files")

    try:
        proj = project(proj_name)
        proj.exp_process_tslist(exp_name, location, domain, timeavg)
    except FileNotFoundError as e:
        print("The directory that contains the tsfiles does not exist.")
        print(e)


@cli.command(name="ppp")
@click.argument("exp_name", type=str)
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
def cli_ppp(exp_name, proj_name=None):
    click.echo("Perform post processing protocol")
    try:
        proj = project(proj_name)
        proj.exp_run_postprocessing_protocol(exp_name)
    except Exception as e:
        print("Problems with the post processing protocoll")
        print(e)


@cli.command(
    name="archive",
    short_help="Archive a wrf run.",
    help="Archive an experiment. The destination path is set in the environment variables",
)
@click.argument("exp_name", type=str)
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
@click.option("--keep_log", help="Keep log files as well [True/False]? [default: True]")
def cli_archive(exp_name, proj_name=None, keep_log="True"):
    """
    Archives an experiment.
    Archiving includes: removing all files except output, namlists, tslist, aux_file.txt, OBS_DOMAIN* and log files.
    log files may be removed as well.

    Args:
        exp_name: the name of the experiment
        proj_name: the name of the project. The project feature is not used if this variable is not used.
        keep_log: if False, logs will be removed. default: true

    Returns: None

    """

    click.echo(f"archiving {exp_name}")

    try:
        proj = project(proj_name)
        proj.exp_archive(exp_name, keep_log=bool(keep_log))
    except FileNotFoundError:
        print("This experiment does not exist")


@cli.command(
    name="wrf_timing",
    short_help="Show time for WRF simulations",
    help="Specify WRF directory",
)
@click.argument("exp_name", type=click.Path(exists=True))
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
def cli_timing(exp_name, proj_name=None):
    click.echo("show timing")

    proj = project(proj_name)
    proj.exp_runtime(exp_name, verbose=True)


# These are the project commands
@cli.command(
    name="create_project",
    short_help="Create a new Project",
    help="specify name of the project (single String, no special characters). Project is created in "
    "WRFTAMER_RUN_PATH.",
)
@click.argument("proj_name", type=str)
def cli_create_project(proj_name):
    """
    Create a new project
    Args:
        proj_name: the name of the project.

    Returns: None

    """
    proj = project(proj_name)

    try:
        proj.create()
    except FileExistsError:
        print("A project with this name already exists.")
        print("Remove project by using wt remove_project or choose a different name")
        return


@cli.command(
    name="remove_project",
    short_help="Remove a Project",
    help="specify name of the project (single String, no special characters",
)
@click.argument("proj_name", type=str)
def cli_remove_project(proj_name):
    """
    remove a project

    Args:
        proj_name: name of the project

    Returns: None

    """
    proj = project(proj_name)
    try:
        proj.remove()
    except FileNotFoundError:
        print("The project or at least one of the directories does not exist.")
        return


@cli.command(
    name="rename_project",
    short_help="Rename a Project",
    help="specify name of the project (single String, no special characters",
)
@click.argument("proj_name", type=str)
@click.argument("new_name", type=str)
def cli_rename_project(proj_name, new_name):
    """
    Rename a project
    Args:
        proj_name: old name
        new_name: new name

    Returns: None

    """
    proj = project(proj_name)

    try:
        proj.rename(new_name)
    except FileNotFoundError:
        print("Project does not exists. Cannot rename")
        return
    except FileExistsError:
        print("New Project Name already exists. Cannot rename")
        return


@cli.command(
    name="list_projects",
    short_help="list Projects",
    help="specify name of the project (single String, no special characters",
)
def cli_list_projects():
    """
    List all project names
    Returns: None

    """
    list_projects()


@cli.command(
    name="du_project",
    short_help="list disk use of a Project",
    help="specify name of the project (single String, no special characters",
)
@click.argument("proj_name", type=str)
def cli_du_project(proj_name):
    """
    Display the memory required by the project
    Args:
        proj_name: the name of the project

    Returns:

    """
    proj = project(proj_name)
    try:
        proj.disk_use()
    except FileNotFoundError:
        print("Project", proj_name, "does not exist.")


@cli.command(
    name="runtimes_project",
    short_help="show runtimes of Experiments in a project",
    help="specify name of the project (single String, no special characters",
)
@click.argument("proj_name", type=str)
def cli_runtimes_project(proj_name):
    """
    Display the runtimes of all experiment of a project
    Args:
        proj_name: the name of the project

    Returns: None

    """
    proj = project(proj_name)

    try:
        proj.runtimes()
    except NotImplementedError:
        print("This method has not yet been implemented")


@cli.command(
    name="cleanup_db",
    short_help="removes entries from database to nonexistend experiments",
    help="specify name of the project (single String, no special characters",
)
@click.option(
    "--proj_name",
    help="Name of the project this experiment is associated with [default: None]",
)
def cli_cleanup_db(proj_name=None):
    proj = project(proj_name)

    # first, check if the project exists in the db, if not, remove it
    if proj.tamer_path.is_dir() and not proj.proj_path.is_dir():
        print(
            "Project",
            proj.name,
            "does not exist, but a database entry remains. Cleaning...",
        )
        shutil.rmtree(proj.tamer_path)
    elif proj.tamer_path.is_dir() and proj.proj_path.is_dir():
        # there may be missing experiments.
        proj.cleanup_db()


@cli.command(
    name="start_watchdog",
    short_help="starts a cronjob that looks for the status of your jobs",
    help="starts a cronjob that checks the status of your jobs once per day and performs a defined"
    " post processing protocol. Arguments: [wd_script, period in hours]",
)
@click.argument("wd_script", type=click.Path(exists=True))
@click.argument("period", type=int)
def cli_start_watchdog(wd_script, period=24):
    croncommand = f"bash {wd_script} >> $HOME/watchdog.log"

    cron = CronTab(user=True)
    job = cron.new(command=croncommand)
    job.hour.every(period)
    cron.write()

    # I now need a piece of code that:
    # 1) Loops over all project names
    # 2a) For all that exist, check status
    # 2b) For all that are marked running, check if finished.
    # 2c) For all that are finished, check if pp is done.
    # 3) Perform ppp if required


@cli.command(
    name="stop_watchdog",
    short_help="stops a cronjob started with start_watchdog",
    help="stops the cronjob started with start_watchdog",
)
@click.argument("wd_script", type=click.Path(exists=True))
def cli_stop_watchdog(wd_script):
    croncommand = f"bash {wd_script} >> $HOME/watchdog.log"

    cron = CronTab(user=True)
    for job in cron:
        if job.command == croncommand:
            cron.remove(job)

    cron.write()


@cli.command(
    name="create_wd_script",
    short_help="creates a scipt for the watchdog",
    help="creates a bash script for the cron job. Required information: environment name, path to miniconda",
)
@click.argument("miniconda_path", type=click.Path(exists=True))
@click.argument("condaenv_name", type=str)
@click.option("--template", help="template file [default: None (== built-in)]")
def cli_create_wd_script(miniconda_path, condaenv_name, template=None):
    wtfun.make_call_wd_file_from_template(miniconda_path, condaenv_name, template)


if __name__ == "__main__":
    cli()
