#!/usr/bin/env python3
#
# Copyright 2024 Richard Hughes <richard@hughsie.com>
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# pylint: disable=invalid-name,missing-docstring,too-few-public-methods

from typing import Dict
import os
import glob
import sys
import subprocess
import json
import argparse
from collections import defaultdict
from termcolor import colored


def _scan_file(fn: str) -> Dict[str, int]:
    new_map: Dict[str, int] = defaultdict(int)
    try:
        print(f"loading {fn}…")
        args = [
            "./src/fwupdtool",
            "firmware-parse",
            fn,
            "ifd",
            "--json",
            "--no-timestamp",
        ]
        p = subprocess.run(args, check=True, capture_output=True)
    except subprocess.CalledProcessError as e:
        print(f"{' '.join(args)}: {e}")
    else:
        for line in p.stdout.decode().split("\n"):
            new_map["Lines"] += 1
            if line.find("gtype=") == -1:
                continue
            sections = line.split('"')
            if not sections[1].startswith("Fu"):
                continue
            new_map[sections[1]] += 1
        for line in p.stderr.decode().split("\n"):
            if not line:
                continue
            new_map["WarningLines"] += 1
            print(line)
    return new_map


def _scan_dir(path: str, force_save: bool = False) -> bool:
    all_okay: bool = True
    needs_save: bool = False

    results: Dict[str, Dict[str, int]] = {}

    # support folders or paths
    if os.path.isdir(path):
        for fn in glob.glob(f"{path}/*.bin"):
            results[fn] = _scan_file(fn)
    else:
        results[path] = _scan_file(path)

    # go through each result
    print(f"    {os.path.basename(sys.argv[0])}:")
    for fn, new_map in results.items():
        try:
            with open(f"{fn}.json", "rb") as f:
                old_map = json.loads(f.read().decode())
        except FileNotFoundError:
            old_map = {}
        print(f"    {fn}")
        for key in sorted(set(list(old_map.keys()) + list(new_map.keys()))):
            cnt_old = old_map.get(key, 0)
            cnt_new = new_map.get(key, 0)
            if cnt_new > cnt_old:
                key_str: str = colored(key, "green")
                needs_save = True
            elif cnt_new < cnt_old:
                key_str = colored(key, "red")
                if key != "WarningLines":
                    all_okay = False
            else:
                continue
            print(f"    {key_str:36}: {cnt_old} -> {cnt_new}")

    # save new results if all better
    if (needs_save and all_okay) or force_save:
        for fn, new_map in results.items():
            with open(f"{fn}.json", "wb") as f:
                f.write(json.dumps(new_map, sort_keys=True, indent=4).encode())

    return all_okay


if __name__ == "__main__":
    rc: int = 0

    parser = argparse.ArgumentParser(
        prog="check-ifd-firmware", description="Check IFD firmware parsing"
    )
    parser.add_argument("paths", nargs="+")
    parser.add_argument(
        "--force-save", action="store_true", help="always save the json reports"
    )
    _args = parser.parse_args()
    for _path in _args.paths:
        if not _scan_dir(_path, force_save=_args.force_save):
            rc = 1
    sys.exit(rc)
