#!/usr/bin/env python3
"""A small pkg-config for the KolibriOS cross builds.

Real pkg-config is not installed on this machine, and several of the ports
(transmission, anything using AC_PKG_CHECK_MODULES) refuse to configure
without one.  This reads .pc files out of PKG_CONFIG_PATH and answers the
handful of queries autoconf actually makes.

Differences from the real thing that are deliberate:
  * --cflags and --libs may be given together, and both are printed, in that
    order.  The NetSurf tree's shim keeps only the last flag seen, which is
    why `pkg-config --cflags --libs libcurl` came back with the libs alone.
  * Requires and Requires.private are always followed; there is no static
    versus shared distinction here because every library in these builds is
    static anyway.
  * Version comparisons understand dotted numeric versions only.
"""
import os
import re
import sys


def search_path():
    raw = os.environ.get('PKG_CONFIG_PATH', '')
    raw = raw.replace('\\', '/')
    # C:/foo -> /c/foo, so that splitting on ':' cannot tear a drive letter off
    raw = re.sub(r'(?:^|(?<=[:;]))([A-Za-z]):/', lambda m: '/' + m.group(1).lower() + '/', raw)
    dirs = [d for d in re.split(r'[:;]+', raw) if d]
    dirs += ['/usr/lib/pkgconfig', '/usr/local/lib/pkgconfig']
    if os.name == 'nt':
        # This runs under native Windows Python but is called from MSYS shells,
        # so the paths arrive as /c/Kolibri/... and open() cannot see them.
        dirs = [re.sub(r'^/([A-Za-z])/', lambda m: m.group(1).upper() + ':/', d)
                for d in dirs]
    return dirs


DIRS = search_path()


def find_pc(name):
    for d in DIRS:
        p = os.path.join(d, name + '.pc')
        if os.path.isfile(p):
            return p
    return None


def load(name, _cache={}):
    if name in _cache:
        return _cache[name]
    path = find_pc(name)
    if not path:
        _cache[name] = None
        return None
    variables, fields = {}, {}
    with open(path, 'r', encoding='latin1') as f:
        for line in f:
            line = line.rstrip('\r\n')
            m = re.match(r'^([A-Za-z0-9_.]+)\s*=\s*(.*)$', line)
            if m:
                variables[m.group(1)] = m.group(2)
                continue
            m = re.match(r'^([A-Za-z0-9_.]+):\s*(.*)$', line)
            if m:
                fields[m.group(1).lower()] = m.group(2)

    def expand(s):
        for _ in range(8):
            new = re.sub(r'\$\{([A-Za-z0-9_.]+)\}',
                         lambda m: variables.get(m.group(1), ''), s)
            if new == s:
                break
            s = new
        return s

    for k in list(fields):
        fields[k] = expand(fields[k])
    _cache[name] = fields
    return fields


def collect(name, field, out, seen):
    key = (name, field)
    if key in seen:
        return
    seen.add(key)
    pc = load(name)
    if pc is None:
        return
    reqs = (pc.get('requires', '') + ' ' + pc.get('requires.private', '')).replace(',', ' ')
    for r in reqs.split():
        if r[0].isalpha():
            collect(r, field, out, seen)
    value = pc.get(field, '')
    if value:
        out.append(value)


def vtuple(v):
    return tuple(int(x) for x in re.findall(r'\d+', v)) or (0,)


def main():
    # autoconf's PKG_CHECK_MODULES passes the whole requirement as one
    # argument -- `--exists "libcurl >= 7.15.4"` -- so split inside each arg
    # too, or the module name never matches anything.
    args = []
    for a in sys.argv[1:]:
        args.extend(a.split())
    modes = [a for a in args if a.startswith('--')]
    words = [a for a in args if not a.startswith('-')]

    if '--version' in modes or '--atleast-pkgconfig-version' in modes:
        if '--atleast-pkgconfig-version' in modes:
            return 0
        print('0.29.2')
        return 0

    # "libfoo >= 1.2" arrives as three words; keep the module names
    mods, i = [], 0
    while i < len(words):
        w = words[i]
        if w in ('>=', '<=', '=', '>', '<'):
            i += 2
            continue
        if not w[0].isdigit():
            mods.append(w)
        i += 1

    if '--exists' in modes:
        return 0 if all(find_pc(m) for m in mods) else 1

    if '--modversion' in modes:
        for m in mods:
            pc = load(m)
            if pc is None:
                return 1
            print(pc.get('version', '0'))
        return 0

    for flag in ('--atleast-version', '--exact-version', '--max-version'):
        if flag in modes:
            want = args[args.index(flag) + 1] if args.index(flag) + 1 < len(args) else '0'
            for m in mods:
                pc = load(m)
                if pc is None:
                    return 1
                have = vtuple(pc.get('version', '0'))
                need = vtuple(want)
                if flag == '--atleast-version' and have < need:
                    return 1
                if flag == '--exact-version' and have != need:
                    return 1
                if flag == '--max-version' and have > need:
                    return 1
            return 0

    out = []
    if '--cflags' in modes or '--cflags-only-I' in modes or '--cflags-only-other' in modes:
        parts = []
        seen = set()
        for m in mods:
            collect(m, 'cflags', parts, seen)
        text = ' '.join(parts).split()
        if '--cflags-only-I' in modes:
            text = [t for t in text if t.startswith('-I')]
        elif '--cflags-only-other' in modes:
            text = [t for t in text if not t.startswith('-I')]
        out += text
    if '--libs' in modes or '--libs-only-L' in modes or '--libs-only-l' in modes or \
       '--libs-only-other' in modes:
        parts = []
        seen = set()
        for m in mods:
            collect(m, 'libs', parts, seen)
        for m in mods:
            collect(m, 'libs.private', parts, seen)
        text = ' '.join(parts).split()
        if '--libs-only-L' in modes:
            text = [t for t in text if t.startswith('-L')]
        elif '--libs-only-l' in modes:
            text = [t for t in text if t.startswith('-l')]
        elif '--libs-only-other' in modes:
            text = [t for t in text if not t.startswith(('-L', '-l'))]
        out += text

    if any(find_pc(m) is None for m in mods):
        return 1
    if out:
        print(' '.join(out))
    return 0


if __name__ == '__main__':
    sys.exit(main())
