import sys
import rpm
import os
from string import *
import types
import urllib
from translate import _
from translate import N_
from log import log
import time

ExcludePackages = { 'XFree86-3DLabs' : None, 	'XFree86-8514' : None,
                    'XFree86-AGX' : None, 	'XFree86-I128' : None,
                    'XFree86-Mach32' : None, 	'XFree86-Mach64' : None,
                    'XFree86-Mach8' : None, 	'XFree86-Mono' : None,
                    'XFree86-P9000' : None, 	'XFree86-S3' : None,
                    'XFree86-S3V' : None, 	'XFree86-SVGA' : None,
                    'XFree86-VGA16' : None,	'XFree86-W32' : None,

                    'kernel' : None,		'kernel-BOOT' : None,
                    'kernel-smp' : None,	'kernel-enterprise' : None,

                    'kinput2-canna' : None,	'kinput-canna-wnn4' : None,
                    'kinput2-wnn4' : None,	'kinput2-wnn6' : None }

# Package selection is complicated. Here are the rules:
#
# Calling package.select() forces the package on. No other rules apply.
# Calling package.unselect() forces the package off. No other rules apply.
#
# Else:
#
# Each package contains a list of selection chains. Each chain consists
# of a set of components. If all of the components in any chain are selected,
# the package is selected.
#
# Otherwise, the package is not selected.

CHECK_CHAIN	= 0
FORCE_SELECT	= 1
FORCE_UNSELECT	= 2

class Package:
    def __getitem__(self, item):
	return self.h[item]

    def __repr__(self):
	return "%s" % self.name

    def select(self):
	self.state = FORCE_SELECT
	self.selected = 1

    def unselect(self):
	self.state = FORCE_UNSELECT
	self.selected = 0

    def isSelected(self):
	return self.selected

    def updateSelectionCache(self):
	if self.state == FORCE_SELECT or self.state == FORCE_UNSELECT:
	    return

	self.selected = 0
	for chain in self.chains:
	    on = 1
	    for comp in chain:
		if not comp.isSelected(justManual = 0):
		    on = 0
                else:
                    if comp.pkgDict[self] != None:
                        on = 0
                        for expr in comp.pkgDict[self]:
                            if comp.set.exprMatch (expr):
                                on = 1
	    if on: 
		self.selected = 1

    def getState(self):
	return (self.state, self.selected)

    def setState(self, state):
	(self.state, self.selected) = state

    def addSelectionChain(self, chain):
	self.chains.append(chain)

    def __init__(self, header):
	self.h = header
	self.chains = []
	self.selected = 0
	self.state = CHECK_CHAIN
	self.name = header[rpm.RPMTAG_NAME]
	self.size = header[rpm.RPMTAG_SIZE]

class HeaderList:
    def selected(self):
	l = []
 	keys = self.packages.keys()
	keys.sort()
	for name in keys:
	    if self.packages[name].selected: l.append(self.packages[name])
	return l

    def has_key(self, item):
	return self.packages.has_key(item)

    def keys(self):
        return self.packages.keys()

    def values(self):
        return self.packages.values()

    def __getitem__(self, item):
	return self.packages[item]

    def list(self):
	return self.packages.values()

    def mergeFullHeaders(self, file):
	fd = os.open(file, os.O_RDONLY)
	rpm.mergeHeaderListFromFD(self.hdlist, fd, 1000004)
	os.close(fd)

    def preordered(self):
        preordered = 1
	for h in self.selected():
            if h[1000003] == None:
                preordered = 0
        return preordered

    def __init__(self, hdlist, compatPackages = None, noscore = 0):
        self.hdlist = hdlist
	self.packages = {}
	newCompat = []
	for h in hdlist:
	    name = h[rpm.RPMTAG_NAME]
            if noscore:
                self.packages[name] = Package(h)
                continue
	    score1 = rpm.archscore(h['arch'])
	    if (score1):
		if self.packages.has_key(name):
		    score2 = rpm.archscore(self.packages[name].h['arch'])
		    if (score1 < score2):
			newCompat.append(self.packages[name])
			self.packages[name] = Package(h)
		    else:
			newCompat.append(Package(h))
		else:
		    self.packages[name] = Package(h)
        if hdlist and not self.packages:
            raise RuntimeError, ("the header list was read, but no packages "
                                 "matching architecture '%s' were found."
                                 % os.uname()[4])

	if compatPackages != None:
	    # don't use list + here, as it creates a new object
	    for p in newCompat:
		compatPackages.append(p)

class HeaderListFromFile (HeaderList):

    def __init__(self, path, compatPackages = None, noscore = 0):
	hdlist = rpm.readHeaderListFromFile(path)
	HeaderList.__init__(self, hdlist, compatPackages = compatPackages,
			    noscore = noscore)

class HeaderListFD (HeaderList):
    def __init__(self, fd):
	hdlist = rpm.readHeaderListFromFD (fd)
	HeaderList.__init__(self, hdlist)

