#!/usr/bin/env python

# PYTHON_ARGCOMPLETE_OK

import sys
import lightargs
import treep
import traceback
import os


import treep.display_doc
import treep.projects
from treep import files
from treep import exceptions
from treep import coloring
from treep import list_git_repos


global _PROJECTS


def _version():
    """Display the current software version."""
    print("")
    print("The treep version is: ", treep.__version__)
    print("")


def _repos():

    print("")
    for repo in _PROJECTS.get_repos(None):
        _PROJECTS.pretty_print_repo(repo.name)
    print("")


def _list_local_repos():
    """List names of all locally cloned repositories."""
    for repo in _PROJECTS.get_repos(None):
        if _PROJECTS.already_cloned(repo.name):
            print(repo.name)


def _projects():

    print("")
    for project in _PROJECTS.get_projects():
        print("\t\t" + treep.coloring.b_green(project.name))
    print("")


def _repo(repo_name):

    print("")
    _PROJECTS.pretty_print_repo(repo_name)
    print("")


def _project(project_name):

    print("")
    _PROJECTS.pretty_print_project(project_name)
    print("")


def _check_project(project_name):
    print("")
    _PROJECTS.raise_exception_if_missing_repo(project_name)
    print("")


def _clone(repo_or_project_name):

    print("")
    _PROJECTS.clone(
        repo_or_project_name, remote_type=None, raise_exception=True
    )
    print("")


def _clone_https(repo_or_project_name):

    print("")
    _PROJECTS.clone(
        repo_or_project_name, remote_type="https", raise_exception=True
    )
    print("")


def _clone_ssh(repo_or_project_name):

    print("")
    _PROJECTS.clone(
        repo_or_project_name, remote_type="ssh", raise_exception=True
    )
    print("")


def _sequencial_clone(repo_or_project_name):

    print("")
    _PROJECTS.clone(
        repo_or_project_name, raise_exception=True, run_multiprocess=False
    )
    print("")


def _clone_all():

    print("")
    _PROJECTS.clone()
    print("")


def _verbose_status():

    print("")
    _PROJECTS.pretty_print_workspace()
    print("")


def _status():

    print("")
    _PROJECTS.short_print_workspace()
    print("")


def _pull(repo):

    # saving current workspace config
    # in .treep_reverse folder. Will allow
    # user to call _reverse_pull (see function
    # below) to restore config of before pull
    # ([:-10] : root folder above "workspace" folder)

    reverse_created = True
    error = None
    try:
        path = _PROJECTS.get_workspace_path()[:-10]
        path += os.sep + ".treep_reverse"
        path = os.path.abspath(path)
        if not os.path.isdir(path):
            os.mkdir(path)
        statuses = list_git_repos.get_statuses(_PROJECTS.get_workspace_path())
        files.generate_yaml_configuration_files(
            path, statuses, "PROJECT", branch=True, commit=True
        )
    except Exception as e:
        reverse_created = False
        error = str(e)

    print("")
    _PROJECTS.pull(repo)
    print("")

    if reverse_created:
        message = str(
            "\nnote: state of the workspace previous to pull "
            + "saved in "
            + str(path)
            + ".\nUse treep --reverse-pull "
            + "to restore\n"
        )
        print(coloring.dim(message))

    else:
        message = str(
            "\nfailed to save state of the workspace previous to pull "
            + "with error:\n"
            + error
            + "\n"
        )
        print(coloring.dim(message))


def _reverse_pull():
    print("")
    # True -> will not use the treep_<smthg> folder to
    # generate projects, but the .treep_reverse folder
    # that should have been created during previous call to
    # treep --pull (before pull was effective).
    projects = treep.files.read_configuration_files(True)
    projects.clone()
    print("")


def _add_and_commit(repo, commit_message):
    if repo is None and commit_message is None:
        raise Exception("please enter commit message")
    if repo is not None and commit_message is None:
        commit_message = repo
        repo = None
    print("")
    _PROJECTS.add_and_commit(repo, commit_message)
    print("")


def _push(repo, commit_message=None):

    if commit_message is not None:
        _add_and_commit(repo, commit_message)
    print("")
    _PROJECTS.push(None)
    print("")


def _create_branch(project_name, branch_name):

    global _PROJECTS
    print("")
    _PROJECTS.checkout_branch(branch_name, project_name, create=True)
    print("")


def _checkout_branch(branch_name):

    global _PROJECTS
    print("")
    _PROJECTS.checkout_branch(branch_name, None, create=False)
    print("")


def _fix_origins():

    global _PROJECTS
    print("")
    _PROJECTS.fix_origins()
    print()


def _list_all_branches():

    global _PROJECTS
    print("")
    _PROJECTS.list_all_branches(fetch_first=True, console=True)
    print("")


