#!/usr/bin/python3

# This file provides the necessary data and functions to deal with the
# Coq-related packages in Debian ; it's the basic foundation to be
# used in various tools

#
# FIRST PART: DATA
#

# here is the complete list of Coq-related src:packages in Debian
#
# J.Puydt: I don't see how to compute it... detecting B-Dep on dh-coq
# is the way to go, but I don't see how to get this information in the
# python3-apt API... this page can help maintain this up to date:
# https://release.debian.org/transitions/html/coq.html

known_sources = [

    # level 1

    'coq',

    # level 2

    'aac-tactics', 'coq-bignums', 'coq-dpdgraph', 'coq-elpi',
    'coq-ext-lib', 'coq-hammer', 'coq-hott', 'coq-libhyps',
    'coq-menhirlib', 'coq-record-update', 'coq-reduction-effects',
    'coq-stdpp', 'coq-unicoq', 'coq-unimath', 'flocq', 'ott',
    'paramcoq', 'ssreflect',

    # level 3

    'coq-deriving', 'coq-equations', 'coq-gappa',
    'coq-hierarchy-builder', 'coq-iris', 'coq-math-classes',
    'coq-mtac2', 'coq-reglang', 'coq-relation-algebra',
    'coq-simple-io', 'coqprime', 'coquelicot', 'mathcomp-bigenough',
    'mathcomp-finmap', 'mathcomp-zify',

    # level 4

    'coq-corn', 'coq-extructures', 'coq-interval', 'coq-quickchick',
    'mathcomp-algebra-tactics', 'mathcomp-analysis',
    'mathcomp-multinomials', 'mathcomp-real-closed',

    # level 5

    'coqeal', 'mathcomp-abel'

]

# dictionary - the keys are source package names in Debian, and
# the values are pairs (package, name) from which the test will be:
# From package Require Import name.
known_tests = {
    'aac-tactics': ('AAC_tactics', 'AAC'),
    'coq': ('Coq', 'Reals'),
    'coq-bignums': ('Bignums', 'BigZ'),
    'coq-corn': ('CoRN', 'fta.FTA'),
    'coq-deriving': ('deriving', 'deriving'),
    'coq-dpdgraph': ('dpdgraph', 'dpdgraph'),
    'coqeal': ('CoqEAL', 'theory.strassen'),
    'coq-elpi': ('elpi','elpi'),
    'coq-equations': ('Equations', 'Init'),
    'coq-ext-lib': ('ExtLib', 'Structures.Monad'),
    'coq-extructures': ('extructures', 'fmap'),
    'coq-gappa': ('Gappa', 'Gappa_common'),
    'coq-hammer': ('Hammer', 'Plugin.Hammer'),
    'coq-hierarchy-builder': ('HB', 'structures'),
    'coq-hott': ('HoTT', 'Basics.Nat'),
    'coq-interval': ('Interval', 'Tactic'),
    'coq-iris': ('iris', 'prelude'),
    'coq-libhyps': ('LibHyps', 'LibHyps'),
    'coq-math-classes': ('MathClasses', 'theory.functors'),
    'coq-menhirlib': ('MenhirLib', 'Version'),
    'coq-mtac2': ('Mtac2', 'Mtac2'),
    'coqprime': ('Coqprime', 'PrimalityTest.Pocklington'),
    'coq-quickchick': ('QuickChick', 'QuickChick'),
    'coq-record-update': ('RecordUpdate', 'RecordUpdate'),
    'coq-reduction-effects': ('ReductionEffect', 'PrintingEffect'),
    'coq-reglang': ('RegLang', 'shepherdson'),
    'coq-relation-algebra': ('RelationAlgebra', 'all'),
    'coq-simple-io': ('SimpleIO', 'SimpleIO'),
    'coq-stdpp': ('stdpp', 'base'),
    'coquelicot': ('Coquelicot', 'Coquelicot'),
    'coq-unicoq': ('Unicoq', 'Unicoq'),
    'coq-unimath': ('UniMath', 'All'),
    'flocq': ('Flocq', 'Version'),
    'mathcomp-abel': ('Abel', 'abel'),
    'mathcomp-algebra-tactics': ('mathcomp.algebra_tactics', 'ring'),
    'mathcomp-analysis': ('mathcomp.analysis', 'topology'),
    'mathcomp-bigenough': ('mathcomp', 'bigenough'),
    'mathcomp-finmap': ('mathcomp.finmap', 'set'),
    'mathcomp-multinomials': ('SsrMultinomials', 'mpoly'),
    'mathcomp-real-closed': ('mathcomp.real_closed', 'complex'),
    'mathcomp-zify': ('mathcomp', 'zify'),
    'ott': ('Ott', 'ott_list'),
    'paramcoq': ('Param', 'Param'),
    'ssreflect': ('mathcomp', 'ssreflect'),
}


