#!/usr/bin/python

# all the crap that is stored on the rhn side of stuff
# updating/fetching package lists, channels, etc

import time

import rpm

import up2dateAuth
import up2dateLog
import up2dateUtils
import up2dateErrors
import xmlrpclib
import config
import rpcServer
import repoDirector
import rhnChannel
import rpmUtils
import rollbacks

# so we can use the same code with python 1.5.2 and 2.2
try:
    from rhn import rpclib
except ImportError:
    rpclib = __import__("xmlrpclib")

from rhpl.translate import _, N_



def remoteAddPackages(pkg):
    log = up2dateLog.initLog()
    log.log_me("Adding packages to package profile: %s" %
               up2dateUtils.pprint_pkglist(pkg))
    s = rpcServer.getServer()
    try:
        if type(pkg[0]) == type([]):
            #s.set_transport(2, 2)
            rpcServer.doCall(s.registration.add_packages,
                             up2dateAuth.getSystemId(), pkg)
        else:
            rpcServer.doCall(s.registration.add_packages,
                             up2dateAuth.getSystemId(), [pkg])            
    except rpclib.Fault, f:
        raise up2dateErrors.CommunicationError(f.faultString)

    updateTransactions()

def remoteDelPackages(pkg):
    log = up2dateLog.initLog()
    log.log_me("Removing packages from package profile: %s" %
               up2dateUtils.pprint_pkglist(pkg))
    s = rpcServer.getServer()

    try:
        if type(pkg[0]) == type([]):
            #s.set_transport(2, 2)
            rpcServer.doCall(s.registration.delete_packages,
                             up2dateAuth.getSystemId(), pkg)
        else:
            rpcServer.doCall(s.registration.delete_packages,
                             up2dateAuth.getSystemId(), [pkg])            
    except rpclib.Fault, f:
        raise up2dateErrors.CommunicationError(f.faultString)

    updateTransactions()

def updateTransactions():
    cfg = config.initUp2dateConfig()
    log = up2dateLog.initLog()
    if cfg["enableRollbacks"]:
        rollback = rollbacks.Rollback()
        log.log_me("Updating transaction list")
        s = rpcServer.getServer()
        try:
            rpcServer.doCall(s.registration.update_transactions,
                             up2dateAuth.getSystemId(),
                             time.time(),
                             rollback.getTransactionsData()
                             )
        except rpclib.Fault, f:
            if f.faultCode == 99:
                raise up2dateErrors.DelayError(f.faultString)
            else:
                raise up2dateErrors.CommunicationError(f.faultString)

def updatePackageProfile():
    log = up2dateLog.initLog()
    log.log_me("Updating package profile")
    s = rpcServer.getServer()


    try:
        #s.set_transport(2, 2)
        rpcServer.doCall(s.registration.update_packages,
                         up2dateAuth.getSystemId(),
                         rpmUtils.getInstalledPackageList())
    except rpclib.Fault, f:
        if f.faultCode == 99:
            raise up2dateErrors.DelayError(f.faultString)
        else:
            raise up2dateErrors.CommunicationError(f.faultString)

    updateTransactions()



avail_package_lst = []
# Fetch the raw package list. We weed it down and munge it
# more agressevily in the PackageList class, this function
# is just used in a couple places (like --showall) so it's
# seperated
def availablePackageList(msgCallback=None, progressCallback=None):
    log = up2dateLog.initLog()
    global avail_package_lst
    # if this is non-zero, we've already fetched it
    if len(avail_package_lst):
        return avail_package_lst

    availPkgList = []

    try:
        log.log_me("availablePackageList from network")
    except:
        pass

    # FIXME: replace with a up2date.getChannels() equilvinet when
    #        I find out where it belongs
    svrChannels = rhnChannel.getChannels()
    log.log_debug("availablePackageList::channels:", svrChannels)
    if not svrChannels:
        return None
    availPkgList = []


    repos = repoDirector.initRepoDirector()
    new_channels = []
    for channel in svrChannels.channels():
	if channel['label'] not in rhnChannel.channel_blacklist:
	    new_channels.append(channel)
    channels = new_channels

    for channel in channels:
        if msgCallback:
            msgCallback("Fetching package list for channel: %s" % channel['label'] )
        # a channel maybe empty, with no version
        if not channel['version']:
            continue
        package_list, type = rpcServer.doCall(repos.listPackages,
                                              channel,
                                              msgCallback = msgCallback,
                                              progressCallback = progressCallback)


        availPkgList = availPkgList + package_list

    avail_package_lst = availPkgList
    return availPkgList

