import os
import random
import sys
from pathlib import Path
import cocotb
from cocotb.triggers import Timer, ReadOnly
from cocotb.runner import get_runner
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, FallingEdge
from cocotb.triggers import with_timeout
from typing import Any, Dict, List
test_file = os.path.basename(__file__).replace(".py","")
import logging
import warnings


import numpy as np
def divider_model(dividend:int, divisor:int):
    x = np.uint32(dividend)
    y = np.uint32(divisor)
    return dict(quotient=x//y, remainder=x%y)

@cocotb.test()
async def divider_basic_test(dut):
    """Basic Divider Tester """
    cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())
    rising_edge = RisingEdge(dut.clk)
    falling_edge = RisingEdge(dut.clk)
    dut._log.info("Initialize and reset model")
    dut.rst.value = 1
    dut.dividend.value = 0
    dut.divisor.value = 0
    dut.data_in_valid.value = 0
    dut._log.info("Resetting")
    await RisingEdge(dut.clk)
    dut.rst.value = 0
    await RisingEdge(dut.clk)
    dut._log.info("Test division operations")
    for _ in range(6):
        await RisingEdge(dut.clk)
    dut._log.info("Starting tests...")
    for i in range(1000): #pick 100 random input numbers
        await FallingEdge(dut.clk)
        dividend = random.randrange(1,2**32-1)
        divisor = random.randrange(1,2**32-1)
        expected = divider_model(dividend, divisor)
        dut.dividend.value = dividend
        dut.divisor.value = divisor
        dut.data_in_valid.value = 1
        await FallingEdge(dut.clk)
        dut.data_in_valid.value = 0
        while True:
            await RisingEdge(dut.clk)
            await ReadOnly()
            if (dut.data_out_valid.value): #got one
                eq = expected['quotient']
                er = expected['remainder']
                aq = dut.quotient.value.integer
                ar = dut.remainder.value.integer
                assert eq==aq and er==ar, f"Error! at Input: {dividend},{divisor}. Expected: {eq}, {er}. Actual {aq}, {ar}"
                dut._log.info(f"Input: {dividend},{divisor}. Expected: {eq}, {er}. Actual {aq}, {ar}")
                break
    # Print result of scoreboard.
    dut._log.info("Done")
    assert True


def test_divider_runner():
    """Simulate the divider using the Python runner."""
    hdl_toplevel_lang = os.getenv("HDL_TOPLEVEL_LANG", "verilog")
    sim = os.getenv("SIM", "icarus")
    proj_path = Path(__file__).resolve().parent.parent
    sys.path.append(str(proj_path /'hdl'/ "model"))
    sources = [proj_path / "hdl" / "divider.sv"]
    build_test_args = ["-Wall"]#,"COCOTB_RESOLVE_X=ZEROS"]
    parameters = {} #!!! nice figured it out.
    hdl_toplevel = "divider"
    sys.path.append(str(proj_path / "tests"))
    runner = get_runner(sim)
    runner.build(
        sources=sources,
        hdl_toplevel=hdl_toplevel,
        always=True,
        build_args=build_test_args,
        parameters=parameters,
        timescale = ('1ns','1ps'),
        waves=True
    )
    run_test_args = []
    runner.test(
        hdl_toplevel=hdl_toplevel,
        test_module=test_file,
        test_args=run_test_args,
        waves=True
    )

if __name__ == "__main__":
    test_divider_runner()

