#!/usr/bin/env python3

#   Copyright (C) 2025   INAF - Osservatorio Astronomico di Cagliari
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#   
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#   
#   A copy of the GNU General Public License is distributed along with
#   this program in the COPYING file. If not, see: <https://www.gnu.org/licenses/>.

## @package pycompare
#  \brief Script to calculate the execution time of logged operations
#
#  Comparing the numeric output can be rendered hard by the amount of information
#  contained in a typical output file and the necessity to determine whether a
#  difference is actually significant or just caused by numeric noise hitting
#  negligible values. The task of `pycompare.py` is to compare two output files, in
#  the assumption that they were written by the FORTRAN and the C++ versions of
#  the code and to flag all the possible inconsistencies according to various
#  severity levels (namely: NOISE, WARNING, and ERROR).
#
#  After execution, the script returns an exit code, which is set to 0, if no
#  error-level inconsistencies were found, or 1 otherwise. This can be used by
#  subsequent system calls to set up a testing suite checking whether the code
#  is able to reproduce legacy results.
#
#  The script execution requires python3.

import re

from sys import argv

## \cond
time_reg = re.compile(r'[0-9]+\.[0-9]+s')
## \endcond

## \brief Main execution code
#
# `main()` is the function that handles the creation of the script configuration
# and the execution of the calculation. It returns 0 on successful completion.
#
# \returns exit_code: `int` 0 on successful completion.
def main():
    config = parse_arguments()
    exit_code = 0
    if config['help_mode'] or len(argv) == 1:
        config['help_mode'] = True
        print_help()
    else:
        if config['log_name'] is None:
            exit_code = 1
        else:
            operation_time = get_time_from_log(config)
            print("Calculation took %fs."%operation_time)
    return exit_code

## \brief Parse a log file and extract the time.
#
#  \param config: `dict` A dictionary containing the script configuration.
#
#  \returns operation_time: `float` The time of the requested operation in seconds.
def get_time_from_log(config):
    op_time = 0.0
    log_file = open(config['log_name'], 'r')
    file_lines = log_file.readlines()
    log_file.close()
    for li in range(len(file_lines)):
        str_line = file_lines[li]
        if (config['filter'] == "" or str_line.startswith(config['filter'])):
            time_iters = time_reg.finditer(str_line)
            time_groups = []
            for ti in time_iters:
                time_groups.append(ti.group())
            if len(time_groups) == 1:
                op_time += float(time_groups[0][:-1])
    if config['threads'] > 1:
        op_time /= config['threads']
    return op_time

## \brief Parse the command line arguments.
#
#  The script behaviour can be modified through a set of mandatory and optional
#  arguments. The only mandatory argument is the name of the log file to be
#  parsed. Additional optional arguments are an operation filter, which should
#  be the starting sequence of the log strings to pe included in the timing
#  calculation and the number of threads used during code execution.
#
#  \returns config: `dict` A dictionary containing the script configuration.
def parse_arguments():
    config = {
        'log_name': None,
        'help_mode': False,
        'filter': "",
        'threads': 1,
    }
    for arg in argv[1:]:
        split_arg = arg.split("=")
        if (arg.startswith("--logname")):
            config['log_name'] = split_arg[1]
        elif (arg.startswith("--filter")):
            config['filter'] = split_arg[1]
        elif (arg.startswith("--threads")):
            config['threads'] = int(split_arg[1])
        elif (arg.startswith("--help")):
            config['help_mode'] = True
        else:
            raise Exception("Unrecognized argument \'{0:s}\'".format(arg))
    return config

## \brief Print a command-line help summary.
def print_help():
    print("                                            ")
    print("***              PYTIMING                ***")
    print("                                            ")
    print("Get the amount of time spent in calculation.")
    print("                                            ")
    print("Usage: \"./pytiming.py OPTIONS\"            ")
    print("                                            ")
    print("Valid options are:                          ")
    print("--logname=TIMING_LOG      File containing log of timing (mandatory).")
    print("--filter=FILTER           Start of the log lines to be accounted for (optional).")
    print("--help                    Print this help and exit.")
    print("--threads=NUM_THREADS     Number of threads or processes used in calculation (optional).")
    print("                                            ")


# ### PROGRAM EXECUTION ###
## \cond
res = main()
## \endcond
exit(res)