# -*- coding: utf-8 -*-

# Copyright (C) 2011 Adam D. Barratt <adsb@debian.org>

# 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.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

import apt_pkg
import logging
from britney2 import SuiteClass


class MigrationItem(object):

    def __init__(self, package=None, version=None, architecture=None,
                 suite=None, is_removal=False, is_cruft_removal=False):

        if architecture is None:
            architecture = 'source'

        if is_cruft_removal:
            is_removal = True

        self._package = package
        self._version = version
        self._architecture = architecture
        self._suite = suite
        self._is_removal = is_removal
        self._is_cruft_removal = is_cruft_removal
        self._uvname = self.get_uvname()
        self._name = self.get_name()

    def get_name(self):
        name = self._package
        if self._architecture != "source":
            name = "%s/%s" % (name, self._architecture)
        if self._version:
            name = "%s/%s" % (name, self._version)
        if self._suite.excuses_suffix:
            name = "%s_%s" % (name, self._suite.excuses_suffix)
        if self._is_removal:
            name = "-%s" % (name)
        return name

    def get_uvname(self):
        name = self._package
        if self._architecture != "source":
            name = "%s/%s" % (name, self._architecture)
        if self._suite.excuses_suffix:
            name = "%s_%s" % (name, self._suite.excuses_suffix)
        if self._is_removal:
            name = "-%s" % (name)
        return name

    def __str__(self):
        if self.version is not None:
            return self.name
        else:
            return self.uvname

    def __eq__(self, other):
        isequal = False
        if self.uvname == other.uvname:
            if self.version is None or other.version is None:
                isequal = True
            else:
                isequal = self.version == other.version

        return isequal

    def __hash__(self):
        if not self.version:
            raise AssertionError("trying to hash unversioned MigrationItem: %s" %
                                 (self.name))

        return hash((self.uvname, self.version))

    def __lt__(self, other):
        return (self.uvname, self.version) < (other.uvname, other.version)

    @property
    def name(self):
        return self._name

    @property
    def is_removal(self):
        return self._is_removal

    @property
    def architecture(self):
        return self._architecture

    @property
    def package(self):
        return self._package

    @property
    def suite(self):
        return self._suite

    @property
    def version(self):
        return self._version

    @property
    def uvname(self):
        return self._uvname

    @property
    def is_cruft_removal(self):
        return self._is_cruft_removal


class MigrationItemFactory(object):

    def __init__(self, suites):
        self._suites = suites
        self._all_architectures = frozenset(suites.target_suite.binaries)
        logger_name = ".".join((self.__class__.__module__, self.__class__.__name__))
        self.logger = logging.getLogger(logger_name)

    def generate_removal_for_cruft_item(self, pkg_id):
        return MigrationItem(package=pkg_id.package_name,
                             version=pkg_id.version,
                             architecture=pkg_id.architecture,
                             suite=self._suites.target_suite,
                             is_cruft_removal=True,
                             )

    @staticmethod
    def _is_right_version(suite, package_name, expected_version):
        if package_name not in suite.sources:
            return False

        actual_version = suite.sources[package_name].version
        if apt_pkg.version_compare(actual_version, expected_version) != 0:
            return False

        return True

    def _find_suite_for_item(self, suites, suite_name, package_name, version, auto_correct):
        suite = suites.by_name_or_alias[suite_name]
        assert suite.suite_class != SuiteClass.TARGET_SUITE
        if version is not None and auto_correct and not self._is_right_version(suite, package_name, version):
            for s in suites.source_suites:
                if self._is_right_version(s, package_name, version):
                    suite = s
                    break
        return suite

    def parse_item(self, item_text, versioned=True, auto_correct=True):
        """

        :param item_text: The string describing the item (e.g. "glibc/2.5")
        :param versioned: If true, a two-part item is assumed to be versioned.
          otherwise, it is assumed to be versionless.  This determines how
          items like "foo/bar" is parsed (if versioned, "bar" is assumed to
          be a version and otherwise "bar" is assumed to be an architecture).
          If in doubt, use versioned=True with auto_correct=True and the
          code will figure it out on its own.
        :param auto_correct: If True, minor issues are automatically fixed
          where possible. This includes handling architecture and version
          being in the wrong order and missing/omitting a suite reference
          for items.  This feature is useful for migration items provided
          by humans (e.g. via hints) to avoid rejecting the input over
          trivial/minor issues with the input.
          When False, there will be no attempt to correct the migration
          input.
        :return: A MigrationItem matching the spec
        """
        suites = self._suites
        version = None
        architecture = None
        is_removal = False
        if item_text.startswith('-'):
            item_text = item_text[1:]
            is_removal = True
        parts = item_text.split('/', 3)
        package_name = parts[0]
        suite_name = suites.primary_source_suite.name
        if '_' in package_name:
            package_name, suite_name = package_name.split('_', 2)

        if len(parts) == 3:
            architecture = parts[1]
            version = parts[2]
        elif len(parts) == 2:
            if versioned:
                version = parts[1]
            else:
                architecture = parts[1]

        if auto_correct and version in self._all_architectures:
            (architecture, version) = (version, architecture)

        if architecture is None:
            architecture = 'source'

        if '_' in architecture:
            architecture, suite_name = architecture.split('_', 2)

        if is_removal:
            suite = suites.target_suite
        else:
            suite = self._find_suite_for_item(suites, suite_name, package_name, version, auto_correct)

        return MigrationItem(package=package_name,
                             version=version,
                             architecture=architecture,
                             suite=suite,
                             is_removal=is_removal,
                             )

    def parse_items(self, *args, **kwargs):
        return [self.parse_item(x, **kwargs) for x in args]