import json
import re

import matplotlib.pyplot as plt
import numpy as np


# Function to convert time units to seconds
def convert_to_seconds(time_str):
    if time_str.endswith('ns'):
        return float(time_str[:-2]) * 1e-9
    elif time_str.endswith('µs'):
        return float(time_str[:-2]) * 1e-6
    elif time_str.endswith('ms'):
        return float(time_str[:-2]) * 1e-3
    elif time_str.endswith('s'):
        return float(time_str[:-1])
    return 0


def report(operation, time):
    return f"{operation}: {time:.6f} seconds"


scenarios = {}
free_logs = []
hypernova = [[None] * 7 for _ in range(7)]


def process_logline(log):
    fields = log.get("fields", {})
    span = log.get("span", {})
    scenario_name = None
    time_seconds = convert_to_seconds(fields["time.busy"])

    # A folding scheme scenario is one of our ancestors
    for s in log.get("spans", []):
        if s.get("name") == "scenario":
            scenario_name = s.get("folding_scheme")

    # Top level span
    if not scenario_name:
        folding_scheme = span.get("folding_scheme")
        if folding_scheme is not None:
            free_logs.append(report(f"{folding_scheme} total time", time_seconds))

            hypernova_params = re.fullmatch(r"HyperNova<(\d),(\d)>", folding_scheme)
            if hypernova_params:
                hypernova[int(hypernova_params.groups()[0])][int(hypernova_params.groups()[1])] = time_seconds
        else:
            free_logs.append(report(span["name"], time_seconds))
        return

    # Within a folding scheme scenario
    if scenario_name not in scenarios:
        scenarios[scenario_name] = {
            "Prepare folding": 0,
            "Transform input": 0,
            "Folding verification": 0,
            "Proving": [],
            "Input prep": [],
        }

    span_name = span.get("name")
    if span_name == "Proving":
        scenarios[scenario_name]["Proving"].append(time_seconds)
    elif span_name == "Input prep":
        scenarios[scenario_name]["Input prep"].append(time_seconds)
    else:
        scenarios[scenario_name][span_name] = time_seconds


def process_logs(file_path):
    with open(file_path, 'r') as f:
        for line in f:
            process_logline(json.loads(line))


def print_results():
    for log in free_logs:
        print(log)
    print()

    for scenario_name, data in scenarios.items():
        print("-" * 80)
        print(f"Scenario: {scenario_name}")
        print(report("  Prepare folding", data["Prepare folding"]))
        print(report("  Transform input", data["Transform input"]))
        print(report("  Folding verification", data["Folding verification"]))

        print(f"  Folding Steps:")
        input_trans = data["Input prep"]
        print("    Input preparation")
        print(report("      Avg", sum(input_trans) / len(input_trans)))
        print(report("      Min", min(input_trans)))
        print(report("      Max", max(input_trans)))
        proving_steps = data["Proving"]
        print("    Proving")
        print(report("      Avg", sum(proving_steps) / len(proving_steps)))
        print(report("      Min", min(proving_steps)))
        print(report("      Max", max(proving_steps)))


def draw_hn_plot():
    global hypernova
    hypernova = np.array(hypernova)[1:,1:]

    data_np = np.array(hypernova, dtype=np.float64)
    data_np = np.where(np.isnan(data_np), 0, data_np)  # Replace None with 0 for better visualization

    cmap = plt.cm.viridis
    cmap.set_under('white')  # Set background color for None

    fig, ax = plt.subplots()
    cax = ax.matshow(data_np, cmap=cmap, vmin=0.01)

    fig.colorbar(cax)

    for i in range(len(hypernova)):
        for j in range(len(hypernova[i])):
            if hypernova[i][j] is not None:
                ax.text(j, i, f'{hypernova[i][j]:.2f}', va='center', ha='center', color='black')

    # Set axis labels and title
    ax.set_xlabel('ν (number of incoming CCCS instances)')
    ax.set_ylabel('μ (number of running LCCCS instances)')

    ax.set_xticks(np.arange(len(hypernova[0])))

    ax.set_yticks(np.arange(len(hypernova)))
    ax.set_xticklabels([f'{i}' for i in range(len(hypernova[0]))])
    ax.set_yticklabels([f'{i}' for i in range(len(hypernova))])

    plt.title("HyperNova multifold times")

    # Show the plot
    plt.show()


process_logs('out.log')
print_results()
draw_hn_plot()
