#!/usr/bin/env python3
# Copyright (c) nexB Inc.
# Copyright (c) 2016-2019 Christoph Reiter
# SPDX-License-Identifier: MIT
#
# Based on MSYS2 web application code.
# download_url: https://raw.githubusercontent.com/msys2/msys2-web/628ec96975ab84b4e13567c8d4bdc25ad1a8f937/main.py
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

# https://wiki.archlinux.org/index.php/User:Apg#String_List_Fields
STRING_LIST_FIELDS = (
    # pkgname is special: this is a list in srcinfo and a string in pkginfo
    # as one source can yield many binaries
    "pkgname",
    "group",
    "license",
    "depend",
    "depends",
    "optdepend",
    "optdepends",
    "makedepend",
    "makedepends",
    "checkdepend",
    "checkdepends",
    "conflict",
    "conflicts",
    "replaces",
    "provides",
    # .BUILDINFO
    "buildenv",
    "options",
    "installed",
    # .SRCINFO
    "source",
    "md5sums",
    "sha1sums",
    "sha224sums",
    "sha256sums",
    "sha384sums",
    "sha512sums",
    "validpgpkeys",
)


def parse_pkginfo(text, string_list_fields=STRING_LIST_FIELDS):
    """
    Parse text from a .PKGINFO found in a binary package root.
    See https://wiki.archlinux.org/index.php/User:Apg#.PKGINFO for details
    Can also parse .SRCINFO files.

    For example, a .PKGINFO:
    >>> text = '''
    ... # Generated by makepkg 5.2.2
    ... pkgname = mingw-w64-x86_64-sqlcipher
    ... pkgbase = mingw-w64-sqlcipher
    ... pkgver = 4.4.2-1
    ... license = GPL
    ... pkgdesc = SQLite extension
    ... url = https://www.zetetic.net/sqlcipher/
    ... builddate = 1607372620
    ... packager = CI (msys2-autobuild/00f7657c/406699447)
    ... size = 5267906
    ... arch = any
    ... license = BSD
    ... depend = mingw-w64-x86_64-gcc-libs
    ... depend = mingw-w64-x86_64-openssl
    ... depend = mingw-w64-x86_64-readline
    ... makedepend = mingw-w64-x86_64-tcl'''
    >>> expected = {
    ...     'name': ['mingw-w64-x86_64-sqlcipher'],
    ...     'source_name': 'mingw-w64-sqlcipher',
    ...     'version': '4.4.2-1',
    ...     'licenses': ['GPL', 'BSD'],
    ...     'desc': 'SQLite extension',
    ...     'url': 'https://www.zetetic.net/sqlcipher/',
    ...     'builddate': '1607372620',
    ...     'packager': 'CI (msys2-autobuild/00f7657c/406699447)',
    ...     'size': '5267906',
    ...     'arch': 'any',
    ...     'depends': [
    ...         'mingw-w64-x86_64-gcc-libs',
    ...         'mingw-w64-x86_64-openssl',
    ...         'mingw-w64-x86_64-readline',
    ...     ],
    ...     'makedepends': ['mingw-w64-x86_64-tcl'],
    ... }
    >>> result = parse_pkginfo(text)
    >>> assert expected == result, result


    Another example of .PKGINFO truncated for bbrevity::

    >>> text = '''# Generated by makepkg 5.2.2
    ... pkgname = mingw-w64-x86_64-gcc-libs
    ... pkgbase = mingw-w64-gcc
    ... pkgver = 10.2.0-6
    ... depend = mingw-w64-x86_64-gmp
    ... depend = mingw-w64-x86_64-mpc
    ... depend = mingw-w64-x86_64-mpfr
    ... depend = mingw-w64-x86_64-libwinpthread
    ... '''
    >>> expected = {
    ... 'name': ['mingw-w64-x86_64-gcc-libs'],
    ... 'source_name': 'mingw-w64-gcc',
    ... 'version': '10.2.0-6',
    ... 'depends': ['mingw-w64-x86_64-gmp', 'mingw-w64-x86_64-mpc', 'mingw-w64-x86_64-mpfr', 'mingw-w64-x86_64-libwinpthread'],
    ... }
    >>> result = parse_pkginfo(text)
    >>> assert expected == result, result


    For example, using a .SRCINFO text::

    >>> text = '''# Generated by makepkg 5.2.1
    ... # Mon May 25 08:54:20 UTC 2020
    ... pkgbase = mingw-w64-libarchive
    ...     pkgdesc = Multi-format archive and compression library (mingw-w64)
    ...     pkgver = 3.4.3
    ...     pkgrel = 1
    ...     url = https://www.libarchive.org/
    ...     arch = any
    ...     license = BSD
    ...     makedepends = mingw-w64-x86_64-gcc
    ...     makedepends = mingw-w64-x86_64-pkg-config
    ...     depends = mingw-w64-x86_64-gcc-libs
    ...     depends = mingw-w64-x86_64-zstd
    ...     options = !libtool
    ...     options = strip
    ...     source = libarchive-3.4.3.tar.gz::https://libarchive.org/downloads/libarchive-3.4.3.tar.gz
    ...     source = 0001-libarchive-3.3.3-bcrypt-fix.patch
    ...     sha256sums = 19556c1c67aacdff547fd719729630444dbc7161c63eca661a310676a022bb01
    ...     sha256sums = 2c318a025029998a9389eb99ab80f733c0fcf3b4888421879f2f6b4530d7f522
    ...
    ... pkgname = mingw-w64-x86_64-libarchive
    ... pkgname = mingw-w64-x86_64-libarchive2'''
    >>> expected = {'source_name': 'mingw-w64-libarchive',
    ...     'desc': 'Multi-format archive and compression library (mingw-w64)',
    ...     'version': '3.4.3', 'release': '1', 'url': 'https://www.libarchive.org/',
    ...     'arch': 'any', 'licenses': ['BSD'],
    ...     'makedepends': ['mingw-w64-x86_64-gcc', 'mingw-w64-x86_64-pkg-config'],
    ...     'depends': ['mingw-w64-x86_64-gcc-libs', 'mingw-w64-x86_64-zstd'],
    ...     'options': ['!libtool', 'strip'],
    ...     'source': [
    ...         'libarchive-3.4.3.tar.gz::https://libarchive.org/downloads/libarchive-3.4.3.tar.gz',
    ...         '0001-libarchive-3.3.3-bcrypt-fix.patch'
    ...      ],
    ...     'sha256sums': [
    ...          '19556c1c67aacdff547fd719729630444dbc7161c63eca661a310676a022bb01',
    ...         '2c318a025029998a9389eb99ab80f733c0fcf3b4888421879f2f6b4530d7f522'],
    ...     'name': ['mingw-w64-x86_64-libarchive', 'mingw-w64-x86_64-libarchive2'],
    ... }
    >>> result = parse_pkginfo(text)
    >>> assert expected == result, result


    And another .SRCINFO example with a release and epoch::

    >>> text = ''' # Generated by makepkg 5.2.1
    ... # Sat May  9 19:53:19 UTC 2020
    ... pkgbase = mingw-w64-libspiro
    ...     pkgdesc = Simplifies the drawing of beautiful curves (mingw-w64)
    ...     pkgver = 20200505
    ...     pkgrel = 1
    ...     epoch = 1
    ...     url = https://libspiro.sourceforge.io/
    ...     arch = any
    ...     license = GPL
    ...     makedepends = mingw-w64-x86_64-gcc
    ...     makedepends = mingw-w64-x86_64-pkg-config
    ...     options = staticlibs
    ...     options = strip
    ...     source = libspiro-20200505.tar.gz::https://github.com/fontforge/libspiro/archive/20200505.tar.gz
    ...     sha256sums = 00be530b5c0ea9274baadf
    ...
    ... pkgname = mingw-w64-x86_64-libspiro'''
    >>> expected = {
    ...     'source_name': 'mingw-w64-libspiro',
    ...     'desc': 'Simplifies the drawing of beautiful curves (mingw-w64)',
    ...     'version': '20200505',
    ...     'release': '1',
    ...     'epoch': '1',
    ...     'url': 'https://libspiro.sourceforge.io/',
    ...     'arch': 'any',
    ...     'licenses': ['GPL'],
    ...     'makedepends': ['mingw-w64-x86_64-gcc', 'mingw-w64-x86_64-pkg-config'],
    ...     'options': ['staticlibs', 'strip'],
    ...     'source': ['libspiro-20200505.tar.gz::https://github.com/fontforge/libspiro/archive/20200505.tar.gz'],
    ...     'sha256sums': ['00be530b5c0ea9274baadf'],
    ...     'name': ['mingw-w64-x86_64-libspiro']}
    >>> result = parse_pkginfo(text)
    >>> assert expected == result, result
    """

    lines = text.splitlines()

    pkginfo = {}
    for line in lines:
        line = line.strip()
        if not line or line.startswith("#"):
            continue

        key, _, value = line.partition("=")
        key = key.strip()
        value = value.strip()

        existing_value = pkginfo.get(key)

        if existing_value:
            if not string_list_fields or (string_list_fields and key in string_list_fields):
                # we treat this field as a list
                if isinstance(existing_value, list):
                    pkginfo[key].append(value)
                else:
                    pkginfo[key] = [existing_value, value]
                continue
            else:
                raise Exception(
                    f"Multiple values but key is not a multivalue key: "
                    f"{key} = {existing_value} and {value}",
                    text,
                )

        if key in string_list_fields:
            value = [value]
        pkginfo[key] = value

    return normalize_package_data_keys(pkginfo)


