# -*- coding:utf-8 -*-

#  ************************** Copyrights and license ***************************
#
# This file is part of gcovr 8.6, a parsing and reporting tool for gcov.
# https://gcovr.com/en/8.6
#
# _____________________________________________________________________________
#
# Copyright (c) 2013-2026 the gcovr authors
# Copyright (c) 2013 Sandia Corporation.
# Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
# the U.S. Government retains certain rights in this software.
#
# This software is distributed under the 3-clause BSD License.
# For more information, see the README.rst file.
#
# ****************************************************************************

"""
Handle writing of LCOV files.

The LCOV format is described in https://github.com/linux-test-project/lcov/blob/07a1127c2b4390abf4a516e9763fb28a956a9ce4/man/geninfo.1#L989
and generated by https://github.com/linux-test-project/lcov/blob/07a1127c2b4390abf4a516e9763fb28a956a9ce4/bin/geninfo
"""

# cspell:ignore FNDA BRDA

from ...data_model.container import CoverageContainer
from ...options import Options
from ...utils import force_unix_separator, get_md5_hexdigest, open_text_for_writing


def write_report(
    covdata: CoverageContainer, output_file: str, options: Options
) -> None:
    """produce gcovr csv report"""

    with open_text_for_writing(output_file, "coverage.lcov") as fh:
        sorted_keys = covdata.sort_coverage(
            sort_key=options.sort_key,
            sort_reverse=options.sort_reverse,
            by_metric="branch" if options.sort_branches else "line",
        )
        if options.lcov_comment is not None:
            # #comment_string
            fh.write(f"#{options.lcov_comment}\n")
        # TN:<test name>
        fh.write(f"TN:{options.lcov_test_name}\n")

        def suffix() -> str:
            return f"_{lineno}" if len(linenos) > 1 else ""

        for key in sorted_keys:
            filecov = covdata[key]
            filename = force_unix_separator(filecov.filename)

            # SF:<path to the source file>
            fh.write(f"SF:{filename}\n")

            # This filename is generated in the JSON intermediate format
            if not options.lcov_format_version == "1.x" and not filename.endswith(
                "<stdin>"
            ):
                # VER:<version ID>
                # Generate md5 hash of file contents
                with open(filename, "rb") as file_handle:
                    contents = file_handle.read()
                fh.write(f"VER:{get_md5_hexdigest(contents)}\n")

            functions = 0
            function_hits = 0
            function_line_counts = dict[int, int]()
            for functioncov in filecov.functioncov(sort=True):
                linenos = functioncov.reportable_linenos
                functions += len(linenos)

                for lineno in linenos:
                    # FN:<line number of function start>,[<line number of function end>,]<function name>
                    fh.write(f"FN:{lineno},{functioncov.name}{suffix()}\n")
                for lineno in linenos:
                    count = functioncov.count[lineno] or 0
                    if count:
                        function_hits += 1
                    if lineno in function_line_counts:
                        function_line_counts[lineno] += count
                    else:
                        function_line_counts[lineno] = count
                    # FNDA:<execution count>,<function name>
                    fh.write(f"FNDA:{count},{functioncov.name}{suffix()}\n")
            # FNF:<number of functions found>
            fh.write(f"FNF:{functions}\n")
            # FNH:<number of function hit>
            fh.write(f"FNH:{function_hits}\n")

            linecovs = [
                list(linecov_collection.merge_lines().linecov())[0]
                for linecov_collection in filecov.lines(sort=True)
            ]
            branches = 0
            branch_hits = 0
            for linecov in linecovs:
                if linecov.is_reportable:
                    for branchno, branchcov in enumerate(
                        branchcov
                        for branchcov in linecov.branches(sort=True)
                        if branchcov.is_reportable
                    ):
                        branches += 1
                        if branchcov.count:
                            branch_hits += 1
                        # BRDA:<line_number>,[<exception>]<block>,<branch>,<taken>
                        fh.write(
                            f"BRDA:{linecov.lineno},{'e' if branchcov.throw else ''}{branchcov.source_block_id_or_0},{branchno},{branchcov.count if branchcov.count else '-'}\n"
                        )

            # BRF:<number of branches found>
            fh.write(f"BRF:{branches}\n")
            # BRH:<number of branches hit>
            fh.write(f"BRH:{branch_hits}\n")

            function_linenos = list(sorted(function_line_counts.keys()))
            for linecov in linecovs:
                if function_linenos and linecov.lineno >= function_linenos[0]:
                    lineno = -1
                    while function_linenos and linecov.lineno >= function_linenos[0]:
                        lineno = function_linenos.pop(0)
                        optional_checksum = (
                            f",{linecov.md5}" if linecov.lineno == lineno else ""
                        )
                        # DA:<line number>,<execution count>[,<checksum>]
                        fh.write(
                            f"DA:{lineno},{function_line_counts[lineno]}{optional_checksum}\n"
                        )
                    if lineno == linecov.lineno:
                        continue

                if linecov.is_reportable:
                    # DA:<line number>,<execution count>[,<checksum>]
                    fh.write(f"DA:{linecov.lineno},{linecov.count},{linecov.md5}\n")

            stats = filecov.stats
            # LH:<number of lines with a non\-zero execution count>
            fh.write(f"LH:{stats.line.covered}\n")
            # LF:<number of instrumented lines>
            fh.write(f"LF:{stats.line.total}\n")

            # End of file section
            fh.write("end_of_record\n")
