# -*- coding: utf-8 -*-

"""
Application micc
"""

import os, sys, shutil
from types import SimpleNamespace
from pathlib import Path

import click

from et_micc.project import Project, micc_version
import et_micc.logger

__template_help = "Ordered list of Cookiecutter templates, or a single Cookiecutter template."


@click.group()
@click.option('-v', '--verbosity', count=True
    , help="The verbosity of the program output."
    , default=1
)
@click.option('-p', '--project-path'
    , help="The path to the project directory. "
           "The default is the current working directory."
    , default='.'
    , type=str
)
@click.option('--clear-log'
    , help="If specified clears the project's ``et_micc.log`` file."
    , default=False, is_flag=True
)
@click.version_option(version=micc_version())
@click.pass_context
def main(ctx, verbosity, project_path, clear_log):
    """Micc command line interface.

    All commands that change the state of the project produce some output that
    is send to the console (taking verbosity into account). It is also sent to
    a logfile ``et_micc.log`` in the project directory. All output is always appended
    to the logfile. If you think the file has gotten too big, or you are no more
    interested in the history of your project, you can specify the ``--clear-log``
    flag to clear the logfile before any command is executed. In this way the
    command you execute is logged to an empty logfile.

    See below for (sub)commands.
    """
    if verbosity > 1:
        print("Current micc command is using Python", sys.version.replace('\n', ' '), end='\n\n')

    if clear_log:
        os.remove(project_path / 'micc.log')

    ctx.obj = SimpleNamespace(
        verbosity=verbosity,
        project_path=Path(project_path).resolve(),
        default_project_path=(project_path=='.'),
        clear_log=clear_log,
        template_parameters={},
    )


@main.command()
@click.option('--publish'
    , help="If specified, verifies that the package name is available on PyPI.\n"
           "If the result is False or inconclusive the project is NOT created."
    , default=False, is_flag=True
)
@click.option('-p', '--package'
    , help="Create a Python project with a package structure rather than a module structure:\n\n"
           "* package structure = ``<module_name>/__init__.py``\n"
           "* module  structure = ``<module_name>.py`` \n"
    , default=False, is_flag=True
)
@click.option('--micc-file'
    , help="The file containing the descriptions of the template parameters used"
           "in the *Cookiecutter* templates. "
    , default='', type=Path
)
@click.option('--python'
    , help="minimal python version for your project."
    , default='3.7'
)
@click.option('-d', '--description'
    , help="Short description of your project."
    , default='<Enter a one-sentence description of this project here.>'
)
@click.option('-l', '--lic'
    , help="License identifier."
    , default='MIT'
)
@click.option('-T', '--template', help=__template_help, default=[])
@click.option('-n', '--allow-nesting'
    , help="If specified allows to nest a project inside another project."
    , default=False, is_flag=True
)
@click.option('--module-name'
    , help="use this name for the module, rather than deriving it from the project name."
    , default=''
)
@click.option('--remote'
    , help="Create remote repo on github. 'public'*|'private'|'none'"
    , default='public'
)
@click.argument('name', type=str, default='')
@click.pass_context
def create(ctx
           , name
           , package
           , module_name
           , micc_file
           , description
           , python
           , lic
           , template
           , allow_nesting
           , publish
           , remote
           ):
    """Create a new project skeleton.

    The project name is taken to be the last directory of the *project_path*.
    If this directory does not yet exist, it is created. If it does exist already, it
    must be empty.

    The package name is the derived from the project name, taking the
    `PEP8 module naming rules <https://www.python.org/dev/peps/pep-0008/#package-and-module-names>`_
    into account:

    * all lowercase.
    * dashes ``'-'`` and spaces ``' '`` replaced with underscores ``'_'``.
    * in case the project name has a leading number, an underscore is prepended ``'_'``.

    If *project_path* is a subdirectory of a micc project, *micc* refuses to continu,
    unless ``--allow-nesting`` is soecified.
    """
    options = ctx.obj

    if name:
        if not options.default_project_path:
            # global option -p and argument name were both specified.
            print( "ERROR: you specified both global option -p and argument 'name':"
                  f"         -p -> {options.project_path}"
                  f"         name -> {name}"
                   "       You must choose one or the other, not both."
                 )
            ctx.exit(-1)
        else:
            # overwrite the -p global option so the project will be created:
            options.project_path = Path(name).resolve()
            options.default_project_path = False

    options.create = True
    options.micc_file = micc_file
    options.package = package
    options.publish = publish
    options.module_name = module_name
    if not remote in ['public','private']:
        remote = None
    options.remote = remote

    if not template:  # default, empty list
        if options.package:
            template = [ 'package-base'
                       , 'package-general'
                       , 'package-simple-docs'
                       , 'package-general-docs'
                       ]
        else:
            template = [ 'package-base'
                       , 'package-simple'
                       , 'package-simple-docs'
                       ]
        options.templates = template
    # else:
    #     # ignore structure
    #     options.structure = 'user-defined'

    options.allow_nesting = allow_nesting

    licenses = ['MIT license'
        , 'BSD license'
        , 'ISC license'
        , 'Apache Software License 2.0'
        , 'GNU General Public License v3'
        , 'Not open source'
                ]
    for l in licenses:
        if l.startswith(lic):
            license_ = l
            break
    else:
        license_ = licenses[0]

    options.template_parameters.update(
        {'project_short_description': description,
         'open_source_license': license_,
         'python_version': python,
        }
    )
    project = Project(options)
    if project.exit_code:
        ctx.exit(project.exit_code)