# A component has a name, a selection state, a list of included components,
# and a list of packages whose selection depends in some way on this component 
# being selected. Selection and deselection recurses through included 
# components.
#
# When the component file is parsed, the selection chain rules for the
# packages are built up. Component selection is used by the packages to
# determine whether or not they are selected.
#
# The selection state consists of a manually selected flag and an included
# selection count. They are separate to make UI coding easier.

class Component:
    def __len__(self):
	return len(self.pkgs)

    def __getitem__(self, key):
	return self.pkgs[key]

    def __repr__(self):
	return "comp %s" % (self.name)

    def __keys__(self):
	return self.pkgs.keys()

    def includesPackage(self, pkg):
	return self.pkgDict.has_key(pkg)

    def select(self, forInclude = 0):
	if forInclude:
	    self.selectionCount = self.selectionCount + 1
	else:
	    self.manuallySelected = 1

	for pkg in self.pkgs:
            pkg.updateSelectionCache()

	for comp in self.includes:
	    comp.select(forInclude = 1)

    def isSelected(self, justManual = 0):
	# don't admit to selection-by-inclusion
	if justManual:
	    return self.manuallySelected

	return self.manuallySelected or (self.selectionCount > 0)

    def unselect(self, forInclude = 0):
	if forInclude:
	    self.selectionCount = self.selectionCount - 1
	    if self.selectionCount < 0:
		self.selectionCount = 0
	else:
	    self.manuallySelected = 0

	for pkg in self.pkgs:
	    pkg.updateSelectionCache()

	for comp in self.includes:
	    comp.unselect(forInclude = 1)

    def addInclude(self, comp):
	self.includes.append(comp)

    def addPackage(self, p):
	self.pkgs.append(p)
	p.addSelectionChain([self])
	self.pkgDict[p] = None

    def addPackageWithExpression(self, expr, p):
        if not self.pkgDict.has_key (p):
            self.pkgDict[p] = [ expr ]
            self.pkgs.append(p)
            p.addSelectionChain([self])
        else:
            if type (self.pkgDict[p]) == type ([]):
                self.pkgDict[p].append (expr)
            else:
                self.pkgDict[p] = [ expr ]

    def addConditionalPackage(self, condComponent, p):
	self.pkgs.append(p)
	p.addSelectionChain([self, condComponent])
	self.pkgDict[p] = None

    def setDefault(self, default):
        self.default = default

    def setDefaultSelection(self):
	if self.default:
	    self.select()

    def getState(self):
	return (self.manuallySelected, self.selectionCount)

    def setState(self, state):
	(self.manuallySelected, self.selectionCount) = state

    def __init__(self, set, name, selected, hidden = 0):
        self.set = set
	self.name = name
	self.hidden = hidden
	self.default = selected
	self.pkgs = []
	self.pkgDict = {}
	self.includes = []
	self.manuallySelected = 0
	self.selectionCount = 0

class ComponentSet:
    def __len__(self):
	return len(self.comps)

    def __getitem__(self, key):
	if (type(key) == types.IntType):
	    return self.comps[key]
	return self.compsDict[key]

    def getSelectionState(self):
	compsState = []
	for comp in self.comps:
	    compsState.append((comp, comp.getState()))

	pkgsState = []
	for pkg in self.packages.list():
	    pkgsState.append((pkg, pkg.getState()))

	return (compsState, pkgsState)

    def setSelectionState(self, pickle):
	(compsState, pkgsState) = pickle

        for (comp, state) in compsState:
	    comp.setState(state)

	for (pkg, state) in pkgsState:
	    pkg.setState(state)
	    
    def sizeStr(self):
	megs = self.size()
	if (megs >= 1000):
	    big = megs / 1000
	    little = megs % 1000
	    return "%d,%03dM" % (big, little)

	return "%dM" % (megs)

    def totalSize(self):
	total = 0
	for pkg in self.packages.list():
	    total = total + (pkg[rpm.RPMTAG_SIZE] / 1024)
	return total

    def size(self):
	size = 0
	for pkg in self.packages.list():
	    if pkg.isSelected(): size = size + (pkg[rpm.RPMTAG_SIZE] / 1024)

	return size / 1024

    def keys(self):
	return self.compsDict.keys()

    def exprMatch(self, expr, tags = [ "lang", "arch" ]):
        theTags = []
        for tag in tags:
            theTags.append(tag)

        # no expression == true
        if not expr:
            return 1

        # XXX preserve backwards compatible behavior
        if self.allLangs and "lang" in theTags:
            theTags.remove ("lang")

        if "lang" in theTags:
            if os.environ.has_key('LINGUAS'):
                langs = split (os.environ['LINGUAS'], ':')
                if len (langs) == 1 and not langs[0]:
                    langs = None
            else:
                if os.environ.has_key('LANG'):
                    langs = [ os.environ['LANG'] ]
                else:
                    langs = None

            if langs == None:
                # no languages specified, install them all
                theTags.remove ("lang")

	if expr[0] != '(':
	    raise ValueError, "leading ( expected"
	expr = expr[1:]
	if expr[len(expr) - 1] != ')':
	    raise ValueError, "bad comps file [missing )]"
	expr = expr[:len(expr) - 1]

	exprList = split(expr, 'and')
	truth = 1
	for expr in exprList:
	    l = split(expr)

            if l[0] == "lang":
                if theTags and "lang" not in theTags:
                    newTruth = 1
                else:
		    #print "check", l, "in", langs
                    if len(l) != 2:
                        raise ValueError, "too many arguments for lang"
                    if l[1] and l[1][0] == "!":
                        newTruth = l[1][1:] not in langs
                    else:
                        newTruth = l[1] in langs
	    elif l[0] == "arch":
                if theTags and "arch" not in theTags:
                    newTruth = 1
                if len(l) != 2:
                    raise ValueError, "too many arguments for arch"
                if l[1] and l[1][0] == "!":
                    newTruth = l[1][1:] not in self.archList
                else:
                    newTruth = l[1] in self.archList
	    else:
		s = "unknown condition type %s" % (l[0],)
		raise ValueError, s

	    truth = truth and newTruth
	return truth

    def readCompsFile(self, filename, packages):
        connected = 0
        while not connected:
            try:
		file = urllib.urlopen(filename)
            except IOError, (errnum, msg):
		log("IOError %s occured getting %s: %s", filename,
			errnum, str(msg))
                time.sleep(5)
            else:
                connected = 1

	lines = file.readlines()

	file.close()
	top = lines[0]
	lines = lines[1:]
	if (top != "3\n" and top != "4\n"):
	    raise TypeError, "comp file version 3 or 4 expected"
	
	comp = None
        conditional = None
	self.comps = []
	self.compsDict = {}
        self.expressions = {}
	for l in lines:
#CJS -- allow for comments in the comps file with the standard # syntax
 	    if (find(l,"#") > -1):
		l = l[:index(l,"#")]
#CJS -- END
	    l = strip (l)
	    if (not l): continue
            expression = None

	    if (find(l, ":") > -1):
		(expression, l) = split(l, ":", 1)
                expression = strip (expression)
                l = strip(l)
                if expression and not expression[0] == '(':
                    # normalize expressions to all be of () type
                    expression = "(arch %s)" % (expression,)
                if not self.exprMatch (expression, tags = [ "arch" ]):
                    continue

	    if (find(l, "?") > -1):
                (trash, cond) = split (l, '?', 1)
                (cond, trash) = split (cond, '{', 1)
                conditional = self.compsDict[strip (cond)]
                continue

	    if (comp == None):
		(default, l) = split(l, None, 1)
		hidden = 0
		if (l[0:6] == "--hide"):
		    hidden = 1
		    (foo, l) = split(l, None, 1)
                (l, trash) = split(l, '{', 1)
                l = strip (l)
                if l == "Base":
                    hidden = 1
		comp = Component(self, l, default == '1', hidden)
	    elif (l == "}"):
                if conditional:
                    conditional = None
                else:
                    self.comps.append(comp)
                    self.compsDict[comp.name] = comp
                    comp = None
	    else:
		if (l[0] == "@"):
		    (at, l) = split(l, None, 1)
		    comp.addInclude(self.compsDict[l])
		else:
                    if conditional:
			# Let both components involved in this conditional
			# know what's going on.
                        comp.addConditionalPackage (conditional, packages[l])
			conditional.addConditionalPackage (comp, packages[l])
                    elif expression:
                        # this is a package with some qualifier prefixing it
                        # XXX last expression noted wins when setting up Everything.
                        self.expressions[packages[l]] = expression
                        comp.addPackageWithExpression (expression, packages[l])
                    else:
                        # if this package is listed anywhere without an expression, it can go in Everything.
                        self.expressions[packages[l]] = None
                        # this is a package.
                        comp.addPackage(packages[l])

        everything = Component(self, N_("Everything"), 0, 0)
        for package in packages.keys ():
	    if not ExcludePackages.has_key(packages[package][rpm.RPMTAG_NAME]):
                if self.expressions.has_key (packages[package]):
                    everything.addPackageWithExpression (self.expressions[packages[package]],
                                                         packages[package])
                else:
                    everything.addPackage (packages[package])
        self.comps.append (everything)
        self.compsDict["Everything"] = everything

	for comp in self.comps:
	    comp.setDefaultSelection()

    def updateSelections(self):
	for comp in self.comps:
            if comp.isSelected ():
                for pkg in comp.pkgs:
                    pkg.updateSelectionCache()
        
    def __repr__(self):
	s = ""
	for n in self.comps:
	    s = s + "{ " + n.name + " [";
	    for include in n.includes:
		s = s + " @" + include.name

	    for package in n:
		s = s + " " + str(package)
	    s = s + " ] } "

	return s

    def __init__(self, file, hdlist, arch = None, matchAllLang = 0):
        self.allLangs = matchAllLang
        self.archList = []
	if not arch:
	    import iutil
	    arch = iutil.getArch()

        self.archList.append(arch)

# always set since with can have i386 arch with i686 arch2, for example
#	arch2 = None
#	if arch == "sparc" and os.uname ()[4] == "sparc64":
#	    arch2 = "sparc64"
#
        arch2 = os.uname ()[4]
        if not arch2 in self.archList:
            self.archList.append (arch2)
        
	self.packages = hdlist
	self.readCompsFile(file, self.packages)
