#!/usr/bin/env python
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import re
import sys

import deb_version


class PackageVersionIntervalEndpoint:
    def __init__(self, is_open, is_inclusive, version):
        self._is_open = is_open
        self._is_inclusive = is_inclusive
        self._version = version

    def _intersect(self, other, is_start):
        if self._is_open and other._is_open:
            return self
        if self._is_open:
            return other
        if other._is_open:
            return self
        cmp_code = self._version.__cmp__(other._version)
        if not is_start:
            cmp_code *= -1
        if cmp_code > 0:
            return self
        if cmp_code < 0:
            return other
        if not self._is_inclusive:
            return self
        return other

    def __str__(self):
        return 'PackageVersionIntervalEndpoint(%s, %s, %s)' % (
            self._is_open,
            self._is_inclusive,
            self._version,
        )

    def __eq__(self, other):
        if self._is_open and other._is_open:
            return True
        return (
            self._is_open == other._is_open
            and self._is_inclusive == other._is_inclusive
            and self._version == other._version
        )


class PackageVersionInterval:
    def __init__(self, string_rep, package, start, end):
        self.string_rep = string_rep
        self.package = package
        self.start = start
        self.end = end

    def contains(self, version):
        if not self.start._is_open:
            if self.start._is_inclusive:
                if version < self.start._version:
                    return False
            elif version <= self.start._version:
                return False
        if not self.end._is_open:
            if self.end._is_inclusive:
                if version > self.end._version:
                    return False
            elif version >= self.end._version:
                return False
        return True

    def intersect(self, other):
        return PackageVersionInterval(
            '',
            '',
            self.start._intersect(other.start, True),
            self.end._intersect(other.end, False),
        )

    def implies(self, other):
        if self.package != other.package:
            return False
        return self.intersect(other) == self

    def __str__(self):
        return 'PackageVersionInterval(%s)' % self.string_rep

    def __eq__(self, other):
        return self.start == other.start and self.end == other.end


class PackageVersionIntervalSet:
    def __init__(self, intervals):
        self.intervals = intervals

    def formatted(self):
        return ' | '.join([interval.string_rep for interval in self.intervals])

    def _interval_implies_other_intervals(self, interval, other_intervals):
        for other_interval in other_intervals:
            if interval.implies(other_interval):
                return True
        return False

    def implies(self, other):
        # This disjunction implies |other| if every term in this
        # disjunction implies some term in |other|.
        for interval in self.intervals:
            if not self._interval_implies_other_intervals(
                interval, other.intervals
            ):
                return False
        return True


def version_interval_endpoints_from_exp(op, version):
    open_endpoint = PackageVersionIntervalEndpoint(True, None, None)
    inclusive_endpoint = PackageVersionIntervalEndpoint(False, True, version)
    exclusive_endpoint = PackageVersionIntervalEndpoint(False, False, version)
    if op == '>=':
        return (inclusive_endpoint, open_endpoint)
    if op == '<=':
        return (open_endpoint, inclusive_endpoint)
    if op == '>>' or op == '>':
        return (exclusive_endpoint, open_endpoint)
    if op == '<<' or op == '<':
        return (open_endpoing, exclusive_endpoint)
    assert op == '='
    return (inclusive_endpoint, inclusive_endpoint)


def parse_dep(dep):
    """Parses a package and version requirement formatted by dpkg-shlibdeps.

    Args:
        dep: A string of the format "package (op version)"

    Returns:
        A PackageVersionInterval.
    """
    package_name_regex = r'[a-z][a-z0-9\+\-\.]+'
    match = re.match('^(%s)$' % package_name_regex, dep)
    if match:
        return PackageVersionInterval(
            dep,
            match.group(1),
            PackageVersionIntervalEndpoint(True, None, None),
            PackageVersionIntervalEndpoint(True, None, None),
        )
    match = re.match(
        r'^(%s) \(([\>\=\<]+) ([\~0-9A-Za-z\+\-\.\:]+)\)$' % package_name_regex,
        dep,
    )
    if match:
        (start, end) = version_interval_endpoints_from_exp(
            match.group(2), deb_version.DebVersion(match.group(3))
        )
        return PackageVersionInterval(dep, match.group(1), start, end)
    print >> sys.stderr, 'Failed to parse ' + dep
    sys.exit(1)


def parse_interval_set(deps):
    r"""Parses a disjunction of package version requirements.

    Args:
        deps: A string of the format
            "package \(op version\) (| package \(op version\))*"

    Returns:
        A list of PackageVersionIntervals
    """
    return PackageVersionIntervalSet(
        [parse_dep(dep.strip()) for dep in deps.split('|')]
    )