@main.command()
@click.option('--overwrite', is_flag=True
    , help="Overwrite pre-existing files (without backup)."
    , default=False
)
@click.option('--backup', is_flag=True
    , help="Make backup files (.bak) before overwriting any pre-existing files."
    , default=False
)
@click.pass_context
def convert_to_package(ctx, overwrite, backup):
    """Convert a Python module project to a package.

    A Python *module* project has only a ``<package_name>.py`` file, whereas
    a Python *package* project has ``<package_name>/__init__.py`` and can contain
    submodules, such as Python modules, packages and applications, as well as
    binary extension modules.

    This command also expands the ``package-general-docs`` template in this
    project, which adds a ``AUTHORS.rst``, ``HISTORY.rst`` and ``installation.rst``
    to the documentation structure.
    """
    options = ctx.obj
    options.overwrite = overwrite
    options.backup = backup

    project = Project(options)
    if project.exit_code:
        ctx.exit(project.exit_code)

    with et_micc.logger.logtime(options):
        project.module_to_package_cmd()

        if project.exit_code == et_micc.expand.EXIT_OVERWRITE:
            options.logger.warning(
                f"It is normally ok to overwrite 'index.rst' as you are not supposed\n"
                f"to edit the '.rst' files in '{options.project_path}{os.sep}docs.'\n"
                f"If in doubt: rerun the command with the '--backup' flag,\n"
                f"  otherwise: rerun the command with the '--overwrite' flag,\n"
            )

    if project.exit_code:
        ctx.exit(project.exit_code)


@main.command()
@click.option('--name', is_flag=True
    , help="print the project name."
    , default=False
)
@click.option('--version', is_flag=True
    , help="print the project version."
    , default=False
)
@click.pass_context
def info(ctx,name,version):
    """Show project info.

    * file location
    * name
    * version number
    * structure (with ``-v``)
    * contents (with ``-vv``)

    Use verbosity to produce more detailed info.
    """
    options = ctx.obj

    project = Project(options)
    if project.exit_code:
        ctx.exit(project.exit_code)

    if name:
        print(project.package_name)
        return
    if version:
        print(project.version)
        return
    else:
        with et_micc.logger.logtime(options):
            project.info_cmd()

    if project.exit_code:
        ctx.exit(project.exit_code)


@main.command()
@click.option('-M', '--major'
    , help='Increment the major version number component and set minor and patch components to 0.'
    , default=False, is_flag=True
)
@click.option('-m', '--minor'
    , help='Increment the minor version number component and set minor and patch component to 0.'
    , default=False, is_flag=True
)
@click.option('-p', '--patch'
    , help='Increment the patch version number component.'
    , default=False, is_flag=True
)
@click.option('-r', '--rule'
    , help='Any semver 2.0 version string.'
    , default=''
)
@click.option('-t', '--tag'
    , help='Create a git tag for the new version, and push it to the remote repo.'
    , default=False, is_flag=True
)
@click.option('-s', '--short'
    , help='Print the version on stdout.'
    , default=False, is_flag=True
)
@click.option('-d', '--dry-run'
    , help='bumpversion --dry-run.'
    , default=False, is_flag=True
)
@click.pass_context
def version(ctx, major, minor, patch, rule, tag, short, dry_run):
    """Modify or show the project's version number."""
    options = ctx.obj

    if rule and (major or minor or patch):
        msg = ("Both --rule and --major|--minor|--patc specified.")
        click.secho("[ERROR]\n" + msg, fg='bright_red')
        ctx.exit(1)
    elif major:
        rule = 'major'
    elif minor:
        rule = 'minor'
    elif patch:
        rule = 'patch'

    options.rule = rule
    options.short = short
    options.dry_run = dry_run

    project = Project(options)
    if project.exit_code:
        ctx.exit(project.exit_code)

    with et_micc.logger.logtime(project):
        project.version_cmd()
        if project.exit_code == 0 and tag:
            project.tag_cmd()

    if project.exit_code:
        ctx.exit(project.exit_code)


@main.command()
@click.pass_context
def tag(ctx):
    """Create a git tag for the current version and push it to the remote repo."""
    options = ctx.obj

    project = Project(options)
    if project.exit_code:
        ctx.exit(project.exit_code)

    if project.exit_code:
        ctx.exit(project.exit_code)

    project.tag_cmd()

    if project.exit_code:
        ctx.exit(project.exit_code)


