# -*- coding: utf-8 -*-
#
# Copyright (C) 2005 - 2007, TUBITAK/UEKAE
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# Please read the COPYING file.
#

"""version structure"""

import re
import string
import exceptions

import pisi
import pisi.util as util

maxdashes = 2

# Basic rule is:
# p > (no suffix) > m > rc > pre > beta > alpha
# m: milestone. this was added for OO.o
# p: patch-level
keywords = {"alpha": 0,
            "beta" : 1,
            "pre"  : 2,
            "rc"   : 3,
            "m"    : 4,
            "NOKEY" : 5,
            "p"    : 6}

# helper functions
def has_keyword(versionitem):
    if versionitem._keyword != "NOKEY":
        return True

    return False

class VersionException(pisi.Exception):
    def __init__(self, str):
        pisi.Exception.__init__(self, str)

class VersionItem:
    _keyword = "NOKEY"
    _value = 0

    def __init__(self, itemstring):

        # special-case: 1.5p
        # here "p" comes as a separate itemstring and conflicts with
        # keyword "p". But keyword should allways contain an integer prefix.
        # So, skipping looking for keywords if the length() is 1 makes the
        # trick.
        if len(itemstring) > 1:

            for keyword in keywords.keys():
                if itemstring.startswith(keyword):

                    if self._keyword == "NOKEY":
                        self._keyword = keyword
                    else:
                        # longer match is correct
                        if len(keyword) > len(self._keyword):
                            self._keyword = keyword

        if self._keyword == "NOKEY":
            if len(itemstring) == 1 and itemstring in string.ascii_letters:
                # single letter version item ('a' to 'Z')
                self._value = itemstring
            else:
                self._value = int(itemstring)
        else:
            # rest is the version item's value. And each must have
            # one!
            self._value = int(itemstring[len(self._keyword):])

    def __str__(self):
        return str(self._value)

    def __lt__(self,rhs):
        l = keywords[self._keyword]
        r = keywords[rhs._keyword]
        if l < r:
            return True
        elif l == r:
            return self._value < rhs._value
        else: # l > r
            return False

    def __le__(self,rhs):
        l = keywords[self._keyword]
        r = keywords[rhs._keyword]
        if l < r:
            return True
        elif l == r:
            return self._value <= rhs._value
        else: # l > r
            return False

    def __gt__(self,rhs):
        l = keywords[self._keyword]
        r = keywords[rhs._keyword]
        if l > r:
            return True
        elif l == r:
            return self._value > rhs._value
        else: # l < r
            return False

    def __ge__(self,rhs):
        l = keywords[self._keyword]
        r = keywords[rhs._keyword]
        if l > r:
            return True
        elif l == r:
            return self._value >= rhs._value
        else: # l < r
            return False

    def __eq__(self,rhs):
        l = keywords[self._keyword]
        r = keywords[rhs._keyword]
        if l == r and self._value == rhs._value:
                return True
        return False

class Version:

    @staticmethod
    def valid(version):
        try:
            pisi.version.Version(version)
        except pisi.version.VersionException, e:
            return False
        return True

    def __init__(self, verstring):
        # PiSi version policy does not allow "-" in version strings.
        # They are special and used for build and release no separation.
        if verstring.count("-") > maxdashes:
            raise VersionException("%s is not a valid PiSi version format" % verstring)

        verchunks = verstring.split("-")
        verchunks.extend("0" * (maxdashes - verstring.count("-")))
        (version, release, build) = verchunks

        self.comps = []
        for i in util.multisplit(version,'._'):
            # some version strings can contain ascii chars at the
            # back. As an example: 2.11a
            # We split '11a' as two items like '11' and 'a'
            s = re.compile("[a-z-A-Z]$").search(i)
            if s:
                head = i[:s.start()]
                tail = s.group()
                self.comps.append(VersionItem(head))
                self.comps.append(VersionItem(tail))
            else:
                self.comps.append(VersionItem(i))

        self.verstring = verstring
        self.release = VersionItem(release)
        self.build = VersionItem(build)

    def string(self):
        return self.verstring

    def compare(self, ver):
        """this comparison routine is essentially a comparison routine
        for two rationals in (0,1) interval. we compare two sequences
        of digits one by one. We start with the leftmost digit
        in the expansion. If they are equal, we proceed to the next. If
        not we use the comparison operator. And we iterate to the left.
        The result is, 0 if two are equal, -1 if self < rhs, and +1
        if self>rhs"""
        lhs = self.comps
        rhs = ver.comps
        # pad the short version string with zeros
        if len(lhs) < len(rhs):
            lhs.extend( [VersionItem('0')] * (len(rhs) - len(lhs)) )
        elif len(lhs) > len(rhs):
            rhs.extend( [VersionItem('0')] * (len(lhs) - len(rhs)) )
        # now let's iterate from left to right in version items
        for (litem, ritem) in zip(lhs, rhs):
            if litem < ritem:
                return -1
            elif litem > ritem:
                return +1
        # now let's compare release and build
        lhs = (self.release, self.build)
        rhs = (ver.release, ver.build)
        for (litem, ritem) in zip(lhs, rhs):
            if litem < ritem:
                return -1
            elif litem > ritem:
                return +1
        return 0

    # premature optimization is the root of all evil

    def __lt__(self,rhs):
        return self.compare(rhs) < 0

    def __le__(self,rhs):
        return self.compare(rhs) <= 0

    def __gt__(self,rhs):
        return self.compare(rhs) > 0

    def __ge__(self,rhs):
        return self.compare(rhs) >= 0

    def __eq__(self,rhs):
        return self.compare(rhs) == 0

    def __str__(self):
        return self.verstring