PACKAGE_DATA_KEYS_MAPPING = {
    # desc_keys_mapping
    "%NAME%": "name",
    "%ARCH%": "arch",
    "%VERSION%": "version",
    "%FILENAME%": "file_name",
    "%SHA256SUM%": "sha256",
    "%DESC%": "desc",
    "%GROUPS%": "groups",
    "%LICENSE%": "licenses",
    "%FILES%": "files",
    "%DEPENDS%": "depends",
    "%PROVIDES%": "provides",
    "%CONFLICTS%": "conflicts",
    "%REPLACES%": "replaces",
    "%OPTDEPENDS%": "optdepends",
    "%MAKEDEPENDS%": "makedepends",
    "%CHECKDEPENDS%": "checkdepends",
    # pkginfo_keys_mapping
    "pkgname": "name",
    "pkgbase": "source_name",
    "pkgver": "version",
    "pkgrel": "release",
    "pkgdesc": "desc",
    "filename": "file_name",
    "group": "groups",
    "license": "licenses",
    "depend": "depends",
    "optdepend": "optdepends",
    "makedepend": "makedepends",
    "checkdepend": "checkdepends",
    "conflict": "conflicts",
}


def normalize_package_data_keys(data, translate=PACKAGE_DATA_KEYS_MAPPING):
    """
    Given a mapping of {name:value} of package data (from a desc or .PKGINFO
    file) return a new mapping with normalized names.
    """
    new_data = {}
    for old_key, value in data.items():
        new_key = translate.get(old_key)
        if new_key:
            new_data[new_key] = value
        else:
            new_data[old_key] = value
    return new_data
