import os

import setuptools

from darcsver import darcsvermodule

from distutils.errors import DistutilsSetupError

def validate_string_or_iter_of_strings(dist, attr, value):
    # value is required to be a string or else a list of strings
    if isinstance(value, basestring):
        return
    try:
        for thing in value:
            if not isinstance(thing, basestring):
                raise DistutilsSetupError("%r is required to be a string or an iterable of strings (got %r)" % (attr, value))
    except TypeError:
        raise DistutilsSetupError("%r is required to be a string or an iterable of strings (got %r)" % (attr, value))

def validate_versionfiles(dist, attr, value):
    return validate_string_or_iter_of_strings(dist, attr, value)

def validate_versionbodies(dist, attr, value):
    return validate_string_or_iter_of_strings(dist, attr, value)

def all(iterator):
    for thing in iterator:
        if not thing:
            return False
    return True

PYTHON_VERSION_BODY='''
# This is the version of this tree, as created by %(versiontool)s from the darcs patch
# information: the main version number is taken from the most recent release
# tag. If some patches have been added since the last release, this will have a
# -NN "build number" suffix, or else a -rNN "revision number" suffix. Please see
# pyutil.version_class for a description of what the different fields mean.

__pkgname__ = "%(pkgname)s"
verstr = "%(pkgversion)s"
try:
    from pyutil.version_class import Version as pyutil_Version
    __version__ = pyutil_Version(verstr)
except (ImportError, ValueError):
    # Maybe there is no pyutil installed, or this may be an older version of
    # pyutil.version_class which does not support SVN-alike revision numbers.
    from distutils.version import LooseVersion as distutils_Version
    __version__ = distutils_Version(verstr)
'''

class DarcsVer(setuptools.Command):
    description = "generate a version number from darcs history"
    user_options = [
        ('project-name', None, "name of the project as it appears in the project's release tags (default's the to the distribution name)"),
        ('filename', None, "path to file into which the version number should be written (defaults to the package directory's _version.py)"),
        ('count-all-patches', None, "If true, count the total number of patches in all history.  If false, count the total number of patches since the most recent release tag."),
        ('abort-if-snapshot', None, "If true, the if the current version is a snapshot (not a release tag), then immediately exit the process with exit code 0."),
        ]

    def initialize_options(self):
        self.project_name = None
        self.filename = None
        self.count_all_patches = None
        self.abort_if_snapshot = None

    def finalize_options(self):
        if self.project_name is None:
            self.project_name = self.distribution.get_name()

        # If the user passed --filename on the cmdline, override
        # the setup.py's versionfiles argument.
        if self.filename is not None:
            if not isinstance(self.filename, basestring):
                 raise TypeError("filename is required to be a string, not %s, filename: %s" % (type(self.filename), self.filename))
            self.distribution.versionfiles = [self.filename]

        if self.abort_if_snapshot is None:
            self.abort_if_snapshot=False

    def run(self):
        if self.distribution.versionfiles is None:
            toppackage = ''
            # If there is a package with the same name as the project name and
            # there is a directory by that name then use that.
            packagedir = None
            if self.distribution.packages and self.project_name in self.distribution.packages:
                toppackage = self.project_name
                srcdir = ''
                if self.distribution.package_dir:
                    srcdir = self.distribution.package_dir.get(toppackage)
                    if not srcdir is None:
                        srcdir = self.distribution.package_dir.get('', '')
                packagedir = os.path.join(srcdir, toppackage)

            if packagedir is None or not os.path.isdir(packagedir):
                # Else, if there is a singly-rooted tree of packages, use the
                # root of that.
                if self.distribution.packages:
                    for package in self.distribution.packages:
                        if not toppackage:
                            toppackage = package
                        else:
                            if toppackage.startswith(package+"."):
                                toppackage = package
                            else:
                                if not package.startswith(toppackage+"."):
                                    # Not singly-rooted
                                    toppackage = ''
                                    break

                srcdir = ''
                if self.distribution.package_dir:
                    srcdir = self.distribution.package_dir.get(toppackage)
                    if srcdir is None:
                        srcdir = self.distribution.package_dir.get('', '')
                packagedir = os.path.join(srcdir, toppackage)

            self.distribution.versionfiles = [os.path.join(packagedir, '_version.py')]

        if self.distribution.versionbodies is None:
            self.distribution.versionbodies = [PYTHON_VERSION_BODY]

        assert all([isinstance(vfn, basestring) for vfn in self.distribution.versionfiles]), self.distribution.versionfiles
        (rc, verstr) = darcsvermodule.update(self.project_name, self.distribution.versionfiles, self.count_all_patches, abort_if_snapshot=self.abort_if_snapshot, EXE_NAME="setup.py darcsver", version_body=self.distribution.versionbodies)
        if rc == 0:
            self.distribution.metadata.version = verstr
