#!/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 sys
if os.path.dirname(sys.argv[0]) != ".":
    if sys.argv[0][0] == "/":
	fullPath = os.path.dirname(sys.argv[0])
    else:
	fullPath = os.getcwd() + "/" + os.path.dirname(sys.argv[0])
else:
    fullPath = os.getcwd()

sys.path.append(os.path.dirname(fullPath))

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>]

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

sys.excepthook = util.genExcepthook()


def main(argv):
    listMissing = False
    doUpdate = False
    updateExceptions = ['kernel']
    display = {}
    displayOrder = ['change', 'name', 'local', 'version', 'flavor', 'repo']
    for param in displayOrder:
        display[param] = True
    display['flavor'] = False
    args = argv[1:]
    lastArg = ''
    while args:
        arg = args[0]
        args = args[1:]
        if arg == '--except':
            arg = args[0]
            args = args[1:]
            updateExceptions.extend(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
        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 = conarycfg.ConaryConfiguration()
    repos = netclient.NetworkRepositoryClient(cfg.repositoryMap)
    db = database.Database(cfg.root, cfg.dbPath)
    dbpkgs = [ x for x in db.iterAllTroveNames() if x.find(':') == -1 ]
    dbVersions = {}
    branchPkgs = {}
    for pkg in dbpkgs:
        if pkg in updateExceptions:
            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.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 = deps.formatFlavor(versionList[pkg][v][0])
                        if v.isAfter(lv) and v not in dbVersions:
                            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) ])

    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))
