import re


class Version(str):
    """
    This is NOT an implementation of semver, as users may use any pattern in their versions.
    It is just a helper to parse "." or "-" and compare taking into account integers when possible
    """
    version_pattern = re.compile('[.-]')

    def __new__(cls, content):
        return str.__new__(cls, content.strip())

    @property
    def as_list(self):
        """
        Return version as a list of items
        :return: list with version items
        """
        if not hasattr(self, "_cached_list"):
            tokens = self.rsplit('+', 1)
            self._base = tokens[0]
            if len(tokens) == 2:
                self._build = tokens[1]
            self._cached_list = []
            tokens = Version.version_pattern.split(tokens[0])
            for item in tokens:
                self._cached_list.append(int(item) if item.isdigit() else item)
        return self._cached_list

    def major(self, fill=True):
        """
        Get the major item from the version string
        :param fill: Fill full version format with major.Y.Z
        :return: version class
        """
        self_list = self.as_list
        if not isinstance(self_list[0], int):
            return self._base
        v = str(self_list[0]) if self_list else "0"
        if fill:
            return Version(".".join([v, 'Y', 'Z']))
        return Version(v)

    def stable(self):
        """
        Get the stable version in a <major>.Y.Z format, otherwise return the version (semver 0.Y.Z
        is not considered stable)
        :return: version class with .Y.Z as ending
        """
        if self.as_list[0] == 0:
            return self
        return self.major()

    def minor(self, fill=True):
        """
        Get the minor item from the version string
        :param fill: Fill full version format with major.minor.Z
        :return: version class
        """
        self_list = self.as_list
        if not isinstance(self_list[0], int):
            return self._base
        v0 = str(self_list[0]) if len(self_list) > 0 else "0"
        v1 = str(self_list[1]) if len(self_list) > 1 else "0"
        if fill:
            return Version(".".join([v0, v1, 'Z']))
        return Version(".".join([v0, v1]))

    def patch(self):
        """
        Get the patch item from the version string
        :return: version class
        """
        self_list = self.as_list
        if not isinstance(self_list[0], int):
            return self._base
        v0 = str(self_list[0]) if len(self_list) > 0 else "0"
        v1 = str(self_list[1]) if len(self_list) > 1 else "0"
        v2 = str(self_list[2]) if len(self_list) > 2 else "0"
        return Version(".".join([v0, v1, v2]))

    def pre(self):
        self_list = self.as_list
        if not isinstance(self_list[0], int):
            return self._base
        v0 = str(self_list[0]) if len(self_list) > 0 else "0"
        v1 = str(self_list[1]) if len(self_list) > 1 else "0"
        v2 = str(self_list[2]) if len(self_list) > 2 else "0"
        v = ".".join([v0, v1, v2])
        if len(self_list) > 3:
            v += "-%s" % self_list[3]
        return Version(v)

    @property
    def build(self):
        """
        Return the build item from version string if any
        :return: build item string if present, otherwise return an empty string
        """
        self.as_list
        if hasattr(self, "_build"):
            return self._build
        return ""

    @property
    def base(self):
        """
        Return the base item from the version string
        :return: version class
        """
        self.as_list
        return Version(self._base)

    def compatible(self, other):
        """
        Determine if one version is compatible to other regarding to semver.
        Useful to check compatibility with major/minor versions with `<major>.Y.Z` format.
        :param other: version to compare to (string or version class)
        :return: compatible true or false
        """
        if not isinstance(other, Version):
            other = Version(other)
        for v1, v2 in zip(self.as_list, other.as_list):
            if v1 in ["X", "Y", "Z"] or v2 in ["X", "Y", "Z"]:
                return True
            if v1 != v2:
                return False
        return True

    def __cmp__(self, other):
        if other is None:
            return 1
        if not isinstance(other, Version):
            other = Version(other)

        # Check equals
        def get_el(a_list, index):
            if len(a_list) - 1 < index:
                return 0  # out of range, 4 == 4.0 == 4.0.0
            return a_list[index]

        equals = all(get_el(other.as_list, ind) == get_el(self.as_list, ind)
                     for ind in range(0, max(len(other.as_list), len(self.as_list))))
        if equals:
            if self.build == other.build:
                return 0
            if self.build > other.build:
                return -1
            else:
                return 1

        # Check greater than or less than
        other_list = other.as_list
        for ind, el in enumerate(self.as_list):
            if ind + 1 > len(other_list):
                if isinstance(el, int):
                    return 1
                return -1
            if not isinstance(el, int) and isinstance(other_list[ind], int):
                # Version compare with 1.4.rc2
                return -1
            elif not isinstance(other_list[ind], int) and isinstance(el, int):
                return 1
            elif el == other_list[ind]:
                continue
            elif el > other_list[ind]:
                return 1
            else:
                return -1
        if len(other_list) > len(self.as_list):
            return -1

    def __gt__(self, other):
        return self.__cmp__(other) == 1

    def __lt__(self, other):
        return self.__cmp__(other) == -1

    def __le__(self, other):
        return self.__cmp__(other) in [0, -1]

    def __ge__(self, other):
        return self.__cmp__(other) in [0, 1]

    def __eq__(self, other):
        return self.__cmp__(other) == 0

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return str.__hash__(self)
