import os, string, sys, re
import xml.dom.minidom
from subprocess import Popen, PIPE

try:
    # If we can import pyutil.version_class then use its regex.
    from pyutil import version_class
    VERSION_BASE_RE_STR = version_class.VERSION_BASE_RE_STR
except ImportError:
    # Else (perhaps a bootstrapping problem),then we'll use this
    # regex, which was copied from the pyutil source code on
    # 2007-10-30.
    VERSION_BASE_RE_STR="(\d+)(\.(\d+)(\.(\d+))?)?((a|b|c)(\d+))?"

def get_text(nodelist):
    rc = ""
    for node in nodelist:
        if node.nodeType == node.TEXT_NODE:
            rc = rc + node.data
    return rc

VERSION_BODY = '''
# This is the version of this tree, as created by %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.

verstr = "%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)
'''

def write_version_py(verstr, outfname, EXE_NAME):
    f = open(outfname, "wt+")
    f.write(VERSION_BODY % (EXE_NAME, verstr,))
    f.close()

def read_version_py(infname):
    try:
        verstrline = open(infname, "rt").read()
    except EnvironmentError:
        return None
    else:
        VSRE = r"^verstr = ['\"]([^'\"]*)['\"]"
        mo = re.search(VSRE, verstrline, re.M)
        if mo:
            return mo.group(1)

def update(pkgname, verfilename, revision_number=False, quiet=False, EXE_NAME="darcsver"):
    """
    @param revision_number 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.

    Returns a tuple of (exit code, new version string).
    """
    rc = -1
    cmd = ["changes", "--xml-output"]
    if not revision_number:
        cmd.append("--from-tag=^%s" % (pkgname,))

    try:
        p = Popen(["darcs"] + cmd, stdout=PIPE, universal_newlines=True)
    except OSError, ose:
        if ose.errno == 2 and '~' in os.environ['PATH']:
            expanded_path = os.environ['PATH'].replace('~', os.path.expanduser('~'))
            msg = ("WARNING: 'darcs' was not found. However '~' was found in your PATH. \n"
                   "Please note that bugs in python cause it to fail to traverse '~' in \n"
                   "the user's PATH.  Please fix your path, e.g. \nPATH=%s" )
            print msg % (expanded_path,)
        pass
    else:
        output = p.communicate()[0]
        rc = p.returncode
    if rc != 0:
        try:
            p = Popen(["realdarcs.exe"] + cmd, stdout=PIPE, universal_newlines=True)
        except OSError, ose:
            if ose.errno == 2 and '~' in os.environ['PATH']:
                expanded_path = os.environ['PATH'].replace('~', os.path.expanduser('~'))
                msg = ("WARNING: 'realdarcs.exe' was not found. However '~' was found in your PATH. \n"
                       "Please note that bugs in python cause it to fail to traverse '~' in \n"
                       "the user's PATH.  Please fix your path, e.g. \nPATH=%s" )
                print msg % (expanded_path,)
            pass
        else:
            output = p.communicate()[0]
            rc = p.returncode
        if rc != 0:
            if os.path.exists(verfilename):
                if not quiet:
                    print "%s: Failure from attempt to find version tags with 'darcs changes', and %s already exists, so leaving it alone." % (EXE_NAME, verfilename,)
                return (0, read_version_py(verfilename))
            else:
                if not quiet:
                    print "%s: Failure from attempt to find version tags with 'darcs changes', and %s doesn't exist." % (EXE_NAME, verfilename,)
                return (rc, None)

    # Filter out bad chars that can cause the XML parser to give up in despair.
    # (Thanks to lelit of the tailor project and ndurner and warner for this hack.)
    allbadchars = "".join([chr(i) for i in range(0x0a) + [0x0b, 0x0c] + range(0x0e, 0x20) + range(0x7f,0x100)])
    tt = string.maketrans(allbadchars, "-"*len(allbadchars))
    output = output.translate(tt)

    doc = xml.dom.minidom.parseString(output)

    changelog = doc.getElementsByTagName("changelog")[0]
    patches = changelog.getElementsByTagName("patch")
    regexstr = "^TAG %s-(%s)" % (pkgname, VERSION_BASE_RE_STR,)
    version_re = re.compile(regexstr)
    last_tag = None
    count_since_last_patch = 0
    for patch in patches:
        name = get_text(patch.getElementsByTagName("name")[0].childNodes)
        m = version_re.match(name)
        if m:
            last_tag = m.group(1)
            last_tag = last_tag.encode("utf-8")
            break
        else:
            count_since_last_patch += 1

    if not last_tag:
        if os.path.exists(verfilename):
            if not quiet:
                print "%s: I'm unable to find a tag in the darcs history matching \"%s\", so I'm leaving %s alone." % (EXE_NAME, regexstr, verfilename,)
            return (0, read_version_py(verfilename))
        else:
            if not quiet:
                print "%s: I'm unable to find a tag in the darcs history matching \"%s\", and %s doesn't exist." % (EXE_NAME, regexstr, verfilename,)
            return (0, None)

    if revision_number:
        if count_since_last_patch:
            # this is an interim version
            verstr = "%s-r%d" % (last_tag, len(patches))
        else:
            # this is a release
            verstr = last_tag
    else:
        if count_since_last_patch:
            # this is an interim version
            verstr = "%s-%d" % (last_tag, count_since_last_patch)
        else:
            # this is a release
            verstr = last_tag

    write_version_py(verstr, verfilename, EXE_NAME)
    if not quiet:
        print "%s: wrote '%s' into %s" % (EXE_NAME, verstr, verfilename,)
    return (0, verstr)
