import tempfile
import time
import pandas as pd
import pickle
import subprocess
import sys

from imc_utils.pps_e36311a import PPS_E36311A
from imc_utils.smu_b2902a import SMU_B2902A
from imc_utils.build_config.cortex_m33 import BuildConfigM33
from imc_utils.build_config.test_env import TestEnv
from imc_utils.serial_watch import SerialWatcher

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 measure_original(benchmark, pps, smu):
    config = get_default_build_config(benchmark)

    print("\n===== A. measure original program =====\n")
    target_current = 0.1
    currs = measure(config, pps, smu, target_current, repeat=2)
    with open(f"output/test_1_{benchmark}_original.pickle", "wb") as f:
        pickle.dump(currs, f)


def measure_ckpt(benchmark, pps, smu: SMU_B2902A):
    config = get_default_build_config(benchmark)
    config.insert_compiler_checkpoints = False
    config.use_checkpoint_pass_counter = False
    config.split_loop = False
    config.enable_adaptive_loop_pass_count = False
    config.use_checkpoint_voltage_check = False
    config.max_loop_ids = 15
    config.enable_dynamic_checkpoint = True

    # print("\n===== B. measure checkpoint-instrumented program =====\n")
    target_current = 0.015
    wait_s = 3
    repeat = 200
    records = measure(config, pps, smu, target_current, repeat=repeat, wait_before_start_s=wait_s)
    records = pd.DataFrame(records)
    columns = ["bench_name", "time_taken", "outputs", "is_correct"]
    print(records[columns])
    with open(f"output/test_2_{benchmark}_ckpt.pickle", "wb") as f:
        pickle.dump(records, f)


def measure(config, pps: PPS_E36311A, smu: SMU_B2902A, target_current, repeat=1, wait_before_start_s=0):
    env = program_with_config(config, pps, smu)

    benchmark = config.bench_name
    pps.set_voltage(3.3, 1)
    pps.set_current(target_current, 1)
    watcher = SerialWatcher(benchmark, repeat)

    if wait_before_start_s > 0:
        print(f" > wait for {wait_before_start_s} secs")
        time.sleep(wait_before_start_s)
        env.resume_board(terminate=True, verbose=False)

    print(" > start measuring\n")
    if wait_before_start_s == 0:
        env.resume_board(terminate=True, verbose=False)
    records = watcher.run()
    print("\n > measure finished")
    return records


def main(args):
    pps = PPS_E36311A()
    smu = SMU_B2902A(voltage=0, use_external_power=True)
    smu.setup_sense()
    smu.set_current_limit(0.1)

    benchmarks = ["vFFT", "vCrc", "vAes", "vSha"]
    benchmark = args[1]
    assert benchmark in benchmarks
    benchmarks = [benchmark]

    total = len(benchmarks)
    for i, benchmark in enumerate(benchmarks):
        print(f"\n+++ ({i+1}/{total}) {benchmark} +++")
        # measure_original(benchmark, pps, smu)
        measure_ckpt(benchmark, pps, smu)


def program_with_config(config, pps, smu):
    env = TestEnv(WORKSPACE_ROOT, NVM_RESET_BIN, OPENOCD_SCRIPT)

    smu.power_on()
    pps.set_voltage(3.3, 1)
    pps.set_current(0.1, 1)
    pps.output_on(1)
    time.sleep(1)

    print(" > build and program binary")

    with tempfile.TemporaryDirectory() as build_dir:
        binary = env.build_binary(config, build_dir, redirect_file=subprocess.DEVNULL)
        env.clear_nvm_and_load_binary(binary, resume=False, verbose=False)
    
    print(" > program finished")
    
    return env


def get_bench_repeat_count(benchmark):
    d = {
        "vFFT": 10,
        "vConv2d": 15000,
        "vCrc": 1000,
        "vBasicMath": 1,
        "vSha": 40,
        "vStringSearch": 3000,
        "vAes": 1300,
        "vMatMul": 20000,
    }
    return d[benchmark]
    

def get_default_build_config(benchmark):
    config = BuildConfigM33()
    config.bench_name = benchmark
    config.insert_compiler_checkpoints = False
    config.enable_extension = True
    config.use_checkpoint_pass_counter = False
    config.use_checkpoint_voltage_check = False
    config.bench_infinite_loop = True
    config.print_recovery_message = True
    config.split_loop = False
    config.enable_static_loop_pass_count = False
    config.enable_adaptive_loop_pass_count = False
    config.print_stats = False
    config.custom_unroll = False
    config.enable_dynamic_checkpoint = False
    config.bench_repeat_count = get_bench_repeat_count(benchmark)
    return config


if __name__ == "__main__":
    main(sys.argv)