def _print_local_path(repo_name):
    """Print the absolute path to the repository in the local workspace.

    Args:
        repo_name:  Name of the repository

    Raises
        UnknownRepository:  If the repository is not known.
        RuntimeError:  If the repository is known but not cloned in this
            workspace.
    """
    if _PROJECTS.already_cloned(repo_name):
        print(_PROJECTS.get_repo_path(repo_name))
    else:
        raise RuntimeError(
            "Repository {} is not cloned in this workspace".format(repo_name)
        )


def _conflicts():

    global _PROJECTS
    print("")
    _PROJECTS.display_conflicts()
    print("")


def _display_documentation():

    treep.display_doc.display_documentation(__file__)


def _compilation_script(repo_or_project_name, kwargs):

    # kwargs in a string passed
    # by the user for configuring the compilation
    # this string is expected to be a python dictionary
    try:
        kwargs = eval(kwargs)
        if not isinstance(kwargs, dict):
            raise Exception()
    except:
        raise Exception(
            red("failed to evaluate " + kwargs + "as a python dictionary")
        )

    global _PROJECTS
    print("")
    _PROJECTS.generate_compilation_script(repo_or_project_name)
    print("")


def _print_trace_and_error(trace, message):

    trace = "\t" + str(trace).replace("\n", "\n\t")
    message = "\t" + message.replace("\n", "\n\t")

    print(treep.coloring.red("\t[ERROR]\n") + treep.coloring.dim(message))
    print("\n")
    print(treep.coloring.dim(trace))
    print("\n")


def _add_arguments():

    global _PROJECTS

    lightargs.add(
        "--version",
        _version,
        nb_args=0,
        args_labels=[],
        man=(
            "Display in the terminal the version of the active "
            + "'treep' package"
        ),
        category="version",
    )

    lightargs.add(
        "--create-branch",
        _create_branch,
        nb_args=2,
        args_labels=["<project name>", "<branch name>"],
        man="for all repos of the project, checkout the specified branch, creating it if necessary",
        category="git",
        autocompletion=_PROJECTS.get_projects_names(),
    )

    lightargs.add(
        "--checkout-branch",
        _checkout_branch,
        nb_args=1,
        args_labels=["<branch name>"],
        man="for all cloned repo, attempt to checkout the specified branch",
        category="git",
        autocompletion=_PROJECTS.get_all_existing_branches(),
    )

    lightargs.add(
        "--list-branches",
        _list_all_branches,
        nb_args=0,
        man="for all cloned repos, list known branches",
        category="git",
    )

    lightargs.add(
        "--repos",
        _repos,
        nb_args=0,
        man="list known repositories",
        category="git",
    )

    lightargs.add(
        "--local-repos",
        _list_local_repos,
        nb_args=0,
        man="list local repositories",
        category="git",
    )

    lightargs.add(
        "--projects",
        _projects,
        nb_args=0,
        man="list known projects",
        category="git",
    )

    lightargs.add(
        "--project",
        _project,
        nb_args=1,
        args_labels=["<project name>"],
        man="provide information about the specified project",
        category="git",
        autocompletion=_PROJECTS.get_projects_names(),
    )

    lightargs.add(
        "--check-project",
        _check_project,
        nb_args=1,
        args_labels=["<project name>"],
        man="exit with an error if at least one of the repo of the project is not cloned",
        category="git",
        autocompletion=_PROJECTS.get_projects_names(),
    )

    lightargs.add(
        "--repo",
        _repo,
        nb_args=1,
        args_labels=["<repository name>"],
        man="provide information about the specified repository",
        category="git",
        autocompletion=_PROJECTS.get_repos_names(),
    )

    lightargs.add(
        "--clone",
        _clone,
        nb_args=1,
        args_labels=["<project or repository name>"],
        man="clone the specified repository or project (do nothing for repositories already in the workspace)",
        category="git",
        autocompletion=_PROJECTS.get_names(),
    )

    lightargs.add(
        "--clone-ssh",
        _clone_ssh,
        nb_args=1,
        args_labels=["<project or repository name>"],
        man="clone the specified repository or project using the ssh url of the origin(s) (do nothing for repositories already in the workspace)",
        category="git",
        autocompletion=_PROJECTS.get_names(),
    )

    lightargs.add(
        "--clone-https",
        _clone_https,
        nb_args=1,
        args_labels=["<project or repository name>"],
        man="clone the specified repository or project using the https url of the origin(s) (do nothing for repositories already in the workspace)",
        category="git",
        autocompletion=_PROJECTS.get_names(),
    )

    lightargs.add(
        "--seq-clone",
        _sequencial_clone,
        nb_args=1,
        args_labels=["<project or repository name>"],
        man="clone (without using multiprocessing) the specified repository or project (do nothing for repositories already in the workspace)",
        category="git",
        autocompletion=_PROJECTS.get_names(),
    )

    lightargs.add(
        "--clone-all", _clone_all, man="clone all known repos", category="git"
    )

    lightargs.add(
        "--status",
        _status,
        nb_args=0,
        man="fetch all repositories, then print status of the workspace",
        category="git",
    )

    lightargs.add(
        "--verbose-status",
        _verbose_status,
        nb_args=0,
        man="fetch all repositories, then print a (verbose) status of the workspace",
        category="git",
    )

    lightargs.add(
        "--pull",
        _pull,
        nb_args=1,
        defaults=[None],
        man="attempt to pull the specified repo (or all cloned repositories if omitted)",
        autocompletion=_PROJECTS.get_names(),
        category="git",
    )

    lightargs.add(
        "--reverse-pull",
        _reverse_pull,
        man="restore the workspace as defined by the .treep_reverse configuration folder, automatically created when --pull is called",
        category="git",
    )

    lightargs.add(
        "--add-and-commit",
        _add_and_commit,
        nb_args=2,
        defaults=[None, None],
        args_labels=["<repo name>,<commit message>"],
        man="for the specified repo (all cloned repo if omitted), attemps to add all files and to commit",
        autocompletion=_PROJECTS.get_names(),
        category="git",
    )

    lightargs.add(
        "--push",
        _push,
        nb_args=1,
        defaults=[None],
        man="for all the specified repo (or all cloned repos if omitted), attempt to push. You may want to add and commit first",
        autocompletion=_PROJECTS.get_names(),
        category="git",
    )

    lightargs.add(
        "--fix-origins",
        _fix_origins,
        man="update the origin urls of repositories, so that they match the ones provided in the yaml configuration files",
        category="git",
    )

    lightargs.add(
        "--path",
        _print_local_path,
        nb_args=1,
        args_labels=["<repository name>"],
        man="get absolute path to the repository in the workspace",
        category="workspace",
    )

    lightargs.add(
        "--see-treep-conflicts",
        _conflicts,
        nb_args=0,
        man="display possible conflicts between configuration folders",
        category="treep",
    )

    lightargs.add(
        "--documentation",
        _display_documentation,
        nb_args=0,
        defaults=None,
        man="display treep documentation",
        category="treep",
    )

    lightargs.add(
        "--compilation-script",
        _compilation_script,
        nb_args=2,
        defaults=[None, "{}"],
        man="generate compilation script for specified repo or projects (all cloned repos if omitted). Pass a string which can be evaluated as a python dictionary as argument to configure.",
        autocompletion=_PROJECTS.get_names(),
        category="compilation",
    )


