#!/usr/bin/env python

# PYTHON_ARGCOMPLETE_OK

import sys
import lightargs
import treep
import traceback
import os

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


global _PROJECTS

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,
                    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("--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("--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()

    # 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 projects.UnknownRepository as e:
        sys.stderr.write("ERROR: Repository {} does not exist.\n".format(e))
        sys.exit(1)
    except 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)
