# zetup.py
#
# Zimmermann's Python package setup.
#
# Copyright (C) 2014-2015 Stefan Zimmermann <zimmermann.code@gmail.com>
#
# zetup.py is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# zetup.py is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with zetup.py. If not, see <http://www.gnu.org/licenses/>.

__all__ = ['find_zetup_config', 'Zetup',
  'ZetupError', 'ZetupConfigNotFound',
  'DistributionNotFound', 'VersionConflict',
  'Packages', 'Package',
  'Popen', 'call']

import sys
import os
from types import ModuleType
## from inspect import ismodule

from .zetup import Zetup
from .error import ZetupError
from .config import ZetupConfigNotFound
from .requires import DistributionNotFound, VersionConflict
from .package import Packages, Package
from .process import Popen, call


COMMANDS = {}


def command(func):
    """Decorator for registering basic (non-project-bound) commands.
    """
    COMMANDS[func.__name__] = func


def find_zetup_config(pkgname):
    zfg_modname = pkgname + '.zetup_config'
    try: # Already imported?
        return sys.modules[zfg_modname]
    except KeyError:
        pass
    try:
        return __import__(zfg_modname).zetup_config
    except ImportError:
        pass
    # ==> no zetup config module
    # ==> assume package imported from source (repo)
    # ==> load setup config from package's parent path:
    mod = sys.modules[pkgname]
    path = os.path.dirname(os.path.dirname(os.path.realpath(mod.__file__)))
    try:
        return Zetup(path)
    except ZetupConfigNotFound as e:
        raise ZetupConfigNotFound(
          "No '%s.zetup_config' module and: %s" % (pkgname, e))


def annotate(pkgname, check_requirements=True, check_packages=True):
    """Find the zetup config for given `pkgname`
       and add __version__, __requires__, __dist__, __description__,
       __packages__ and __extras__ (if defined) to the package object.

    - Automatically checks installed package requirements
      unless `check_requirements` is False.
    - Automatically checks installed package files
      unless `check_packages` is False.
    - Returns the zetup config object.
    """
    try:
        mod = sys.modules[pkgname]
    except KeyError:
        raise ZetupError(
          "Package %s was not found in sys.modules" % repr(pkgname))
    zfg = find_zetup_config(pkgname)
    mod.__version__ = zfg.VERSION
    mod.__requires__ = zfg.REQUIRES
    if check_requirements:
        zfg.REQUIRES.check()
    if zfg.EXTRAS:
        mod.__extras__ = zfg.EXTRAS
    mod.__distribution__ = zfg.DISTRIBUTION.find(os.path.dirname(__file__))
    mod.__description__ = zfg.DESCRIPTION
    mod.__packages__ = zfg.PACKAGES
    if (check_packages
        #TODO: remove (only for backwards compatibility)
        and isinstance(zfg.PACKAGES, Packages)
        ):
        zfg.PACKAGES.check()
    return zfg


annotate(__name__, check_requirements=False)


class ZetupModule(ModuleType):
    """Custom module class for wrapping native ``zetup`` module on import,
       to dynamically provide additional module members
       based on installed dependencies.
    """
    def __init__(self):
        self.__name__ = __name__
        self.module = sys.modules[__name__]
        try:
            from .path import Path
        except ImportError: #==> no path.py installed
            pass
        else:
            self.__all__.append('Path')

    def __getattr__(self, name):
        """Just delegate to native ``zetup`` module.
        """
        return getattr(self.module, name)

    def __dir__(self):
        """Just delegate to native ``zetup`` module.
        """
        return list(self.__dict__) + dir(self.module)

    @property
    def Path(self):
        """Provide **path.py**-wrapping ``zetup.Path`` class
           if **path.py** is installed.
        """
        from .path import Path
        return Path

# replace native zetup module with wrapper
sys.modules[__name__] = ZetupModule()


# # Get zetup's own config:
# zfg = find_zetup_config(__name__)

# __distribution__ = zfg.DISTRIBUTION.find(__path__[0])
# __description__ = zfg.DESCRIPTION

# __version__ = zfg.VERSION

# __requires__ = zfg.REQUIRES # .checked
# ## if ismodule(zfg): # if this is an installed zetup package:
# ##     __requires__.check()

# __extras__ = zfg.EXTRAS

# ## __notebook__ = zfg.NOTEBOOKS['README']


def setup_entry_point(dist, keyword, value):
    """zetup's ``entry_point`` handler for ``setup()``
       in a project's **setup.py**, setting all setup keyword parameters
       based on zetup config.

    - Activated with ``setup(setup_requires=['zetup'], use_zetup=True)``
    """
    assert keyword == 'use_zetup'
    if not value:
        return

    zetup = Zetup() # reads config from current working directory
                    # (where setup.py is run)
    keywords = zetup.setup_keywords()
    for name, value in keywords.items():
        # Generally, setting stuff on dist.metadata is enough
        # (and necessary), but *pip* only works correct
        # if stuff is also set directly on dist object
        # (seems to read at least packages list somehow from there):
        for obj in [dist, dist.metadata]:
            setattr(obj, name, value)

    # finally run any custom setup hooks defined in project's zetup config,
    # but first check setup requirements
    if zetup.SETUP_REQUIRES:
        zetup.SETUP_REQUIRES.check()
    if zetup.SETUP_HOOKS:
        sys.path.insert(0, '.')
        for hook in zetup.SETUP_HOOKS:
            modname, funcname = hook.split(':')
            mod = __import__(modname)
            for subname in modname.split('.')[1:]:
                mod = getattr(mod, subname)
            func = getattr(mod, funcname)
            func(dist)
