#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import hashlib
import os
import sys

from os.path import basename

from fontTools.ttLib import TTFont


def write_checksum(
    filepaths,
    stdout_write=False,
    use_ttx=False,
    include_tables=None,
    exclude_tables=None,
    do_not_cleanup=False,
):
    checksum_dict = {}
    for path in filepaths:
        if not os.path.exists(path):
            sys.stderr.write(
                "[checksum.py] ERROR: "
                + path
                + " is not a valid file path"
                + os.linesep
            )
            sys.exit(1)

        if use_ttx:
            # append a .ttx extension to existing extension to maintain data about the binary that
            # was used to generate the .ttx XML dump.  This creates unique checksum path values for
            # paths that would otherwise not be unique with a file extension replacement with .ttx
            # An example is woff and woff2 web font files that share the same base file name:
            #
            #  coolfont-regular.woff  ==> coolfont-regular.ttx
            #  coolfont-regular.woff2 ==> coolfont-regular.ttx  (KAPOW! checksum data lost as this would overwrite dict value)
            temp_ttx_path = path + ".ttx"

            tt = TTFont(path)
            tt.saveXML(temp_ttx_path, skipTables=exclude_tables, tables=include_tables)
            checksum_path = temp_ttx_path
        else:
            if include_tables is not None:
                sys.stderr.write(
                    "[checksum.py] -i and --include are not supported for font binary filepaths. \
                    Use these flags for checksums with the --ttx flag."
                )
                sys.exit(1)
            if exclude_tables is not None:
                sys.stderr.write(
                    "[checksum.py] -e and --exclude are not supported for font binary filepaths. \
                    Use these flags for checksums with the --ttx flag."
                )
                sys.exit(1)
            checksum_path = path

        file_contents = _read_binary(checksum_path)

        # store SHA1 hash data and associated file path basename in the checksum_dict dictionary
        checksum_dict[basename(checksum_path)] = hashlib.sha1(file_contents).hexdigest()

        # remove temp ttx files when present
        if use_ttx and do_not_cleanup is False:
            os.remove(temp_ttx_path)

    # generate the checksum list string for writes
    checksum_out_data = ""
    for key in checksum_dict.keys():
        checksum_out_data += checksum_dict[key] + "  " + key + "\n"

    # write to stdout stream or file based upon user request (default = file write)
    if stdout_write:
        sys.stdout.write(checksum_out_data)
    else:
        checksum_report_filepath = "checksum.txt"
        with open(checksum_report_filepath, "w") as file:
            file.write(checksum_out_data)


def check_checksum(filepaths):
    check_failed = False
    for path in filepaths:
        if not os.path.exists(path):
            sys.stderr.write(
                "[checksum.py] ERROR: " + path + " is not a valid filepath" + os.linesep
            )
            sys.exit(1)

        with open(path, mode="r") as file:
            for line in file.readlines():
                cleaned_line = line.rstrip()
                line_list = cleaned_line.split(" ")
                # eliminate empty strings parsed from > 1 space characters
                line_list = list(filter(None, line_list))
                if len(line_list) == 2:
                    expected_sha1 = line_list[0]
                    test_path = line_list[1]
                else:
                    sys.stderr.write(
                        "[checksum.py] ERROR: failed to parse checksum file values"
                        + os.linesep
                    )
                    sys.exit(1)

                if not os.path.exists(test_path):
                    print(test_path + ": Filepath is not valid, ignored")
                else:
                    file_contents = _read_binary(test_path)
                    observed_sha1 = hashlib.sha1(file_contents).hexdigest()
                    if observed_sha1 == expected_sha1:
                        print(test_path + ": OK")
                    else:
                        print("-" * 80)
                        print(test_path + ": === FAIL ===")
                        print("Expected vs. Observed:")
                        print(expected_sha1)
                        print(observed_sha1)
                        print("-" * 80)
                        check_failed = True

    # exit with status code 1 if any fails detected across all tests in the check
    if check_failed is True:
        sys.exit(1)


def _read_binary(filepath):
    with open(filepath, mode="rb") as file:
        return file.read()


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        prog="checksum.py",
        description="A SHA1 hash checksum list generator and checksum testing script",
    )
    parser.add_argument(
        "-t", "--ttx", help="Calculate from ttx file", action="store_true"
    )
    parser.add_argument(
        "-s", "--stdout", help="Write output to stdout stream", action="store_true"
    )
    parser.add_argument(
        "-n",
        "--noclean",
        help="Do not discard *.ttx files used to calculate SHA1 hashes",
        action="store_true",
    )
    parser.add_argument(
        "-c", "--check", help="Verify checksum values vs. files", action="store_true"
    )
    parser.add_argument(
        "filepaths",
        nargs="+",
        help="One or more file paths.  Use checksum file path for -c/--check.  Use paths\
        to font files for all other commands.",
    )

    parser.add_argument(
        "-i",
        "--include",
        action="append",
        help="Included OpenType tables for ttx data dump",
    )
    parser.add_argument(
        "-e",
        "--exclude",
        action="append",
        help="Excluded OpenType tables for ttx data dump",
    )

    args = parser.parse_args(sys.argv[1:])

    if args.check is True:
        check_checksum(args.filepaths)
    else:
        write_checksum(
            args.filepaths,
            stdout_write=args.stdout,
            use_ttx=args.ttx,
            do_not_cleanup=args.noclean,
            include_tables=args.include,
            exclude_tables=args.exclude,
        )
