#-*- coding: utf8 -*-


"""A small subset of the functionality implemented in libslapt exported to
Python.
Libslapt is part of slapt-get, by Jason Woodward (http://jaos.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
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 Library General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

"""


from ctypes import *
from .libslapt import *
from .config import global_config
import os
#import finalize #recipe 577242


__all__ = ["Repository", "Available", "Installed", "Package"]
__author__ = "rbistolfi"
__version__ = "0.2"


class Package(object):
    """A wrapper for slapt_pkg_info_t.
    
    Provides access to slapt_pkg_info_t structures and some convenient extra
    methods.
    It is initialized with a pointer to the C package object (PackagePointer)

    """
    def __init__(self, package_pointer):
        """Initialize a Package from a slapt_pkg_info_t pointer.
        
        """
        self._pointer = self._as_parameter_ = package_pointer
        self._contents = package_pointer.contents
        #finalize.track_for_finalization(self, self._pointer,slapt_free_pkg)

    def __getattr__(self, name):
        """Delegate to contents
        
        """
        return getattr(self._contents, name)

    def __hash__(self):
        """Provides a hash from package name and version. Useful for
        comparing.
        
        """
        name, version, arch, build = slapt_stringify_pkg(self).rsplit("-", 3)
        return hash("-".join([name, version]))

    def __eq__(self, other):
        """Returns True if the other package has the same name and version.
        
        """
        return hash(self) == hash(other)

    def __cmp__(self, other):
        """Compares packages using the C function provided by libslapt (sorts
        by version using qsort)
        
        """
        if not isinstance(other, Package):
            raise TypeError("Operation not supported for these types")
        return slapt_cmp_pkgs(self, other)

    def __repr__(self):
        """Return a useful representation of a Package instance.
        
        """
        return "Package('%s')" % slapt_stringify_pkg(self)

    @property
    def required(self):
        """Return a list of package names for required dependencies
        
        """
        required = self._contents.required
        deps = required.split(",")
        names = []
        for dep in deps:
            items = dep.split()
            if items:
                name = items[0]
                name = name.strip()
                names.append(name)
        return names

    @property
    def required_packages(self):
        """Return a PackageList with dependencies
        
        """
        config = global_config
        available = Available()
        installed = Installed()
        pkg = self
        deps = slapt_init_pkg_list(None)
        conflict = libslapt.slapt_init_pkg_err_list(None)
        missing = libslapt.slapt_init_pkg_err_list(None)
        err = slapt_get_pkg_dependencies(
                config,
                available,
                installed,
                pkg,
                deps,
                conflict,
                missing
        )
        if err > 0:
            raise RuntimeError("slapt_get_pkg_dependencies returned: %s" % err)
        return PackageList(deps)

    def is_excluded(self):
        """Return True if pakcage is excluded
        
        """
        return slapt_is_excluded(global_config, self) > 0

    def install(self):
        """Install this package and its dependencies
        
        """
        #XXX handle conflicts, missing and suggests
        #XXX handle install vs reinstall vs upgrade
        config = global_config 
        available = Available()
        installed = Installed()
        transaction = slapt_init_transaction(None)
        slapt_add_deps_to_trans(config, transaction, available, installed, self)
        slapt_add_install_to_transaction(transaction, self)
        slapt_handle_transaction(config, transaction)


class PackageList(object):
    """A wrapper around slapt_pkg_list. Provides safe access to the
    structure.
    Supports iteration and item access.
    Usually you don't need to use this class directly, subclasses with
    straight forward constructors are provided.

    """
    def __init__(self, package_list_pointer):
        """PackageList initialization from a slapt_pkg_list pointer
        (PackageListPointer)
        
        """
        self._pointer = self._as_parameter_ = package_list_pointer
        self._contents = package_list_pointer.contents
        self._counter = 0

        #finalize.track_for_finalization(self, self._pointer,
        #        slapt_free_pkg_list)

    def __getattr__(self, name):
        """Delegates access to the structure fields when needed.
        
        """
        return getattr(self._contents, name)

    def __iter__(self):
        """This class supports the iteration protocol.
        
        """
        return self

    def __len__(self):
        """The length is already provided by the original C structure.
        
        """
        return self.pkg_count

    def __contains__(self, package):
        """Test if a package is in the package list.
        
        """
        if not isinstance(package, Package):
            return False
        if slapt_get_exact_pkg(self, package.name,
                package.version):
            return True
        return False

    def next(self):
        """Return the next package in the list. State of the stream is kept
        in self._counter. This counter is reset to 0 when the end of the list
        is reached.
        
        """
        # pointers have no len, we need to do the bookkeeping. Since our
        # basic data structure knows the len (self.pkg_count), we use that.
        if self._counter >= self.pkg_count:
            self._counter = 0
            raise StopIteration
        package = self.pkgs[self._counter]
        self._counter += 1
        return Package(package)

    def get(self, name, version=None):
        """Get a package from the list, by string.
        
        """
        if version is None:
            p = slapt_get_newest_pkg(self, name)
        else:
            p = slapt_get_exact_pkg(self, name, version)
        return Package(p)


class PackageError(object):

    def __init__(self, package_error_pointer):
        """PackageList initialization from a slapt_pkg_list pointer
        (PackageListPointer)
        
        """
        self._pointer = self._as_parameter_ = package_error_pointer
        self._contents = package_error_pointer.contents


class PackageErrorList(object):

    def __init__(self, package_error_list_pointer):
        """PackageList initialization from a slapt_pkg_list pointer
        (PackageListPointer)
        
        """
        self._pointer = self._as_parameter_ = package_error_list_pointer
        self._contents = package_error_list_pointer.contents

    def __len__(self):
        return self.err_count


## PackageList subclasses ########


class Repository(PackageList):
    """Representation of a Repository, initialized with it's PACKAGES.TXT file
    path.
    
    """
    def __init__(self, filepath):
        """PackageList initialization from a path to PACKAGES.TXT
        
        """
        package_list_pointer = slapt_get_pkg_list_from_filepath(filepath)
        PackageList.__init__(self, package_list_pointer)


class Installed(PackageList):
    """List of installed packages.
    
    """
    def __init__(self):
        """Initialize a PackageList using the slapt_get_installed_pkgs C
        function provided by libslapt.
        
        """
        package_list_pointer = slapt_get_installed_pkgs()
        PackageList.__init__(self, package_list_pointer)


class Available(PackageList):
    """List of available packages from all the repositories configured in the
    slapt-get configuration file.
    
    """
    def __init__(self):
        """PackageList initialization from a file path to the package_data
        file. This file is created by slapt-get and its location is defined by
        the slapt-get configuration file.
        
        """
        cwd = os.getcwd()
        try:
            os.chdir(global_config.working_dir)
            package_list_pointer = slapt_get_available_pkgs()
            PackageList.__init__(self, package_list_pointer)
        finally:
            os.chdir(cwd)