@main.command()
@click.option('--app'
    , default=False, is_flag=True
    , help="Add a CLI ."
)
@click.option('--group'
    , default=False, is_flag=True
    , help="Add a CLI with a group of sub-commands rather than a single command CLI."
)
@click.option('--py'
    , default=False, is_flag=True
    , help="Add a Python module."
)
@click.option('--package'
    , help="Add a Python module with a package structure rather than a module structure:\n\n"
           "* module  structure = ``<module_name>.py`` \n"
           "* package structure = ``<module_name>/__init__.py``\n\n"
           "Default = module structure."
    , default=False, is_flag=True
)
@click.option('--f90'
    , default=False, is_flag=True
    , help="Add a f90 binary extionsion module (Fortran)."
)
@click.option('--cpp'
    , default=False, is_flag=True
    , help="Add a cpp binary extionsion module (C++)."
)
@click.option('-T', '--templates', default='', help=__template_help)
@click.option('--overwrite', is_flag=True
    , help="Overwrite pre-existing files (without backup)."
    , default=False
)
@click.option('--backup', is_flag=True
    , help="Make backup files (.bak) before overwriting any pre-existing files."
    , default=False
)
@click.argument('name', type=str)
@click.pass_context
def add(ctx
        , name
        , app, group
        , py, package
        , f90
        , cpp
        , templates
        , overwrite
        , backup
        ):
    """Add a module or CLI to the projcect.

    :param str name: name of the CLI or module added.

    If ``app==True``: (add CLI application)

    * :py:obj:`app_name` is also the name of the executable when the package is installed.
    * The source code of the app resides in :file:`<project_name>/<package_name>/cli_<name>.py`.


    If ``py==True``: (add Python module)

    * Python source  in :file:`<name>.py*`or :file:`<name>/__init__.py`, depending on the :py:obj:`package` flag.

    If ``f90==True``: (add f90 module)

    * Fortran source in :file:`f90_<name>/<name>.f90` for f90 binary extension modules.

    If ``cpp==True``: (add cpp module)

    * C++ source     in :file:`cpp_<name>/<name>.cpp` for cpp binary extension modules.
    """
    options = ctx.obj
    options.add_name = name
    options.app = app
    options.group = group
    options.py = py
    options.package = package
    options.f90 = f90
    options.cpp = cpp
    options.templates = templates
    options.overwrite = overwrite
    options.backup = backup

    project = Project(options)
    if project.exit_code:
        ctx.exit(project.exit_code)

    with et_micc.logger.logtime(options):
        project.add_cmd()

    if project.exit_code:
        ctx.exit(project.exit_code)


@main.command()
@click.option('--silent', is_flag=True
    , help="Do not ask for confirmation on deleting a component."
    , default=False
)
@click.option('--entire-package', is_flag=True
    , help="Replace all occurences of <cur_name> in the entire package and in the ``tests`` directory."
    , default=False
)
@click.option('--entire-project', is_flag=True
    , help="Replace all occurences of <cur_name> in the entire project."
    , default=False
)
@click.argument('cur_name', type=str)
@click.argument('new_name', type=str, default='')
@click.pass_context
def mv(ctx, cur_name, new_name, silent, entire_package, entire_project):
    """Rename or remove a component, i.e an app (CLI) or a submodule.

    :param cur_name: name of component to be removed or renamed.
    :param new_name: new name of the component. If empty, the component will be removed.
    """
    options = ctx.obj

    options.cur_name = cur_name
    options.new_name = new_name
    options.silent = silent
    if new_name:
        options.entire_package, options.entire_project =  entire_package, entire_project
    # else these flags are ignored.

    project = Project(options)
    if project.exit_code:
        ctx.exit(project.exit_code)

    with et_micc.logger.logtime(options):
        project.mv_component()


@main.command()
@click.option('--force', '-f', is_flag=True
    , help="Overwrite existing setup."
    , default=False
)
@click.pass_context
def setup(ctx
        , force
        ):
    """Setup your micc preferences.

    This command must be run once before you can use micc to manage your projects.
    """
    options = ctx.obj

    options.force = force
    micc_file_template = Path(__file__).parent / 'micc.json'
    setupdir = Path().home() / '.et_micc'
    setupdir.mkdir(exist_ok=True)
    path_to_miccfile = setupdir / 'micc.json'
    if not path_to_miccfile.exists() or force:
        shutil.copyfile(str(micc_file_template),str(path_to_miccfile))
        preferences = et_micc.expand.set_preferences(path_to_miccfile)
        print("\nConfiguring git:")
        cmds = [['git', 'config', '--global', 'user.name' , preferences['full_name']['default'] ]
               ,['git', 'config', '--global', 'user.email', preferences['email'    ]['default'] ]
               ,['git', 'config', '--global', 'credential.helper', 'cache']
               ]
        et_micc.utils.execute(cmds, print, stop_on_error=False)

    else:
        print("Micc has already been setup. Use '--force' or '-f' to overwrite the existing setup.")
    print("\nIf you want to change your preferences, edit the default entries in file \n"
         f"    {path_to_miccfile}\n"
          "Note that these changes will only affect NEW projects. Existing projects will be unaffected.\n"
          "Micc is now configured and ready to be used.\n\n"
          "Done"
    )

if __name__ == "__main__":
    sys.exit(main())  # pragma: no cover

# eof