all_avail_package_lst = []
def allAvailablePackageList(msgCallback=None, progressCallback=None):
    log = up2dateLog.initLog()
    global all_avail_package_lst
    # if this is non-zero, we've already fetched it
    if len(all_avail_package_lst):
        return all_avail_package_lst

    availPkgList = []

    try:
        log.log_me("allAvailablePackageList from network")
    except:
        pass

    repos = repoDirector.initRepoDirector()

    # FIXME: replace with a up2date.getChannels() equilvinet when
    #        I find out where it belongs
    svrChannels = rhnChannel.getChannels()
    log.log_debug("availablePackageList::channels:", svrChannels)
    if not svrChannels:
        return None
    availPkgList = []

    new_channels = []
    for channel in svrChannels.channels():
	if channel['label'] not in rhnChannel.channel_blacklist:
	    new_channels.append(channel)
    channels = new_channels
    
    for channel in channels:
        if msgCallback:
            msgCallback("Fetching all package list for channel: %s" % channel['label'] )
        # a channel maybe empty, with no version
        if not channel['version']:
            continue
        package_list,type = rpcServer.doCall(
            repos.listAllPackages,
            channel,
            msgCallback = msgCallback,
            progressCallback = progressCallback )


        availPkgList = availPkgList + package_list

    all_avail_package_lst = availPkgList
    return all_avail_package_lst

avail_package_list = []
# return the prefered arch, of the latest version
def getAvailablePackageList(msgCallback=None, progressCallback=None):
    log = up2dateLog.initLog()
    global avail_package_list

    if len(avail_package_list):
        return avail_package_list
    
    package_list = availablePackageList(
        msgCallback, progressCallback)

    availPkgsDict = {}
    for pkg in package_list:
        archScore = rpm.archscore(pkg[4])
        if archScore <= 0:
            # incompatible architecture
            continue

        pkgName = pkg[0]
        if not availPkgsDict.has_key(pkgName):
            availPkgsDict[pkgName] = pkg
            continue

        # Already seen this name
        ret = up2dateUtils.comparePackages(availPkgsDict[pkgName], pkg)
        if ret > 0:
            # don't care, we already have a better version
            continue
        if ret < 0:
            # Better version
            availPkgsDict[pkgName] = pkg
            continue
        # Same version, so compare the arches
        oldScore = rpm.archscore(availPkgsDict[pkgName][4])
        if oldScore <= archScore:
            continue
        # Package with a better architecture
        availPkgsDict[pkgName] = pkg

    # Sort the keys (i.e. the package names)
    avail_package_list = availPkgsDict.values()
    avail_package_list.sort()
#    print avail_package_list
    return avail_package_list

all_avail_package_all_arch_list = []
def getAllAvailableAllArchPackageList(msgCallback=None, progressCallback=None):
    global all_avail_all_arch_package_list

    if len(all_avail_package_all_arch_list):
        return all_avail_package_all_arch_list

    package_list = allAvailablePackageList(
        msgCallback, progressCallback)

    package_list.sort()
    all_avail_all_arch_package_lst = package_list
    return all_avail_all_arch_package_lst