#
# SECOND PART: FUNCTIONS
#

import apt
cache = apt.Cache()

def check_known_tests():
    for pkg in known_sources:
        if not pkg in known_tests:
            print(f"- {pkg} doesn't have a corresponding test to run!")

def guess_binary_name(srcname):
    if srcname.startswith('coq-'):
        return 'lib' + srcname
    if srcname == 'coq':
        return 'libcoq-stdlib'
    if srcname == 'ssreflect':
        return 'libcoq-mathcomp-ssreflect'
    return 'libcoq-' + srcname

def get_source_name(pkgname):
    pkg = cache[pkgname]
    version = pkg.versions[0]
    return version.source_name

def check_get_source_name():
    for srcname in known_sources:
        pkgname = guess_binary_name(srcname)
        guessed = get_source_name(pkgname)
        if guessed !=  srcname:
            print(f"- For {srcname} I guessed binary name {pkgname} then source {guessed} and it's not correct")
            return False
    return True

def guess_deps(srcname):
    pkg = cache[guess_binary_name(srcname)].candidate

    if srcname == 'coq':
        res = []
    else:
        res = ['coq']

    for dep in pkg.get_dependencies('Depends'):
        rawname = dep[0].name
        # we look for a 'libcoq-foo-deadbeef' provided by 'libcoq-foo'
        if not rawname.startswith('libcoq-'):
            continue
        try:
            pkgname = rawname[:rawname.rfind('-')]
            deppkg = cache[pkgname] # read just to detect non-existing
            depsrcname = get_source_name(pkgname)
            if depsrcname != srcname and depsrcname not in res and not depsrcname.endswith('-ocaml'):
                res.append(depsrcname)
        except:
            continue
    return res

coq_packages_deps = {srcname: guess_deps(srcname) for srcname in known_sources}

def group_by(ll, criterium):
    if ll == []:
        return []
    res = []
    val = criterium(ll[0])
    chunk = []
    for obj in ll:
        if criterium(obj) == val:
            chunk.append(obj)
        else:
            val = criterium(obj)
            res.append(chunk)
            chunk = [obj]
    res.append(chunk)
    return res

def hauteur(paquet):
    deps = coq_packages_deps[paquet]
    if deps == []:
        return 0
    return 1 + max([hauteur(child) for child in deps])

def transitive_deps(paquets):
    res = set(paquets)
    new_len = 1
    old_len = 0
    while new_len > old_len:
        old_len = len(res)
        neuu = set()
        for pkg in res:
            neuu.update(coq_packages_deps[pkg])
        res.update(neuu)
        new_len = len(res)
    res = list(res)
    res.sort(key=hauteur)
    return res

def transitive_rdeps(paquets):
    res = paquets
    found_something = True
    while found_something:
        found_something = False
        neuu = []
        for other, deps in coq_packages_deps.items():
            ens = set(deps)
            ens.intersection_update(res)
            if ens != set() and other not in res:
                found_something = True
                neuu.append(other)
        res.extend(neuu)
    res.sort(key=hauteur)
    return res

def minimal_to_install_all():
    res = ['coqide']
    for pkgname in coq_packages_deps.keys():
        if transitive_rdeps([pkgname]) == [pkgname]:
            res.append(guess_binary_name(pkgname))
    return ' '.join(res)

def next_binary_version(version):
    binindex = version.find('+b')
    if binindex == -1:
        return version + '+b1'
    return version[:binindex]+ '+b' + str(int(version[binindex+2:])+1)

def run_checks():
    print('List of issues:')
    check_known_tests()
    check_get_source_name()
    print('done.')

if __name__ == "__main__":
    run_checks()