if __name__ == "__main__":

    if any(
        [
            arg in sys.argv[1:]
            for arg in ["--documentation", "-h", "-documentation", "--h"]
        ]
    ):
        _display_documentation()
        exit()

    if "--version" in sys.argv:
        _version()
        exit()

    # Disable printing of the header for some commands
    print_header = True
    if any([arg in sys.argv[1:] for arg in ["--local-repos", "--path"]]):
        print_header = False

    starting_dir = os.getcwd()

    try:

        global _PROJECTS
        _PROJECTS = treep.files.read_configuration_files(False)

    except exceptions.TreepConfigFolderNotFound:
        print(
            treep.coloring.red(
                "\n\tFailed to find a configuration folder (treep_xxx) in the current folder"
                + " or one of its parent folders\n"
            )
        )
        exit(1)

    except Exception as e:

        print("\n")
        trace = traceback.format_exc()
        _print_trace_and_error(trace, str(e))
        exit(1)

    # this has to be called before _add_arguments,
    # as _add_arguments will fill autocompletion lists
    lightargs.enable_autocompletion()

    _add_arguments()

    lightargs.autocomplete()

    if print_header:
        print(
            treep.coloring.dim(
                "\n\ttreep, copyright 2019 Max Planck Gesellschaft"
            )
        )
        print(
            treep.coloring.dim(
                "\t\tworkspace: " + _PROJECTS.get_workspace_path()
            )
        )
        print(
            treep.coloring.dim(
                "\t\tprojects: " + str(_PROJECTS.get_nb_projects())
            )
        )
        print(
            treep.coloring.dim(
                "\t\trepositories: " + str(_PROJECTS.get_nb_repos())
            )
        )
        if _PROJECTS.has_conflicts():
            repos, projects = _PROJECTS.get_conflicts()
            conflicts = ""
            if len(repos) > 0:
                conflicts += str(len(repos)) + " repos "
            if len(projects) > 0:
                conflicts += str(len(projects)) + " repos "
            print(treep.coloring.dim("\t\tconflicts: " + conflicts))
        print("")

    os.chdir(starting_dir)

    try:
        lightargs.execute(sys.argv[1:])
    except treep.projects.UnknownRepository as e:
        sys.stderr.write("ERROR: Repository {} does not exist.\n".format(e))
        sys.exit(1)
    except treep.projects.UnknownProject as e:
        sys.stderr.write("ERROR: Project {} does not exist.\n".format(e))
        sys.exit(1)
    except Exception as e:
        sys.stderr.write("ERROR: {}\n".format(e))
        sys.exit(1)

    sys.exit(0)
