"""
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 | QUERY_FMT_NAN,
"TEMP_MATRIX_ROW"    : QUERY_SUP | QUERY_WR | QUERY_RD | QUERY_FMT_NAN,
"TC_ONOFF"           : QUERY_SUP | QUERY_WR | QUERY_RD,
}


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

@pytest.fixture(scope="module", autouse=True)
def check_boot_mode(monimod_bus):
    if monimod_bus.get_fw_mode() != monimod_bus.FW_MAIN:
        monimod_bus.switch_fw_mode()
        time.sleep(1)
        if monimod_bus.get_fw_mode() != monimod_bus.FW_MAIN:
            pytest.skip("Monimod not in main mode! Unable to switch to main mode!")
        warnings.warn(UserWarning("Monimod not in main mode! Switched to main mode."))

class TestFwMainRegAccess(object):

    @pytest.mark.check_uptime
    @pytest.mark.parametrize("cmds", ["PAGE",
                                      "CAPABILITY",
                                      "VOUT_MODE",
                                      "FAN_CONFIG_1_2",
                                      "FAN_CONFIG_3_4",
                                      "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", ["VOUT_MODE"         ,
                                      "FAN_CONFIG_1_2"    ,
                                      "FAN_COMMAND_1"     ,
                                      "FAN_COMMAND_2"     ,
                                      "FAN_CONFIG_3_4"    ,
                                      "FAN_COMMAND_3"     ,
                                      "STATUS_BYTE"       ,
                                      "STATUS_CML"        ,
                                      "READ_VOUT"         ,
                                      "READ_IOUT"         ,
                                      "READ_TEMPERATURE_1",
                                      "READ_TEMPERATURE_2",
                                      "READ_TEMPERATURE_3",
                                      "READ_FAN_SPEED_1"  ,
                                      "READ_FAN_SPEED_2"  ,
                                      "READ_FAN_SPEED_3"  ,
                                      "READ_POUT"         ,
                                      "MFR_ID"            ,
                                      "MFR_MODEL"         ,
                                      "MFR_REVISION"      ,
                                      "MFR_LOCATION"      ,
                                      "MFR_DATE"          ,
                                      "MFR_SERIAL"        ,
                                      "IC_DEVICE_REV"
                                      ]
    )
    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", ["CAPABILITY"        ,
                                      "VOUT_MODE"         ,
                                      "STATUS_BYTE"       ,
                                      "READ_VOUT"         ,
                                      "READ_IOUT"         ,
                                      "READ_TEMPERATURE_1",
                                      "READ_TEMPERATURE_2",
                                      "READ_TEMPERATURE_3",
                                      "READ_FAN_SPEED_1"  ,
                                      "READ_FAN_SPEED_2"  ,
                                      "READ_FAN_SPEED_3"  ,
                                      "READ_POUT"         ,
                                      "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", ["PAGE"              ,
                                      "CAPABILITY"        ,
                                      "QUERY"             ,
                                      "VOUT_MODE"         ,
                                      "FAN_CONFIG_1_2"    ,
                                      "FAN_COMMAND_1"     ,
                                      "FAN_COMMAND_2"     ,
                                      "FAN_CONFIG_3_4"    ,
                                      "FAN_COMMAND_3"     ,
                                      "STATUS_BYTE"       ,
                                      "STATUS_CML"        ,
                                      "READ_VOUT"         ,
                                      "READ_IOUT"         ,
                                      "READ_TEMPERATURE_1",
                                      "READ_TEMPERATURE_2",
                                      "READ_TEMPERATURE_3",
                                      "READ_FAN_SPEED_1"  ,
                                      "READ_FAN_SPEED_2"  ,
                                      "READ_FAN_SPEED_3"  ,
                                      "READ_POUT"         ,
                                      "MFR_ID"            ,
                                      "MFR_MODEL"         ,
                                      "MFR_REVISION"      ,
                                      "MFR_LOCATION"      ,
                                      "MFR_DATE"          ,
                                      "MFR_SERIAL"        ,
                                      "IC_DEVICE_REV"     ,
                                      "BOOT_NEW_FW"       ,
                                      "UC_RESET"          ,
                                      "UPTIME_SECS"       ,
                                      "TMR_ERROR_CNT"     ,
                                      "USE_PEC"           ,
                                      "TEMP_CURVE_POINTS" ,
                                      "TEMP_MATRIX_ROW"   ,
                                      "TC_ONOFF"          ,
                                      ]
    )
    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]))


    @pytest.mark.parametrize("cmds", ["FAN_COMMAND_1"     ,
                                      "FAN_COMMAND_2"     ,
                                      "FAN_COMMAND_3"     ,
                                      ]
    )
    def test_write_fan_command_n(self, monimod_bus, cmds):
        '''Read value of FAN_COMMAND_n register
           write old + 1
           read again
           compare if write was successful
           write old value
           read again
           compare if the original values has been stored successfully'''
        old_val = monimod_bus.pmbus_read_word_cmd(monimod_bus.cmd_ids[cmds])
        monimod_bus.pmbus_write_word_cmd(monimod_bus.cmd_ids[cmds], old_val + 1)
        new_val = monimod_bus.pmbus_read_word_cmd(monimod_bus.cmd_ids[cmds])
        assert old_val + 1 == new_val, ("Unable to write value 0x{:X}, written value {:X}, original value {:X}\n".format(old_val + 1, new_val, old_val))
        monimod_bus.pmbus_write_word_cmd(monimod_bus.cmd_ids[cmds], old_val)
        new_val = monimod_bus.pmbus_read_word_cmd(monimod_bus.cmd_ids[cmds])
        assert old_val == new_val, ("Unable to restore original value 0x{:X}, read value {:X}\n".format(old_val, new_val))

class TestFwMainRegValues(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

    def test_uptime_increase(self, monimod_bus):
        uptime1 = monimod_bus.get_uptime()
        time.sleep(1)
        uptime2 = monimod_bus.get_uptime()
        assert uptime1 < uptime2

    def test_TMR_zero(self, monimod_bus):
        TMR_val = monimod_bus.get_tmr()
        assert TMR_val == 0

    @pytest.mark.parametrize("cmds, test_data", [# cmd,                size
                                                 ("TEMP_CURVE_POINTS", [0x02, 0x80, 0xda, 0x20, 0xeb, 0x20, 0xdb, 0x20, 0xf3, 0xc0, 0xdb, 0xe8, 0x03]),
                                                 ("TEMP_CURVE_POINTS", [0x02, 0x11, 0x22, 0x33, 0x44, 0xaa, 0x5c, 0xee, 0x80, 0x99, 0xaa, 0xbb, 0xcc]),
                                                 ("TEMP_MATRIX_ROW",   [0x00, 0x02, 0xfa, 0x04, 0x05, 0x0c, 0x92]),
                                                 ("TEMP_MATRIX_ROW",   [0x00, 0x05, 0x33, 0x44, 0x05, 0x12, 0x92])
                                                ]
    )
    def test_write_multibyte(self, monimod_bus, cmds, test_data):
        data_old = monimod_bus.pmbus_read_block_cmd(monimod_bus.cmd_ids[cmds])
        # Use the same fan ID
        data_new = [data_old[0]] + test_data[1:]

        monimod_bus.pmbus_write_block_cmd(monimod_bus.cmd_ids[cmds], data_new)
        data_new_read = monimod_bus.pmbus_read_block_cmd(monimod_bus.cmd_ids[cmds])

        # restore and verify the old value
        monimod_bus.pmbus_write_block_cmd(monimod_bus.cmd_ids[cmds], data_old)
        data_old_read = monimod_bus.pmbus_read_block_cmd(monimod_bus.cmd_ids[cmds])
        assert data_new_read == data_new
        assert data_new_read != data_old
        assert data_old_read == data_old

    @pytest.mark.parametrize("cmds", ["USE_PEC", "TC_ONOFF",])
    def test_on_off(self, monimod_bus, cmds):
        '''Check functionality to off then on by the write to the register.
           A the end restore the old value'''
        off_val = 0
        on_val = 1
        old_val = monimod_bus.pmbus_read_byte_cmd(monimod_bus.cmd_ids[cmds])

        # set to off
        monimod_bus.pmbus_write_byte_cmd(monimod_bus.cmd_ids[cmds], off_val)
        off_val_read = monimod_bus.pmbus_read_byte_cmd(monimod_bus.cmd_ids[cmds])
        assert off_val == off_val_read

        # set to on
        monimod_bus.pmbus_write_byte_cmd(monimod_bus.cmd_ids[cmds], on_val)
        on_val_read = monimod_bus.pmbus_read_byte_cmd(monimod_bus.cmd_ids[cmds])
        assert on_val == on_val_read

        # restore and verify the old value
        monimod_bus.pmbus_write_byte_cmd(monimod_bus.cmd_ids[cmds], old_val)
        old_val_read = monimod_bus.pmbus_read_byte_cmd(monimod_bus.cmd_ids[cmds])
        assert old_val == old_val_read

@pytest.mark.check_uptime
class TestFwMainOther(object):
    def test_switch_to_bootloader(self, monimod_bus):
        if monimod_bus.get_tmr():
            warnings.warn(UserWarning("TMR not zero"))
        assert monimod_bus.get_fw_mode() == monimod_bus.FW_MAIN, "Monimod not in 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!"
        monimod_bus.switch_fw_mode()
        time.sleep(2)
        assert monimod_bus.get_fw_mode() == monimod_bus.FW_MAIN, "Failed to switch to main mode!"
