# -*- coding: utf-8 -*-
"""
Functionality to generate and work with the directory structure of a project
"""
from __future__ import absolute_import, print_function

import os
from os.path import join as join_path

from six import string_types

from . import shell, templates, utils

__author__ = "Florian Wilhelm"
__copyright__ = "Blue Yonder"
__license__ = "new BSD"


class FileOp(object):
    """Namespace for file operations during an update:

    - NO_OVERWRITE: Do not overwrite an existing file during update
    - NO_CREATE: Do not create the file during an update
    """
    NO_OVERWRITE = 0
    NO_CREATE = 1


def add_namespace(opts, struct):
    """Prepend the namespace to a given file structure

    Args:
        opts (dict): options of the project
        struct (dict): directory structure as dictionary of dictionaries

    Returns:
        dict: directory structure as dictionary of dictionaries
    """
    if not opts['namespace']:
        return struct
    namespace = opts['namespace'][-1].split('.')
    base_struct = struct
    pkg_struct = struct[opts['project']][opts['package']]
    struct = base_struct[opts['project']]
    del struct[opts['package']]
    for sub_package in namespace:
        struct[sub_package] = {'__init__.py': templates.namespace(opts)}
        struct = struct[sub_package]
    struct[opts['package']] = pkg_struct
    return base_struct


def make_structure(opts):
    """Creates the project structure as dictionary of dictionaries

    Args:
        opts (dict): options of the project

    Returns:
        dict: structure as dictionary of dictionaries
    """
    struct = {opts['project']: {
        '.gitignore': templates.gitignore(opts),
        opts['package']: {'__init__.py': templates.init(opts),
                          'skeleton.py': templates.skeleton(opts)},
        'tests': {'conftest.py': templates.conftest_py(opts),
                  'test_skeleton.py': templates.test_skeleton(opts)},
        'docs': {'conf.py': templates.sphinx_conf(opts),
                 'authors.rst': templates.sphinx_authors(opts),
                 'index.rst': templates.sphinx_index(opts),
                 'license.rst': templates.sphinx_license(opts),
                 'changes.rst': templates.sphinx_changes(opts),
                 'Makefile': templates.sphinx_makefile(opts),
                 '_static': {
                     '.gitignore': templates.gitignore_empty(opts)}},
        'README.rst': templates.readme(opts),
        'AUTHORS.rst': templates.authors(opts),
        'LICENSE.txt': templates.license(opts),
        'CHANGES.rst': templates.changes(opts),
        'setup.py': templates.setup_py(opts),
        'setup.cfg': templates.setup_cfg(opts),
        'requirements.txt': templates.requirements(opts),
        'test-requirements.txt': templates.test_requirements(opts),
        '.coveragerc': templates.coveragerc(opts)}}
    proj_dir = struct[opts['project']]
    if opts['travis']:
        proj_dir['.travis.yml'] = templates.travis(opts)
        proj_dir['tests']['travis_install.sh'] = templates.travis_install(opts)
    if opts['gitlab_ci']:
        proj_dir['.gitlab-ci.yml'] = templates.gitlab_ci(opts)
    if opts['pre_commit']:
        proj_dir['.pre-commit-config.yaml'] = templates.pre_commit_config(opts)
    if opts['tox']:
        proj_dir['tox.ini'] = templates.tox(opts)
    if opts['update'] and not opts['force']:
        # Do not overwrite following files
        rules = {opts['project']: {
            '.gitignore': FileOp.NO_OVERWRITE,
            '.gitattributes': FileOp.NO_OVERWRITE,
            'setup.cfg': FileOp.NO_OVERWRITE,
            'README.rst': FileOp.NO_OVERWRITE,
            'CHANGES.rst': FileOp.NO_OVERWRITE,
            'LICENSE.txt': FileOp.NO_OVERWRITE,
            'AUTHORS.rst': FileOp.NO_OVERWRITE,
            'requirements.txt': FileOp.NO_OVERWRITE,
            'test-requirements.txt': FileOp.NO_OVERWRITE,
            '.travis.yml': FileOp.NO_OVERWRITE,
            '.gitlab-ci.yml': FileOp.NO_OVERWRITE,
            '.coveragerc': FileOp.NO_OVERWRITE,
            '.pre-commit-config.yaml': FileOp.NO_OVERWRITE,
            'tox.ini': FileOp.NO_OVERWRITE,
            opts['package']: {'skeleton.py': FileOp.NO_CREATE},
            'tests': {'conftest.py': FileOp.NO_OVERWRITE,
                      'travis_install.sh': FileOp.NO_OVERWRITE,
                      'test_skeleton.py': FileOp.NO_CREATE},
            'docs': {'index.rst': FileOp.NO_OVERWRITE}
        }}
        struct = apply_update_rules(rules, struct)
    struct = add_namespace(opts, struct)

    return struct


def create_structure(struct, prefix=None, update=False):
    """Manifests a directory structure in the filesystem

    Args:
        struct (dict): directory structure as dictionary of dictionaries
        prefix (str): prefix path for the structure
        update (bool): update an existing directory structure

    Raises:
        :obj:`RuntimeError`: raised if content type in struct is unknown
    """
    if prefix is None:
        prefix = os.getcwd()
    for name, content in struct.items():
        if isinstance(content, string_types):
            with open(join_path(prefix, name), 'w') as fh:
                fh.write(utils.utf8_encode(content))
        elif isinstance(content, dict):
            try:
                os.mkdir(join_path(prefix, name))
            except OSError:
                if not update:
                    raise
            create_structure(struct[name],
                             prefix=join_path(prefix, name),
                             update=update)
        elif content is None:
            pass
        else:
            raise RuntimeError("Don't know what to do with content type "
                               "{type}.".format(type=type(content)))


def create_django_proj(opts):
    """Creates a standard Django project with django-admin.py

    Args:
        opts (dict): options of the project

    Raises:
        :obj:`RuntimeError`: raised if django-admin.py is not installed
    """
    try:
        shell.django_admin('--version')
    except:
        raise RuntimeError("django-admin.py is not installed, "
                           "run: pip install django")
    shell.django_admin('startproject', opts['project'])


def create_cookiecutter(opts):
    """Create a cookie cutter template

    Args:
        opts (dict): options of the project
    """
    try:
        from cookiecutter.main import cookiecutter
    except:
        raise RuntimeError("cookiecutter is not installed, "
                           "run: pip install cookiecutter")
    extra_context = dict(full_name=opts['author'],
                         email=opts['email'],
                         project_name=opts['project'],
                         repo_name=opts['package'],
                         project_short_description=opts['description'],
                         release_date=opts['release_date'],
                         version='unknown',  # will be replaced later
                         year=opts['year'])

    cookiecutter(opts['cookiecutter_template'],
                 no_input=True,
                 extra_context=extra_context)


def apply_update_rules(rules, struct, prefix=None):
    """Apply update rules using :obj:`~.FileOp` to a directory structure

    Args:
        rules (dict): directory structure as dictionary of dictionaries with
                      :obj:`~.FileOp` keys. The structure will be modified.
        struct (dict): directory structure as dictionary of dictionaries
        prefix (str): prefix path for the structure

    Returns:
        dict: directory structure with keys removed according to the rules
    """
    if prefix is None:
        prefix = os.getcwd()
    for k, v in rules.items():
        if isinstance(v, dict):
            apply_update_rules(v, struct[k], join_path(prefix, k))
        else:
            path = join_path(prefix, k)
            if v == FileOp.NO_OVERWRITE and os.path.exists(path):
                struct.pop(k, None)
            elif v == FileOp.NO_CREATE:
                struct.pop(k, None)
    return struct
