#!/usr/bin/python2.4
# -*- mode: python -*-
#
# Copyright (c) 2004-2005 Specifix, Inc.
#
# This program is distributed under the terms of the Common Public License,
# version 1.0. A copy of this license should have been distributed with this
# source file in a file called LICENSE. If it is not present, the license
# is always available at http://www.opensource.org/licenses/cpl.php.
#
# This program is distributed in the hope that it will be useful, but
# without any waranty; without even the implied warranty of merchantability
# or fitness for a particular purpose. See the Common Public License for
# full details.
#

""" Compares local packages against the current install label and 
    lists those that are out of date """

import os
import re
import sys

import conary
import conarycfg
from deps import deps
from local import database
from lib import util
from repository import netclient
from repository import repository
import versions
import updatecmd

def usage():
    print """Usage: yuck [--missing] [--update] [--except <exception,...>]
            [--display|--display-only <displayparam>,<displayparam>] 
            [--root <root>]

        display parameters: [no]change,name,local,version,repo,flavor
         """                      
    return 1

sys.excepthook = util.genExcepthook()


def main(argv):
    listMissing = False
    doUpdate = False
    updateExceptions = [ re.compile('kernel$')]
    display = {}
    displayOrder = ['change', 'name', 'local', 'version', 'flavor', 'repo']
    for param in displayOrder:
        display[param] = True
    display['flavor'] = False
    args = argv[1:]
    lastArg = ''
    cfg = conarycfg.ConaryConfiguration()

    while args:
        arg = args[0]
        args = args[1:]
        if arg == '--except':
            arg = args[0]
            args = args[1:]
            updateExceptions.extend([re.compile(x) for x in arg.split(',')])
            lastArg = ''
            continue
        elif arg == '--missing':
            listMissing = True
        elif arg == '--display' or arg == '--display-only':
            disptype = arg
            if arg == '--display-only':
                for param in display.keys():
                    display[param] = False
            arg = args[0]
            args = args[1:]
            params = arg.split(',')
            for param in params:
                if param.startswith('no'):
                    value = False
                    param = param[2:]
                else:
                    value = True
                if param not in display:
                    usage()
                    print "Error: Unknown display parameter %s.  Available options: %s" % (param, displayOrder)
                    return 1
                display[param] = value
        elif arg == '--update':
            doUpdate = True
        elif arg == '--root':
            cfg.configLine('root %s' % args[0])
            args = args[1:]
        else:
            usage()
            print "Error: unknown option", arg
            return 1

    # remove items from displayOrder that we don't plan on displaying:
    for param in display.keys():
        if not display[param]:
            displayOrder.remove(param)
    
    cfg.initializeFlavors()
    
    repos = netclient.NetworkRepositoryClient(cfg.repositoryMap)
    db = database.Database(cfg.root, cfg.dbPath)
    dbTroves = dict.fromkeys(db.iterAllTroveNames())
    dbpkgs = dict.fromkeys(x for x in dbTroves if x.find(':') == -1)
    dbComps = [ x for x in dbTroves if x.split(':')[0] not in dbpkgs ]
    dbpkgs = dbpkgs.keys() + dbComps
    branchPkgs = {}
    for pkg in dbpkgs:
        if [x for x in updateExceptions if x.match(pkg)]:
            continue
        if pkg.startswith('group-'):
            continue
        dbVersions = db.getTroveVersionList(pkg, withFlavors=True)
        for v,f in dbVersions:
            branch = v.branch()
            if isinstance(branch.label(), versions.CookLabel) or\
               isinstance(branch.label(), versions.EmergeLabel):
                continue
            if branch not in branchPkgs:
                branchPkgs[branch] = {}
            if pkg not in branchPkgs[branch]:
                branchPkgs[branch][pkg] = []
            branchPkgs[branch][pkg].append((v, f))
    query = {}
    for branch in branchPkgs:
        host = branch.label().getHost()
        if host not in query:
            query[host] = {}
        for pkg in branchPkgs[branch]:
            if pkg not in query:
                query[host][pkg] = {}
            for v, f in branchPkgs[branch][pkg]:
                # XXX this fails when multiple versions are installed from the 
                # same branch -- only one new version is returned 
                queryflavor = cfg.flavor[0].copy()
                queryflavor.union(f, mergeType=deps.DEP_MERGE_TYPE_OVERRIDE)
                query[host][pkg][branch] = [queryflavor]
    dbpkgs.sort()


    updates = []
    notavail = []
    for host in query:
        try:
            versionList = repos.getTroveLeavesByBranch(query[host])
        except repository.OpenError, e:
            print >>sys.stderr, "Warning: could not access %s: %s" % (host, e)
            print >>sys.stderr, "Skipping packages %s" % query[host].keys()
            continue
        for pkg in query[host]:
            for branch in query[host][pkg]:
                dbVersions = branchPkgs[branch][pkg]
                try:
                    repoVersions = versionList[pkg].keys()
                except KeyError:
                    repoVersions = []
                # only look for updates to this version on the same branch
                for lv,flavor in dbVersions:
                    if not repoVersions:
                        lvt = lv.trailingRevision()
                        notavail.append((branch.asString(), pkg, lv, None, ''))
                    else:
                        repoVersions.sort()
                        v = repoVersions[-1]
                        f = versionList[pkg][v][0]
                        if v.isAfter(lv) and (v, f) not in dbVersions:
                            f = deps.formatFlavor(f)
                            updates.append((branch.asString(), pkg, lv, v, f))
    maxL = 0
    maxF = 0
    maxP = 0
    maxLV = 0
    if notavail:
        maxV = len('<Not Avail>')
    else:
        maxV = 0

    
    for (label, pkg, lv, v, f) in updates + notavail:
        maxL = max(maxL, len(label))
        maxP = max(maxP, len(pkg))
        maxF = max(maxF, len(f))
        maxLV = max(maxLV, len(lv.trailingRevision().asString()))
        if v:
            maxV = max(maxV, len(v.trailingRevision().asString()))

    displayVars = {}
    displayVars['repo'] = ("%-*s" , [maxL])
    displayVars['change'] = ("%-3s" , [])
    displayVars['name'] = ("%-*s" , [maxP + 3])
    displayVars['flavor'] = ("%-*s" , [maxF + 3])
    displayVars['local'] = ("%-*s" , [maxLV + 3])
    displayVars['version'] = ("%-*s" , [maxV + 3])

    displayStr = []
    for param in displayOrder:
        displayStr.append(displayVars[param][0])
    displayStr = ''.join(displayStr)
    
    updates.sort()
    notavail.sort()

    for (label, pkg, lv, v, f) in updates + notavail:
        lvt = lv.trailingRevision()
        if v:
            vt = v.trailingRevision()
            if lvt.getVersion() != vt.getVersion():
                change = 'V'
            elif lvt.getSourceCount() != vt.getSourceCount():
                change = 'S'
            else:
                change = 'B'
            vt = vt.asString()
        else:
            vt = '<Not Avail>'
            change = ' '
        lvt = lvt.asString()
        displayVals = {}
        displayVals['local'] = lvt
        displayVals['version'] = vt
        displayVals['flavor'] = f
        displayVals['change'] = change
        displayVals['repo'] = label
        displayVals['name'] =  pkg
        

        
        displayArgs = []
        for param in displayOrder:
            displayArgs.extend(displayVars[param][1])
            displayArgs.append(displayVals[param])

        print displayStr % tuple(displayArgs)
        #print "%s  %-*s   %-*s   %-*s    %-*s" % (change, maxP, pkg, maxLV, lvt, maxV, vt, maxL, label.asString())
        if doUpdate:
            updatecmd.doUpdate(cfg, [ "%s=%s[%s]" %(pkg, v.asString(), f) ],
                               callback=updatecmd.UpdateCallback())

    if listMissing:
        missing = []
        repoMap = {}
        for label in cfg.installLabelPath:
            repopkgs = [ x for x in repos.troveNames(label) if x.find(':') == -1 and not (x.startswith('cross-') or x.startswith('bootstrap-')) ]
            for pkg in repopkgs:
                if pkg not in repoMap:
                    repoMap[pkg] = label
        allpkgs = repoMap.keys()
        allpkgs.sort()
        for pkg in allpkgs:
            if not dbpkgs:
                missing.append(pkg)
                continue
            elif pkg != dbpkgs[0]:
                while dbpkgs and dbpkgs[0] < pkg:
                    del dbpkgs[0]
                if not dbpkgs or pkg != dbpkgs[0]: 
                    missing.append(pkg)
            else: 
                del dbpkgs[0]
        maxL = 0
        maxP = 0
        for pkg in missing:
            maxP = max(maxP, len(pkg))
            maxL = max(maxL, len(repoMap[pkg].asString()))
        print "=" * 78
        displayVals = {}
                

        for pkg in missing:
            displayVals['local'] = '<Not Installed>'
            displayVals['version'] = ''
            displayVals['change'] = ''
            displayVals['repo'] = repoMap[pkg].asString()
            displayVals['name'] =  pkg
            displayArgs = []
            for param in displayOrder:
                displayArgs.extend(displayVars[param][1])
                displayArgs.append(displayVals[param])
            print displayStr % tuple(displayArgs)

           # print "   %-*s    <Not Installed>    %-*s" % (maxP, pkg, maxL, repoMap[pkg].asString())
    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv))
