#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
"""Parse device output to extract LLVM profile data.

The profile data obtained from the device is raw. Thus, it must be indexed
before it can be used to generate coverage reports.

Typical usage:
    stty -F /dev/ttyUSB0 icanon
    cat /dev/ttyUSB0 | ./device_profile_data.py foo.profraw
    llvm-profdata merge -sparse foo.profraw -o foo.profdata
    llvm-cov show OBJECT_FILE -instr-profile=foo.profdata
"""
import argparse
import zlib
import re
import sys


def extract_profile_data(device_output):
    """Parse device output to extract LLVM profile data.

    This function returns the LLVM profile data as a byte array after
    verifying its length and checksum.

    Args:
        device_output: Device output.
    Returns:
        LLVM profile data.
    Raises:
        ValueError: If LLVM profile data cannot be detected in the device
            output or its length or checksum is incorrect.
    """
    device_output = re.sub(r'^.*] (.*)$',
                           r'\1',
                           device_output,
                           flags=re.MULTILINE)
    device_output = device_output.translate(
        device_output.maketrans('', '', '\r\n'))
    match = re.search(
        r"""
            LLVM\ profile\ data\ \(length:\ (?P<len>\d+)\ bytes,\ CRC32:\ (?P<crc>0x[0-9a-f]*)\):
            (?P<data> 0x [0-9a-f]+)
            \x04
        """, device_output, re.VERBOSE)
    if not match:
        raise ValueError(
            'Could not detect LLVM profile data in device output.')
    exp_length = int(match.group('len'))
    exp_checksum = int(match.group('crc'), 0)
    byte_array = int(match.group('data'), 0).to_bytes(len(match.group('data')) // 2 - 1,
                                                      byteorder='little',
                                                      signed=False)
    # Check length
    act_length = len(byte_array)
    if act_length != exp_length:
        raise ValueError(('Length check failed! ',
                          f'Expected: {exp_length}, actual: {act_length}.'))
    # Check checksum
    act_checksum = zlib.crc32(byte_array)
    if act_checksum != exp_checksum:
        raise ValueError(
            ('Checksum check failed! ',
             f'Expected: {exp_checksum}, actual: {act_checksum}.'))
    return byte_array


def main():
    """Parses command line arguments and extracts the profile data from device
    output."""
    argparser = argparse.ArgumentParser(
        description='Extract LLVM profile data from device output.')
    argparser.add_argument(dest='output_file',
                           type=argparse.FileType('wb'),
                           default=sys.stdout,
                           help='output file for writing LLVM profile data')
    argparser.add_argument('--input_file',
                           type=argparse.FileType('rb'),
                           default=sys.stdin.buffer,
                           help='device output')
    args = argparser.parse_args()

    args.output_file.write(
        extract_profile_data(args.input_file.read().decode('ascii', 'ignore')))


if __name__ == '__main__':
    main()
