import fnmatch

from conans.errors import ConanException


class BuildMode(object):
    """ build_mode => ["*"] if user wrote "--build"
                   => ["hello*", "bye*"] if user wrote "--build hello --build bye"
                   => ["hello/0.1@foo/bar"] if user wrote "--build hello/0.1@foo/bar"
                   => False if user wrote "never"
                   => True if user wrote "missing"
                   => "outdated" if user wrote "--build outdated"
                   => ["!foo"] means exclude when building all from sources
    """
    def __init__(self, params, output):
        self._out = output
        self.outdated = False
        self.missing = False
        self.never = False
        self.cascade = False
        self.patterns = []
        self._unused_patterns = []
        self._excluded_patterns = []
        self.all = False
        if params is None:
            return

        assert isinstance(params, list)
        if len(params) == 0:
            self.all = True
        else:
            for param in params:
                if param == "outdated":
                    self.outdated = True
                elif param == "missing":
                    self.missing = True
                elif param == "never":
                    self.never = True
                elif param == "cascade":
                    self.cascade = True
                else:
                    # Remove the @ at the end, to match for "conan install pkg/0.1@ --build=pkg/0.1@"
                    clean_pattern = param[:-1] if param.endswith("@") else param
                    clean_pattern = clean_pattern.replace("@#", "#")
                    if clean_pattern and clean_pattern[0] == "!":
                        self._excluded_patterns.append(clean_pattern[1:])
                    else:
                        self.patterns.append(clean_pattern)

            if self.never and (self.outdated or self.missing or self.patterns or self.cascade):
                raise ConanException("--build=never not compatible with other options")
        self._unused_patterns = list(self.patterns) + self._excluded_patterns

    def forced(self, conan_file, ref, with_deps_to_build=False):
        def pattern_match(pattern_):
            return (fnmatch.fnmatchcase(ref.name, pattern_) or
                    fnmatch.fnmatchcase(repr(ref.copy_clear_rev()), pattern_) or
                    fnmatch.fnmatchcase(repr(ref), pattern_))

        for pattern in self._excluded_patterns:
            if pattern_match(pattern):
                try:
                    self._unused_patterns.remove(pattern)
                except ValueError:
                    pass
                conan_file.output.info("Excluded build from source")
                return False

        if conan_file.build_policy == "never":  # this package has been export-pkg
            return False

        if self.never:
            return False
        if self.all:
            return True

        if conan_file.build_policy_always:
            conan_file.output.info("Building package from source as defined by "
                                   "build_policy='always'")
            return True

        if self.cascade and with_deps_to_build:
            return True

        # Patterns to match, if package matches pattern, build is forced
        for pattern in self.patterns:
            if pattern_match(pattern):
                try:
                    self._unused_patterns.remove(pattern)
                except ValueError:
                    pass
                return True
        return False

    def allowed(self, conan_file):
        if self.missing or self.outdated:
            return True
        if conan_file.build_policy_missing:
            conan_file.output.info("Building package from source as defined by "
                                   "build_policy='missing'")
            return True
        return False

    def report_matches(self):
        for pattern in self._unused_patterns:
            self._out.error("No package matching '%s' pattern found." % pattern)
