import tempfile
import time
import pandas as pd
import pickle

from imc_utils.pps_e36311a import PPS_E36311A
from imc_utils.build_config.cortex_m33 import BuildConfigM33
from imc_utils.build_config.test_env import TestEnv
import imc_utils.serial_device

WORKSPACE_ROOT = "/home/ybkim/workspace/imc/imc_freertos_app_m33"
NVM_RESET_BIN = f"{WORKSPACE_ROOT}/imc/utils/nvm_reset.elf"
OPENOCD_SCRIPT = f"{WORKSPACE_ROOT}/imc_freertos_app_m33.cfg"

def main():
    pps = PPS_E36311A()
    config = get_default_build_config()

    benchmarks = [
        "vBasicMath", "vCrc", "vFFT", "vSha"
        # "vSha"
    ]

    for benchmark in benchmarks:
        config.bench_name = benchmark
        config.insert_compiler_checkpoints = True
        config.use_checkpoint_pass_counter = False
        config.use_checkpoint_voltage_check = True
        config.checkpoint_pass_count = 100
        config.bench_infinite_loop = True

        pps.set_voltage(3.3, 1)
        pps.set_current(0.1, 1)
        pps.output_on(1)

        env = TestEnv(WORKSPACE_ROOT, NVM_RESET_BIN, OPENOCD_SCRIPT)

        with tempfile.TemporaryDirectory() as build_dir:
            binary = env.build_binary(config, build_dir)
            env.clear_nvm_and_load_binary(binary, resume=True)
        
        pps.set_current(0.015, 1)
        time.sleep(1)

        total_iterations = 5
        records = measure_execution_time(benchmark, total_iterations)
        df = pd.DataFrame(records)
        save_records(benchmark, df)
        print(df)


def measure_execution_time(bench_nanme, total_iterations):
    ser = imc_utils.serial_device.get_serial()

    num_finished = 0
    start_detected = False
    time_takens = []
    time_total = 0
    outputs = []
    records = []
    num_recovery = 0

    ser.reset_input_buffer()

    while num_finished < total_iterations:
        if ser.readable():
            res = ser.readline()
            try:
                line = res.decode()[: len(res) - 1]
            except:
                print("readline() exception")
                continue

        if line.startswith("hardfault"):
            print("\nHARD FAULT")

        if not start_detected:
            if line.startswith("Start benchmark"):
                print("\nbenchmark start detected")
                t_start = time.time()
                start_detected = True
        else:
            if line.startswith("End benchmark"):
                t_end = time.time()
                t_diff = t_end - t_start
                time_takens.append(t_diff)
                time_total += t_diff
                num_finished += 1
                print(
                    f"\nbenchmark end detected, time: {t_diff:.2f} secs, finished: {num_finished}, average: {time_total/num_finished:.2f} secs"
                )
                start_detected = False
                record = {
                    "bench_name": bench_nanme,
                    "start": t_start,
                    "end": t_end,
                    "time_taken": t_diff,
                    "recovery": num_recovery,
                    "outputs": outputs,
                    "is_correct": check_output_is_correct(bench_nanme, outputs)
                }
                records.append(record)
                outputs = []
                num_recovery = 0

            elif line.startswith("(OUT)"):
                print(line)
                outputs.append(line[6:].strip())
            
            elif line.startswith("Start recovery"):
                num_recovery += 1
                print(f"recovery: #{num_recovery}", end="\r")

    return records


def get_default_build_config():
    config = BuildConfigM33()
    config.bench_name = "vBasicMath"
    config.insert_compiler_checkpoints = True
    config.enable_extension = True
    config.use_checkpoint_pass_counter = False
    config.use_checkpoint_voltage_check = True
    config.bench_infinite_loop = True
    config.checkpoint_pass_count = 100
    config.print_recovery_message = True
    return config


correct_outputs = {
    "vBasicMath": "Sum: -9313",
    "vCrc": "210692533",
    "vFFT": "2807, 915",
    "vSha": "4926a88d 0ca714f4 a9ebc1eb def37b8e 3911ee0f",
}

def check_output_is_correct(bench_name, outputs):
    if len(outputs) == 0 or bench_name not in correct_outputs:
        return False
    is_single_output = len(outputs) == 1
    is_output_correct = correct_outputs[bench_name] == outputs[0]
    return is_single_output and is_output_correct


def save_records(bench_name, df):
    with open(f"output/{bench_name}.pickle", "wb") as f:
        pickle.dump(df, f)


if __name__ == "__main__":
    main()
