"""
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: 2022 CERN
"""

import pytest
import random
import smbus
import time
import warnings


QUERY_SUP      = 0x80
QUERY_WR       = 0x40
QUERY_RD       = 0x20
QUERY_FMT_LIN  = 0x00
QUERY_FMT_16B  = 0x04
QUERY_FMT_DIR  = 0x0c
QUERY_FMT_8B   = 0x10
QUERY_FMT_VID  = 0x14
QUERY_FMT_MAN  = 0x18
QUERY_FMT_NAN  = 0x1c

cmd_query = {
"PAGE"               : QUERY_SUP | QUERY_WR | QUERY_RD | QUERY_FMT_8B,
"CAPABILITY"         : QUERY_SUP | QUERY_RD,
"QUERY"              : QUERY_SUP | QUERY_WR,
"VOUT_MODE"          : QUERY_SUP | QUERY_RD,
"FAN_CONFIG_1_2"     : QUERY_SUP | QUERY_WR | QUERY_RD | QUERY_FMT_NAN,
"FAN_COMMAND_1"      : QUERY_SUP | QUERY_WR | QUERY_RD | QUERY_FMT_LIN,
"FAN_COMMAND_2"      : QUERY_SUP | QUERY_WR | QUERY_RD | QUERY_FMT_LIN,
"FAN_CONFIG_3_4"     : QUERY_SUP | QUERY_WR | QUERY_RD | QUERY_FMT_NAN,
"FAN_COMMAND_3"      : QUERY_SUP | QUERY_WR | QUERY_RD | QUERY_FMT_LIN,
"STATUS_BYTE"        : QUERY_SUP | QUERY_RD,
"STATUS_CML"         : QUERY_SUP | QUERY_WR | QUERY_RD,
"READ_VOUT"          : QUERY_SUP | QUERY_RD | QUERY_FMT_LIN,
"READ_IOUT"          : QUERY_SUP | QUERY_RD | QUERY_FMT_LIN,
"READ_TEMPERATURE_1" : QUERY_SUP | QUERY_RD | QUERY_FMT_LIN,
"READ_TEMPERATURE_2" : QUERY_SUP | QUERY_RD | QUERY_FMT_LIN,
"READ_TEMPERATURE_3" : QUERY_SUP | QUERY_RD | QUERY_FMT_LIN,
"READ_FAN_SPEED_1"   : QUERY_SUP | QUERY_RD | QUERY_FMT_LIN,
"READ_FAN_SPEED_2"   : QUERY_SUP | QUERY_RD | QUERY_FMT_LIN,
"READ_FAN_SPEED_3"   : QUERY_SUP | QUERY_RD | QUERY_FMT_LIN,
"READ_POUT"          : QUERY_SUP | QUERY_RD | QUERY_FMT_LIN,
"MFR_ID"             : QUERY_SUP | QUERY_RD | QUERY_FMT_NAN,
"MFR_MODEL"          : QUERY_SUP | QUERY_RD | QUERY_FMT_NAN,
"MFR_REVISION"       : QUERY_SUP | QUERY_RD | QUERY_FMT_NAN,
"MFR_LOCATION"       : QUERY_SUP | QUERY_RD | QUERY_FMT_NAN,
"MFR_DATE"           : QUERY_SUP | QUERY_RD | QUERY_FMT_NAN,
"MFR_SERIAL"         : QUERY_SUP | QUERY_RD | QUERY_FMT_NAN,
"IC_DEVICE_REV"      : QUERY_SUP | QUERY_RD | QUERY_FMT_NAN,
# manufacturer specific commands
"WRITTEN_FW_SIZE"    : QUERY_SUP | QUERY_WR | QUERY_RD,
"WRITTEN_FW_BLOCK"   : QUERY_SUP | QUERY_WR | QUERY_RD,
"WRITTEN_FW_CHKSUM"  : QUERY_SUP | QUERY_WR | QUERY_RD,
"LOCAL_FW_CHKSUM"    : QUERY_SUP | QUERY_RD,
"BOOT_NEW_FW"        : QUERY_SUP | QUERY_WR | QUERY_RD,
"UC_RESET"           : QUERY_SUP | QUERY_WR,
"UPTIME_SECS"        : QUERY_SUP | QUERY_RD,
"TMR_ERROR_CNT"      : QUERY_SUP | QUERY_WR | QUERY_RD,
"USE_PEC"            : QUERY_SUP | QUERY_WR | QUERY_RD,
"TEMP_CURVE_POINTS"  : QUERY_SUP | QUERY_WR | QUERY_RD,
"TEMP_MATRIX_ROW"    : QUERY_SUP | QUERY_WR | QUERY_RD,
"TC_ONOFF"           : QUERY_SUP | QUERY_WR | QUERY_RD,
}


MFR_ID = "CERN (BE/CEM)"
MFR_MODEL = "DI/OT MoniMod (bl)"