all_avail_package_list = []
def getAllAvailablePackageList(msgCallback=None, progressCallback=None):
    global all_avail_package_list

    if len(all_avail_package_list):
        return all_avail_package_list

    package_list = allAvailablePackageList(
        msgCallback, progressCallback)

    pkgDict = {}
    for pkg in package_list:
        archScore = rpm.archscore(pkg[4])
        if archScore <= 0:
            # incompatible architecture
            continue

        nvre = tuple(pkg[0:3])
        # first package that matches, no need to test anything else
        if not pkgDict.has_key(nvre):
            pkgDict[nvre] = pkg
            continue

        
        oldScore = rpm.archscore(pkgDict[nvre][4])
        if oldScore <= archScore:
            continue

        # else this is a better arch, so use it...
        pkgDict[nvre] = pkg

    # should have a dict with the best 
    pkg_list = pkgDict.values()
    all_avail_package_lst = package_list
    return all_avail_package_lst

avail_package_all_arch_list = []
# return the latest version of the package, but all available arches of it
def getAvailableAllArchPackageList(msgCallback=None, progressCallback=None):
    global avail_package_all_arch_list

    if len(avail_package_all_arch_list):
        return avail_package_all_arch_list

    package_list = availablePackageList(
        msgCallback, progressCallback)


    # screw this, what this needs to do is to split
    # based on arch, this find the latest version available
    # for each arch

    archList = {}
    # key on arch, building a list of packages for each arch
    for pkg in package_list:
        if archList.has_key(pkg[4]):
            archList[pkg[4]].append(pkg)
        else:
            archList[pkg[4]] = [pkg]

    arches = archList.keys()

    availpkgs = []
    for arch in arches:
        availPkgsDict = {}
        pkglist = archList[arch]
        for pkg in pkglist:
            pkgName = pkg[0]
            
            if not availPkgsDict.has_key(pkgName):
                availPkgsDict[pkgName] = [pkg]
                continue
            
            # Already seen this name for this arch (this can happen with
            # mulitple repos). aka, multiple versions of the same package available
            ret = up2dateUtils.comparePackages(availPkgsDict[pkgName][0], pkg)
            #print "ret: %s  foo: %s  pkg: %s" % (ret, availPkgsDict[pkgName][0], pkg)
            if ret > 0:
                # don't care, we already have a better version
                continue
            if ret < 0:
                # Better version, replace the existing one
                availPkgsDict[pkgName] =  [pkg]
                continue
            if ret == 0:
                # this is where we could stick in fancy stuff to do channel
                # preference/scoring stuff... ie, this is where we have the same
                # package in multiple channels
#                print "this inst suppose to happen"
                continue

        # add the latest packages for each arch to the main list
        values = availPkgsDict.values()
        tmplist = []
        for value in values:
            for i in value:
                tmplist.append(i)
        availpkgs = availpkgs + tmplist

    avail_package_all_arch_list = availpkgs
    avail_package_all_arch_list.sort()
    return avail_package_all_arch_list



def obsoletesList(msgCallback=None, progressCallback=None):
    log = up2dateLog.initLog()
    obsoletesList = []
    svrChannels =  rhnChannel.getChannels()
    log.log_debug("obsoletesList::channels:", svrChannels)
    if not svrChannels:
        return None

    repos = repoDirector.initRepoDirector()
    
    new_channels = []
    for channel in svrChannels.channels():
	if channel['label'] not in rhnChannel.channel_blacklist:
	    new_channels.append(channel)
    channels = new_channels

    for channel in channels:
        # a channel maybe empty, with no version
        if not channel['version']:
            continue
        if msgCallback:
            # FIXME: plugin a useful message and dump it in
            # up2dateMessages for reuse
            msgCallback("Fetching Obsoletes list for channel: %s" % channel['label'])
        obsoletes_list, type = rpcServer.doCall(
            repos.getObsoletes,
            channel,
            msgCallback = msgCallback,
            progressCallback = progressCallback )        

        obsoletesList = obsoletesList + obsoletes_list

    return obsoletesList

