#!/usr/bin/python
#
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008, TUBITAK/UEKAE
#
# 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 (at your option)
# any later version.
#
# Please read the COPYING file.
#

import sys
import multiprocessing

import urllib2
import bz2
import lzma

import piksemel

import pisi
import pisi.dependency as dependency
from pisi.graph import CycleException


class SourceDB:
    def __init__(self, index):

        self.__source_nodes = {}
        self.__pkgstosrc = {}

        doc = piksemel.parseString(index)
        self.__source_nodes, self.__pkgstosrc = self.__generate_sources(doc)

    def __generate_sources(self, doc):
        sources = {}
        pkgstosrc = {}

        for spec in doc.tags("SpecFile"):
            src_name = spec.getTag("Source").getTagData("Name")
            sources[src_name] = spec.toString()
            for package in spec.tags("Package"):
                pkgstosrc[package.getTagData("Name")] = src_name

        return sources, pkgstosrc

    def has_spec(self, name):
        return self.__source_nodes.has_key(name)

    def get_spec(self, name):
        src = self.__source_nodes[name]
        spec = pisi.specfile.SpecFile()
        spec.parse(src)
        return spec

    def list_specs(self):
        return self.__source_nodes.keys()

    def pkgtosrc(self, name):
        return self.__pkgstosrc[name]

def find_circle(sourcedb, A):

    G_f = pisi.graph.Digraph()

    def get_spec(name):
        if sourcedb.has_spec(name):
            return sourcedb.get_spec(name)
        else:
            raise Exception('Cannot find source package: %s' % name)

    def get_src(name):
        return get_spec(name).source

    def add_src(src):
        if not str(src.name) in G_f.vertices():
            G_f.add_vertex(str(src.name), (src.version, src.release))

    def pkgtosrc(pkg):
        try:
            tmp = sourcedb.pkgtosrc(pkg)
        except KeyError, e:
            # this is a bad hack but after we hit a problem we need to continue
            tmp = "e3"
            print "---> borks in ", e

        return tmp

    B = A

    install_list = set()

    while len(B) > 0:
        Bp = set()
        for x in B:
            sf = get_spec(x)
            src = sf.source
            add_src(src)

            # add dependencies

            def process_dep(dep):
                srcdep = pkgtosrc(dep.package)
                if not srcdep in G_f.vertices():
                    Bp.add(srcdep)
                    add_src(get_src(srcdep))
                if not src.name == srcdep: # firefox - firefox-devel thing
                    G_f.add_edge(src.name, srcdep)

            for builddep in src.buildDependencies:
                process_dep(builddep)

            for pkg in sf.packages:
                for rtdep in pkg.packageDependencies:
                    process_dep(rtdep)
        B = Bp

        try:
            order_build = G_f.topological_sort()
            order_build.reverse()
        except CycleException, cycle:
            return str(cycle)

    return ""

def getIndex(uri):
    try:
        if "://" in uri:
            rawdata = urllib2.urlopen(uri).read()
        else:
            rawdata = open(uri, "r").read()
    except IOError:
        print "could not fetch %s" % uri
        return None

    if uri.endswith("bz2"):
        data = bz2.decompress(rawdata)
    elif uri.endswith("xz") or uri.endswith("lzma"):
        data = lzma.decompress(rawdata)
    else:
        data = rawdata

    return data

def processPackage(pkg, sourcesLength, counter):
    global sourcedb

    sys.stdout.write("\r(%04d/%d) Calculating build dep of %s                       " % (counter, sourcesLength, pkg))
    sys.stdout.flush()

    return find_circle(sourcedb, [pkg])

def updateStatus(circleResult):
    global cycles
    cycles.add(circleResult)


if __name__ == "__main__":

    if len(sys.argv) < 2:
        print "Usage: circlefinder.py <source repo pisi-index.xml file>"
        sys.exit(1)

    rawIndex = getIndex(sys.argv[1])
    sourcedb = SourceDB(rawIndex)
    sources = sourcedb.list_specs()

    sourcesLength = len(sources)
    counter = 0

    global cycles
    cycles = set()

    pool = multiprocessing.Pool()

    for pkg in sources:
        counter += 1
        pool.apply_async(processPackage, (pkg, sourcesLength, counter), callback=updateStatus)

    pool.close()
    pool.join()

    if len(cycles):
        print
        for cycle in cycles:
            print cycle
    else:
        print "No circular dep found"