@pytest.fixture(scope="module", autouse=True)
def check_boot_mode(monimod_bus):
    restore_boot_mode = 0

    if monimod_bus.get_fw_mode() != monimod_bus.FW_BOOTLOADER:
        restore_boot_mode = 1
        monimod_bus.switch_fw_mode()
        time.sleep(1)
        if monimod_bus.get_fw_mode() != monimod_bus.FW_BOOTLOADER:
            pytest.skip("Monimod not in bootloader mode! Unable to switch to bootloader!")
    yield
    if restore_boot_mode == 1:
        monimod_bus.switch_fw_mode()
        time.sleep(1)
        if monimod_bus.get_fw_mode() != monimod_bus.FW_MAIN:
            warnings.warn(UserWarning("Monimod not in main mode! Unable to switch to main mode."))


class TestBootloaderRegAccess(object):

    @pytest.mark.check_uptime
    @pytest.mark.parametrize("cmds", ["BOOT_NEW_FW",
                                      "UC_RESET",
        ]
    )
    def test_byte_read(self, monimod_bus, cmds):
        monimod_bus.pmbus_read_byte_cmd(monimod_bus.cmd_ids[cmds])

    @pytest.mark.parametrize("cmds", ["WRITTEN_FW_SIZE"  ,
                                      "WRITTEN_FW_CHKSUM",
                                      "LOCAL_FW_CHKSUM"  ,
                                      ]
    )
    def test_word_read(self, monimod_bus, cmds):
        monimod_bus.pmbus_read_word_cmd(monimod_bus.cmd_ids[cmds])

    @pytest.mark.parametrize("cmds", ["MFR_ID",
                                      "MFR_MODEL",
                                      "MFR_REVISION",
                                      "MFR_LOCATION",
                                      "MFR_DATE",
                                      "MFR_SERIAL",
                                      "IC_DEVICE_REV"
                                      ]
    )
    def test_block_read(self, monimod_bus, cmds):
        monimod_bus.pmbus_read_block_cmd(monimod_bus.cmd_ids[cmds])

    @pytest.mark.parametrize("cmds", ["MFR_ID"            ,
                                      "MFR_MODEL"         ,
                                      "MFR_REVISION"      ,
                                      "MFR_LOCATION"      ,
                                      "MFR_DATE"          ,
                                      "MFR_SERIAL"        ,
                                      "IC_DEVICE_REV"     ,
        ]
    )
    def test_write_fail(self, monimod_bus, cmds):
        try:
            monimod_bus.pmbus_write_byte_cmd(monimod_bus.cmd_ids[cmds], 0xFa)
        except OSError:
            return
        pytest.fail("{:s} register should be read only! Is read-write.".format(cmds))

    @pytest.mark.parametrize("cmds", ["QUERY"             ,
                                      "MFR_ID"            ,
                                      "MFR_MODEL"         ,
                                      "MFR_REVISION"      ,
                                      "MFR_LOCATION"      ,
                                      "MFR_DATE"          ,
                                      "MFR_SERIAL"        ,
                                      "IC_DEVICE_REV"     ,
                                      "WRITTEN_FW_SIZE"   ,
                                      "WRITTEN_FW_CHKSUM" ,
                                      "LOCAL_FW_CHKSUM"   ,
                                      "BOOT_NEW_FW"       ,
                                      "UC_RESET"          ,
                                      ]
    )
    def test_query(self, monimod_bus, cmds):
        ret = monimod_bus.pmbus_block_process_call(monimod_bus.cmd_ids["QUERY"], [monimod_bus.cmd_ids[cmds]])
        assert cmd_query[cmds] == ret[0], ("Read 0x{:X}, expected 0x{:X}".format(ret[0], cmd_query[cmds]))

class TestBootloaderRegValues(object):
    def test_mfr_id(self, monimod_bus):
        fw_id = monimod_bus.pmbus_read_block_cmd(monimod_bus.cmd_ids["MFR_ID"])

        assert monimod_bus.var_to_str(fw_id) == MFR_ID

    def test_mfr_model(self, monimod_bus):
        fw_model = monimod_bus.pmbus_read_block_cmd(monimod_bus.cmd_ids["MFR_MODEL"])

        assert monimod_bus.var_to_str(fw_model) == MFR_MODEL


@pytest.mark.check_uptime
class TestBootloaderOther(object):
    def test_switch_to_bootloader(self, monimod_bus):
        assert monimod_bus.get_fw_mode() == monimod_bus.FW_BOOTLOADER, "Monimod not in bootloader mode!"
        monimod_bus.switch_fw_mode()
        time.sleep(2)
        assert monimod_bus.get_fw_mode() == monimod_bus.FW_MAIN, "Failed to switch to main mode!"
        monimod_bus.switch_fw_mode()
        time.sleep(2)
        assert monimod_bus.get_fw_mode() == monimod_bus.FW_BOOTLOADER, "Failed to switch to bootloader mode!"
