## Copyright (C) 2024  Solaar Contributors https://pwr-solaar.github.io/Solaar/
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""The tests work by creating a faked device (from the hidpp module) that uses provided data as responses to HID++ commands.
The device uses some methods from the real device to set up data structures that are needed for some tests.
"""

from dataclasses import dataclass
from typing import Any

import pytest

from logitech_receiver import common
from logitech_receiver import hidpp20
from logitech_receiver import hidpp20_constants
from logitech_receiver import settings_templates
from logitech_receiver import special_keys

from . import fake_hidpp

# TODO action part of DpiSlidingXY, MouseGesturesXY


class Setup:
    def __init__(self, test, *params):
        self.test = test
        self.responses = [r for r in params if isinstance(r, fake_hidpp.Response)]
        self.choices = None if isinstance(params[0], fake_hidpp.Response) else params[0]


@dataclass
class RegisterTest:
    sclass: Any
    initial_value: Any = False
    write_value: Any = True
    write_params: str = "01"


register_tests = [
    Setup(
        RegisterTest(settings_templates.RegisterHandDetection, False, True, [b"\x00\x00\x00"]),
        fake_hidpp.Response("000030", 0x8101),  # keyboard_hand_detection
        fake_hidpp.Response("000000", 0x8001, "000000"),
    ),
    Setup(
        RegisterTest(settings_templates.RegisterHandDetection, True, False, [b"\x00\x00\x30"]),
        fake_hidpp.Response("000000", 0x8101),  # keyboard_hand_detection
        fake_hidpp.Response("000030", 0x8001, "000030"),
    ),
    Setup(
        RegisterTest(settings_templates.RegisterSmoothScroll, False, True, [b"\x40"]),
        fake_hidpp.Response("00", 0x8101),  # mouse_button_flags
        fake_hidpp.Response("40", 0x8001, "40"),
    ),
    Setup(
        RegisterTest(settings_templates.RegisterSideScroll, True, False, [b"\x00"]),
        fake_hidpp.Response("02", 0x8101),  # mouse_button_flags
        fake_hidpp.Response("00", 0x8001, "00"),
    ),
    Setup(
        RegisterTest(settings_templates.RegisterFnSwap, False, True, [b"\x00\x01"]),
        fake_hidpp.Response("0000", 0x8109),  # keyboard_fn_swap
        fake_hidpp.Response("0001", 0x8009, "0001"),
    ),
    Setup(
        RegisterTest(
            settings_templates._PerformanceMXDpi, common.NamedInt(0x88, "800"), common.NamedInt(0x89, "900"), [b"\x89"]
        ),
        fake_hidpp.Response("88", 0x8163),  # mouse_dpi
        fake_hidpp.Response("89", 0x8063, "89"),
    ),
]


@pytest.mark.parametrize("test", register_tests)
def test_register_template(test, mocker):
    device = fake_hidpp.Device(protocol=1.0, responses=test.responses)
    spy_request = mocker.spy(device, "request")

    setting = test.test.sclass.build(device)
    value = setting.read(cached=False)
    cached_value = setting.read(cached=True)
    write_value = setting.write(test.test.write_value)

    assert setting is not None
    assert value == test.test.initial_value
    assert cached_value == test.test.initial_value
    assert write_value == test.test.write_value
    spy_request.assert_called_with(test.test.sclass.register + 0x8000, *test.test.write_params)


@dataclass
class FeatureTest:
    sclass: Any
    initial_value: Any = False
    write_value: Any = True
    matched_calls: int = 1
    offset: int = 0x04
    version: int = 0x00
    rewrite: bool = False
    readable: bool = True


simple_tests = [
    Setup(
        FeatureTest(settings_templates.K375sFnSwap, False, True, offset=0x06),
        fake_hidpp.Response("FF0001", 0x0600, "FF"),
        fake_hidpp.Response("FF0101", 0x0610, "FF01"),
    ),
    Setup(
        FeatureTest(settings_templates.K375sFnSwap, False, True, offset=0x06),
        fake_hidpp.Response("050001", 0x0000, "1815"),  # HOSTS_INFO
        fake_hidpp.Response("FF0001", 0x0600, "FF"),
        fake_hidpp.Response("FF0101", 0x0610, "FF01"),
    ),
    Setup(
        FeatureTest(settings_templates.K375sFnSwap, False, True, offset=0x06),
        fake_hidpp.Response("050001", 0x0000, "1815"),  # HOSTS_INFO
        fake_hidpp.Response("07050301", 0x0500),  # current host is 0x01, i.e., host 2
        fake_hidpp.Response("010001", 0x0600, "01"),
        fake_hidpp.Response("010101", 0x0610, "0101"),
    ),
    Setup(
        FeatureTest(settings_templates.FnSwap, True, False),
        fake_hidpp.Response("01", 0x0400),
        fake_hidpp.Response("00", 0x0410, "00"),
    ),
    Setup(
        FeatureTest(settings_templates.NewFnSwap, True, False),
        fake_hidpp.Response("01", 0x0400),
        fake_hidpp.Response("00", 0x0410, "00"),
    ),
    #    Setup(  # Backlight has caused problems
    #        FeatureTest(settings_templates.Backlight, 0, 5, offset=0x06),
    #        fake_hidpp.Response("00", 0x0600),
    #        fake_hidpp.Response("05", 0x0610, "05"),
    #    ),
    Setup(
        FeatureTest(settings_templates.Backlight2DurationHandsOut, 80, 160, version=0x03),
        fake_hidpp.Response("011830000000100040006000", 0x0400),
        fake_hidpp.Response("0118FF00200040006000", 0x0410, "0118FF00200040006000"),
    ),
    Setup(
        FeatureTest(settings_templates.Backlight2DurationHandsIn, 320, 160, version=0x03),
        fake_hidpp.Response("011830000000200040006000", 0x0400),
        fake_hidpp.Response("0118FF00200020006000", 0x0410, "0118FF00200020006000"),
    ),
    Setup(
        FeatureTest(settings_templates.Backlight2DurationPowered, 480, 80, version=0x03),
        fake_hidpp.Response("011830000000200040006000", 0x0400),
        fake_hidpp.Response("0118FF00200040001000", 0x0410, "0118FF00200040001000"),
    ),
    Setup(
        FeatureTest(settings_templates.Backlight3, 0x50, 0x70),
        fake_hidpp.Response("50", 0x0410),
        fake_hidpp.Response("70", 0x0420, "007009"),
    ),
    Setup(
        FeatureTest(settings_templates.HiResScroll, True, False),
        fake_hidpp.Response("01", 0x0400),
        fake_hidpp.Response("00", 0x0410, "00"),
    ),
    Setup(
        FeatureTest(settings_templates.LowresMode, False, True),
        fake_hidpp.Response("00", 0x0400),
        fake_hidpp.Response("01", 0x0410, "01"),
    ),
    Setup(
        FeatureTest(settings_templates.HiresSmoothInvert, True, False),
        fake_hidpp.Response("06", 0x0410),
        fake_hidpp.Response("02", 0x0420, "02"),
    ),
    Setup(
        FeatureTest(settings_templates.HiresSmoothResolution, True, False),
        fake_hidpp.Response("06", 0x0410),
        fake_hidpp.Response("04", 0x0420, "04"),
    ),
    Setup(
        FeatureTest(settings_templates.HiresMode, False, True),
        fake_hidpp.Response("06", 0x0410),
        fake_hidpp.Response("07", 0x0420, "07"),
    ),
    Setup(
        FeatureTest(settings_templates.PointerSpeed, 0x0100, 0x0120),
        fake_hidpp.Response("0100", 0x0400),
        fake_hidpp.Response("0120", 0x0410, "0120"),
    ),
    Setup(
        FeatureTest(settings_templates.ThumbMode, True, False),
        fake_hidpp.Response("0100", 0x0410),
        fake_hidpp.Response("0000", 0x0420, "0000"),
    ),
    Setup(
        FeatureTest(settings_templates.ThumbInvert, False, True),
        fake_hidpp.Response("0100", 0x0410),
        fake_hidpp.Response("0101", 0x0420, "0101"),
    ),
    Setup(
        FeatureTest(settings_templates.DivertCrown, False, True),
        fake_hidpp.Response("01", 0x0410),
        fake_hidpp.Response("02", 0x0420, "02"),
    ),
    Setup(
        FeatureTest(settings_templates.CrownSmooth, True, False),
        fake_hidpp.Response("0001", 0x0410),
        fake_hidpp.Response("0002", 0x0420, "0002"),
    ),
    Setup(
        FeatureTest(settings_templates.DivertGkeys, False, True),
        fake_hidpp.Response("01", 0x0420, "01"),
    ),
    Setup(
        FeatureTest(settings_templates.ScrollRatchet, 2, 1),
        fake_hidpp.Response("02", 0x0400),
        fake_hidpp.Response("01", 0x0410, "01"),
    ),
    Setup(
        FeatureTest(settings_templates.SmartShift, 1, 10),
        fake_hidpp.Response("0100", 0x0400),
        fake_hidpp.Response("000A", 0x0410, "000A"),
    ),
    Setup(
        FeatureTest(settings_templates.SmartShift, 5, 50),
        fake_hidpp.Response("0005", 0x0400),
        fake_hidpp.Response("00FF", 0x0410, "00FF"),
    ),
    Setup(
        FeatureTest(settings_templates.SmartShiftEnhanced, 5, 50),
        fake_hidpp.Response("0005", 0x0410),
        fake_hidpp.Response("00FF", 0x0420, "00FF"),
    ),
    Setup(
        FeatureTest(settings_templates.DisableKeyboardKeys, {1: True, 8: True}, {1: False, 8: True}),
        fake_hidpp.Response("09", 0x0400),
        fake_hidpp.Response("09", 0x0410),
        fake_hidpp.Response("08", 0x0420, "08"),
    ),
    Setup(
        FeatureTest(settings_templates.DualPlatform, 0, 1),
        fake_hidpp.Response("00", 0x0400),
        fake_hidpp.Response("01", 0x0420, "01"),
    ),
    Setup(
        FeatureTest(settings_templates.MKeyLEDs, {1: False, 2: False, 4: False}, {1: False, 2: True, 4: True}),
        fake_hidpp.Response("03", 0x0400),
        fake_hidpp.Response("06", 0x0410, "06"),
    ),
    Setup(
        FeatureTest(settings_templates.MRKeyLED, False, True),
        fake_hidpp.Response("01", 0x0400, "01"),
    ),
    Setup(
        FeatureTest(settings_templates.Sidetone, 5, 0xA),
        fake_hidpp.Response("05", 0x0400),
        fake_hidpp.Response("0A", 0x0410, "0A"),
    ),
    Setup(
        FeatureTest(settings_templates.ADCPower, 5, 0xA, version=0x03),
        fake_hidpp.Response("05", 0x0410),
        fake_hidpp.Response("0A", 0x0420, "0A"),
    ),
    Setup(
        FeatureTest(settings_templates.LEDControl, 0, 1),
        fake_hidpp.Response("00", 0x0470),
        fake_hidpp.Response("01", 0x0480, "01"),
    ),
    Setup(
        FeatureTest(
            settings_templates.LEDZoneSetting,
            hidpp20.LEDEffectSetting(ID=3, intensity=0x50, period=0x100),
            hidpp20.LEDEffectSetting(ID=3, intensity=0x50, period=0x101),
        ),
        fake_hidpp.Response("0100000001", 0x0400),
        fake_hidpp.Response("00000102", 0x0410, "00FF00"),
        fake_hidpp.Response("0000000300040005", 0x0420, "000000"),
        fake_hidpp.Response("0001000B00080009", 0x0420, "000100"),
        fake_hidpp.Response("000000000000010050", 0x04E0, "00"),
        fake_hidpp.Response("000000000000000101500000", 0x0430, "000000000000000101500000"),
    ),
    Setup(
        FeatureTest(settings_templates.RGBControl, 0, 1),
        fake_hidpp.Response("0000", 0x0450),
        fake_hidpp.Response("010100", 0x0450, "0101"),
    ),
    Setup(
        FeatureTest(
            settings_templates.RGBEffectSetting,
            hidpp20.LEDEffectSetting(ID=3, intensity=0x50, period=0x100),
            hidpp20.LEDEffectSetting(ID=2, color=0x505050, speed=0x50),
            readable=False,
        ),
        fake_hidpp.Response("FFFF0100000001", 0x0400, "FFFF00"),
        fake_hidpp.Response("0000000102", 0x0400, "00FF00"),
        fake_hidpp.Response("0000000300040005", 0x0400, "000000"),
        fake_hidpp.Response("0001000200080009", 0x0400, "000100"),
        fake_hidpp.Response("000000000000010050", 0x04E0, "00"),
        fake_hidpp.Response("00015050505000000000000001", 0x0410, "00015050505000000000000001"),
    ),
    Setup(
        FeatureTest(
            settings_templates.RGBEffectSetting,
            None,
            hidpp20.LEDEffectSetting(ID=3, intensity=0x60, period=0x101),
            readable=False,
        ),
        fake_hidpp.Response("FFFF0100000001", 0x0400, "FFFF00"),
        fake_hidpp.Response("0000000102", 0x0400, "00FF00"),
        fake_hidpp.Response("0000000300040005", 0x0400, "000000"),
        fake_hidpp.Response("0001000200080009", 0x0400, "000100"),
        fake_hidpp.Response("00000000000000010160000001", 0x0410, "00000000000000010160000001"),
    ),
    Setup(
        FeatureTest(
            settings_templates.RGBEffectSetting,
            None,
            hidpp20.LEDEffectSetting(ID=3, intensity=0x60, period=0x101),
            readable=False,
        ),
        fake_hidpp.Response("FF000200020004000000000000000000", 0x0400, "FFFF00"),
        fake_hidpp.Response("00000002040000000000000000000000", 0x0400, "00FF00"),
        fake_hidpp.Response("00000000000000000000000000000000", 0x0400, "000000"),
        fake_hidpp.Response("00010001000000000000000000000000", 0x0400, "000100"),
        fake_hidpp.Response("00020003C00503E00000000000000000", 0x0400, "000200"),
        fake_hidpp.Response("0003000AC0011E0B0000000000000000", 0x0400, "000300"),
        fake_hidpp.Response("01000001070000000000000000000000", 0x0400, "01FF00"),
        fake_hidpp.Response("01000000000000000000000000000000", 0x0400, "010000"),
        fake_hidpp.Response("01010001000000000000000000000000", 0x0400, "010100"),
        fake_hidpp.Response("0102000AC0011E0B0000000000000000", 0x0400, "010200"),
        fake_hidpp.Response("01030003C00503E00000000000000000", 0x0400, "010300"),
        fake_hidpp.Response("01040004DCE1001E0000000000000000", 0x0400, "010400"),
        fake_hidpp.Response("0105000B000000320000000000000000", 0x0400, "010500"),
        fake_hidpp.Response("0106000C001B02340000000000000000", 0x0400, "010600"),
        fake_hidpp.Response("00020000000000010160000001", 0x0410, "00020000000000010160000001"),
    ),
    Setup(
        FeatureTest(settings_templates.Backlight2, 0xFF, 0x00),
        common.NamedInts(Disabled=0xFF, Enabled=0x00),
        fake_hidpp.Response("000201000000000000000000", 0x0400),
        fake_hidpp.Response("010201", 0x0410, "0102FF00000000000000"),
    ),
    Setup(
        FeatureTest(settings_templates.Backlight2, 0x03, 0xFF),
        common.NamedInts(Disabled=0xFF, Automatic=0x01, Manual=0x03),
        fake_hidpp.Response("011838000000000000000000", 0x0400),
        fake_hidpp.Response("001801", 0x0410, "0018FF00000000000000"),
    ),
    Setup(
        FeatureTest(settings_templates.Backlight2Level, 0, 3, version=0x03),
        [0, 4],
        fake_hidpp.Response("011830000000000000000000", 0x0400),
        fake_hidpp.Response("05", 0x0420),
        fake_hidpp.Response("01180103000000000000", 0x0410, "0118FF03000000000000"),
    ),
    Setup(
        FeatureTest(settings_templates.Backlight2Level, 0, 2, version=0x03),
        [0, 4],
        fake_hidpp.Response("011830000000000000000000", 0x0400),
        fake_hidpp.Response("05", 0x0420),
        fake_hidpp.Response("01180102000000000000", 0x0410, "0118FF02000000000000"),
    ),
    Setup(
        FeatureTest(settings_templates.OnboardProfiles, 0, 1, offset=0x0C),
        common.NamedInts(**{"Disabled": 0, "Profile 1": 1, "Profile 2": 2}),
        fake_hidpp.Response("01030001010101000101", 0x0C00),
        fake_hidpp.Response("00010100000201FFFFFFFFFFFFFFFFFF", 0x0C50, "00000000"),
        fake_hidpp.Response("000201FFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0C50, "00000004"),
        fake_hidpp.Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0C50, "00000008"),
        fake_hidpp.Response("02", 0x0C20),
        fake_hidpp.Response("01", 0x0C10, "01"),
        fake_hidpp.Response("0001", 0x0C30, "0001"),
    ),
    Setup(
        FeatureTest(settings_templates.OnboardProfiles, 1, 0, offset=0x0C),
        common.NamedInts(**{"Disabled": 0, "Profile 1": 1, "Profile 2": 2}),
        fake_hidpp.Response("01030001010101000101", 0x0C00),
        fake_hidpp.Response("00010100000201FFFFFFFFFFFFFFFFFF", 0x0C50, "00000000"),
        fake_hidpp.Response("000201FFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0C50, "00000004"),
        fake_hidpp.Response("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 0x0C50, "00000008"),
        fake_hidpp.Response("01", 0x0C20),
        fake_hidpp.Response("0001", 0x0C40),
        fake_hidpp.Response("02", 0x0C10, "02"),
    ),
    Setup(
        FeatureTest(settings_templates.ReportRate, 1, 5, offset=0x0C),
        common.NamedInts(**{"1ms": 1, "2ms": 2, "5ms": 5, "6ms": 6}),
        fake_hidpp.Response("33", 0x0C00),
        fake_hidpp.Response("01", 0x0C10),
        fake_hidpp.Response("05", 0x0C20, "05"),
    ),
    Setup(
        FeatureTest(settings_templates.ExtendedReportRate, 1, 5, offset=0x0C),
        common.NamedInts(**{"8ms": 0, "4ms": 1, "500us": 4, "250us": 5}),
        fake_hidpp.Response("33", 0x0C10),
        fake_hidpp.Response("01", 0x0C20),
        fake_hidpp.Response("05", 0x0C30, "05"),
    ),
    Setup(
        FeatureTest(settings_templates.AdjustableDpi, 800, 400, version=0x03),
        common.NamedInts.list([400, 800, 1600]),
        fake_hidpp.Response("000190032006400000", 0x0410, "000000"),
        fake_hidpp.Response("000320", 0x0420),
        fake_hidpp.Response("000190", 0x0430, "000190"),
    ),
    Setup(
        FeatureTest(settings_templates.AdjustableDpi, 256, 512, version=0x03),
        common.NamedInts.list([256, 512]),
        fake_hidpp.Response("000100e10002000000", 0x0410, "000000"),
        fake_hidpp.Response("000100", 0x0420),
        fake_hidpp.Response("000200", 0x0430, "000200"),
    ),
    Setup(
        FeatureTest(settings_templates.AdjustableDpi, 400, 800, version=0x03),
        common.NamedInts.list([400, 800, 1200, 1600]),
        fake_hidpp.Response("000190E19006400000000000000000", 0x0410, "000000"),
        fake_hidpp.Response("000190", 0x0420),
        fake_hidpp.Response("000320", 0x0430, "000320"),
    ),
    Setup(
        FeatureTest(settings_templates.Multiplatform, 0, 1),
        common.NamedInts(**{"MacOS 0.1-0.5": 0, "iOS 0.1-0.7": 1, "Linux 0.2-0.9": 2, "Windows 0.3-0.9": 3}),
        fake_hidpp.Response("020004000001", 0x0400),
        fake_hidpp.Response("00FF200000010005", 0x0410, "00"),
        fake_hidpp.Response("01FF400000010007", 0x0410, "01"),
        fake_hidpp.Response("02FF040000020009", 0x0410, "02"),
        fake_hidpp.Response("03FF010000030009", 0x0410, "03"),
        fake_hidpp.Response("FF01", 0x0430, "FF01"),
    ),
    Setup(
        FeatureTest(settings_templates.ChangeHost, 1, 0),
        common.NamedInts(**{"1:ABCDEF": 0, "2:GHIJKL": 1}),
        fake_hidpp.Response("050003", 0x0000, "1815"),  # HOSTS_INFO
        fake_hidpp.Response("01000200", 0x0500),
        fake_hidpp.Response("000100000600", 0x0510, "00"),
        fake_hidpp.Response("000041424344454600", 0x0530, "0000"),
        fake_hidpp.Response("000100000600", 0x0510, "01"),
        fake_hidpp.Response("00004748494A4B4C00", 0x0530, "0100"),
        fake_hidpp.Response("0201", 0x0400),
        fake_hidpp.Response(True, 0x0410, "00"),
    ),
    Setup(
        FeatureTest(settings_templates.BrightnessControl, 0x10, 0x20),
        [0, 80],
        fake_hidpp.Response("00505100000000", 0x0400),  # 0 to 80, all acceptable, no separate on/off
        fake_hidpp.Response("10", 0x0410),  # brightness 16
        fake_hidpp.Response("0020", 0x0420, "0020"),  # set brightness 32
    ),
    Setup(
        FeatureTest(settings_templates.BrightnessControl, 0x10, 0x00),
        [0, 80],
        fake_hidpp.Response("00505104000000", 0x0400),  # 0 to 80, all acceptable, separate on/off
        fake_hidpp.Response("10", 0x0410),  # brightness 16
        fake_hidpp.Response("01", 0x0430),  # on
        fake_hidpp.Response("00", 0x0440),  # set off
        fake_hidpp.Response("0000", 0x0420, "0000"),  # set brightness 0
    ),
    Setup(
        FeatureTest(settings_templates.BrightnessControl, 0x00, 0x20),
        [0, 80],
        fake_hidpp.Response("00505104000000", 0x0400),  # 0 to 80, all acceptable, separate on/off
        fake_hidpp.Response("10", 0x0410),  # brightness 16
        fake_hidpp.Response("00", 0x0430),  # off
        fake_hidpp.Response("01", 0x0440),  # set on
        fake_hidpp.Response("0020", 0x0420, "0020"),  # set brightness 32
    ),
    Setup(
        FeatureTest(settings_templates.BrightnessControl, 0x20, 0x08),
        [0, 80],
        fake_hidpp.Response("00504104001000", 0x0400),  # 16 to 80, all acceptable, separate on/off
        fake_hidpp.Response("20", 0x0410),  # brightness 32
        fake_hidpp.Response("01", 0x0430),  # on
        fake_hidpp.Response("00", 0x0440, "00"),  # set off
    ),
    Setup(
        FeatureTest(settings_templates.SpeedChange, 0, None, 0),  # need to set up all settings to successfully write
        common.NamedInts(**{"Off": 0, "DPI Change": 0xED}),
        *fake_hidpp.responses_speedchange,
    ),
]


@pytest.fixture
def mock_gethostname(mocker):
    mocker.patch("socket.gethostname", return_value="ABCDEF.foo.org")


@pytest.mark.parametrize("test", simple_tests)
def test_simple_template(test, mocker, mock_gethostname):
    tst = test.test
    print("TEST", tst.sclass, tst.sclass.feature)
    device = fake_hidpp.Device(responses=test.responses, feature=tst.sclass.feature, offset=tst.offset, version=tst.version)
    spy_request = mocker.spy(device, "request")

    setting = settings_templates.check_feature(device, tst.sclass)
    assert setting is not None
    if isinstance(setting, list):
        setting = setting[0]
    if isinstance(test.choices, list):
        assert setting._validator.min_value == test.choices[0]
        assert setting._validator.max_value == test.choices[1]
    elif test.choices is not None:
        assert setting.choices == test.choices

    if tst.readable:
        value = setting.read(cached=False)
        assert value == tst.initial_value

        cached_value = setting.read(cached=True)
        assert cached_value == tst.initial_value

    write_value = setting.write(tst.write_value) if tst.write_value is not None else None
    assert write_value == tst.write_value

    fake_hidpp.match_requests(tst.matched_calls, test.responses, spy_request.call_args_list)


responses_reprog_controls = [
    fake_hidpp.Response("03", 0x0500),
    fake_hidpp.Response("00500038010001010400000000000000", 0x0510, "00"),  # left button
    fake_hidpp.Response("00510039010001010400000000000000", 0x0510, "01"),  # right button
    fake_hidpp.Response("00C4009D310003070500000000000000", 0x0510, "02"),  # smart shift
    fake_hidpp.Response("00500000000000000000000000000000", 0x0520, "0050"),  # left button current
    fake_hidpp.Response("00510000500000000000000000000000", 0x0520, "0051"),  # right button current
    fake_hidpp.Response("00C40000000000000000000000000000", 0x0520, "00C4"),  # smart shift current
    fake_hidpp.Response("00500005000000000000000000000000", 0x0530, "0050000050"),  # left button write
    fake_hidpp.Response("00510005000000000000000000000000", 0x0530, "0051000050"),  # right button write
    fake_hidpp.Response("00C4000C400000000000000000000000", 0x0530, "00C40000C4"),  # smart shift write
]

key_tests = [
    Setup(
        FeatureTest(settings_templates.ReprogrammableKeys, {0x50: 0x50, 0x51: 0x50, 0xC4: 0xC4}, {0x51: 0x51}, 4, offset=0x05),
        {
            common.NamedInt(0x50, "Left Button"): common.UnsortedNamedInts(Left_Click=0x50, Right_Click=0x51),
            common.NamedInt(0x51, "Right Button"): common.UnsortedNamedInts(Right_Click=0x51, Left_Click=0x50),
            common.NamedInt(0xC4, "Smart Shift"): common.UnsortedNamedInts(Smart_Shift=0xC4, Left_Click=80, Right_Click=81),
        },
        *responses_reprog_controls,
        fake_hidpp.Response("0051000051", 0x0530, "0051000051"),  # right button set write
    ),
    Setup(
        FeatureTest(settings_templates.DivertKeys, {0xC4: 0}, {0xC4: 1}, 2, offset=0x05),
        {common.NamedInt(0xC4, "Smart Shift"): common.NamedInts(Regular=0, Diverted=1, Mouse_Gestures=2)},
        *responses_reprog_controls,
        fake_hidpp.Response("00C4020000", 0x0530, "00C4020000"),  # Smart Shift write
        fake_hidpp.Response("00C4030000", 0x0530, "00C4030000"),  # Smart Shift divert write
    ),
    Setup(
        FeatureTest(settings_templates.DivertKeys, {0xC4: 0}, {0xC4: 2}, 2, offset=0x05),
        {common.NamedInt(0xC4, "Smart Shift"): common.NamedInts(Regular=0, Diverted=1, Mouse_Gestures=2, Sliding_DPI=3)},
        *responses_reprog_controls,
        fake_hidpp.Response("0A0001", 0x0000, "2201"),  # ADJUSTABLE_DPI
        fake_hidpp.Response("00C4300000", 0x0530, "00C4300000"),  # Smart Shift write
        fake_hidpp.Response("00C4030000", 0x0530, "00C4030000"),  # Smart Shift divert write
    ),
    Setup(
        FeatureTest(settings_templates.PersistentRemappableAction, {80: 16797696, 81: 16797696}, {0x51: 16797952}, 3),
        {
            common.NamedInt(80, "Left Button"): special_keys.KEYS_KEYS_CONSUMER,
            common.NamedInt(81, "Right Button"): special_keys.KEYS_KEYS_CONSUMER,
        },
        fake_hidpp.Response("050001", 0x0000, "1B04"),  # REPROG_CONTROLS_V4
        *responses_reprog_controls,
        fake_hidpp.Response("0041", 0x0400),
        fake_hidpp.Response("0201", 0x0410),
        fake_hidpp.Response("02", 0x0400),
        fake_hidpp.Response("0050", 0x0420, "00FF"),  # left button
        fake_hidpp.Response("0051", 0x0420, "01FF"),  # right button
        fake_hidpp.Response("0050000100500000", 0x0430, "0050FF"),  # left button current
        fake_hidpp.Response("0051000100500001", 0x0430, "0051FF"),  # right button current
        fake_hidpp.Response("0050FF01005000", 0x0440, "0050FF01005000"),  # left button write
        fake_hidpp.Response("0051FF01005000", 0x0440, "0051FF01005000"),  # right button write
        fake_hidpp.Response("0051FF01005100", 0x0440, "0051FF01005100"),  # right button set write
    ),
    Setup(
        FeatureTest(
            settings_templates.Gesture2Gestures,
            {
                1: True,
                2: True,
                30: True,
                10: True,
                45: False,
                42: True,
                43: True,
                64: False,
                65: False,
                67: False,
                84: True,
                34: False,
            },
            {45: True},
            4,
        ),
        *fake_hidpp.responses_gestures,
        fake_hidpp.Response("0001FF6F", 0x0420, "0001FF6F"),  # write
        fake_hidpp.Response("01010F04", 0x0420, "01010F04"),
        fake_hidpp.Response("0001FF7F", 0x0420, "0001FF7F"),  # write 45
        fake_hidpp.Response("01010F04", 0x0420, "01010F04"),
    ),
    Setup(
        FeatureTest(
            settings_templates.Gesture2Divert,
            {1: False, 2: False, 10: False, 44: False, 64: False, 65: False, 67: False, 84: False, 85: False, 100: False},
            {44: True},
            4,
        ),
        *fake_hidpp.responses_gestures,
        fake_hidpp.Response("0001FF00", 0x0440, "0001FF00"),  # write
        fake_hidpp.Response("01010300", 0x0440, "01010300"),
        fake_hidpp.Response("0001FF08", 0x0440, "0001FF08"),  # write 44
        fake_hidpp.Response("01010300", 0x0440, "01010300"),
    ),
    Setup(
        FeatureTest(settings_templates.Gesture2Params, {4: {"scale": 256}}, {4: {"scale": 128}}, 2),
        *fake_hidpp.responses_gestures,
        fake_hidpp.Response("000100FF000000000000000000000000", 0x0480, "000100FF"),
        fake_hidpp.Response("000080FF000000000000000000000000", 0x0480, "000080FF"),
    ),
    Setup(
        FeatureTest(settings_templates.Equalizer, {0: -0x20, 1: 0x10}, {1: 0x18}, 2),
        [-32, 32],
        fake_hidpp.Response("0220000000", 0x0400),
        fake_hidpp.Response("0000800100000000000000", 0x0410, "00"),
        fake_hidpp.Response("E010", 0x0420, "00"),
        fake_hidpp.Response("E010", 0x0430, "02E010"),
        fake_hidpp.Response("E018", 0x0430, "02E018"),
    ),
    Setup(
        FeatureTest(settings_templates.PerKeyLighting, {1: -1, 2: -1, 9: -1, 10: -1, 113: -1}, {2: 0xFF0000}, 4, 4, 0, 1),
        {
            common.NamedInt(1, "A"): special_keys.COLORSPLUS,
            common.NamedInt(2, "B"): special_keys.COLORSPLUS,
            common.NamedInt(9, "I"): special_keys.COLORSPLUS,
            common.NamedInt(10, "J"): special_keys.COLORSPLUS,
            common.NamedInt(113, "KEY 113"): special_keys.COLORSPLUS,
        },
        fake_hidpp.Response("00000606000000000000000000000000", 0x0400, "0000"),  # first group of keys
        fake_hidpp.Response("00000200000000000000000000000000", 0x0400, "0001"),  # second group of keys
        fake_hidpp.Response("00000000000000000000000000000000", 0x0400, "0002"),  # last group of keys
        fake_hidpp.Response("02FF0000", 0x0410, "02FF0000"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
        fake_hidpp.Response("02FF0000", 0x0410, "02FF0000"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
    ),
    Setup(
        FeatureTest(
            settings_templates.PerKeyLighting,
            {1: -1, 2: -1, 9: -1, 10: -1, 113: -1},
            {2: 0xFF0000, 9: 0xFF0000, 10: 0xFF0000},
            8,
            4,
            0,
            1,
        ),
        {
            common.NamedInt(1, "A"): special_keys.COLORSPLUS,
            common.NamedInt(2, "B"): special_keys.COLORSPLUS,
            common.NamedInt(9, "I"): special_keys.COLORSPLUS,
            common.NamedInt(10, "J"): special_keys.COLORSPLUS,
            common.NamedInt(113, "KEY 113"): special_keys.COLORSPLUS,
        },
        fake_hidpp.Response("00000606000000000000000000000000", 0x0400, "0000"),  # first group of keys
        fake_hidpp.Response("00000200000000000000000000000000", 0x0400, "0001"),  # second group of keys
        fake_hidpp.Response("00000000000000000000000000000000", 0x0400, "0002"),  # last group of keys
        fake_hidpp.Response("02FF0000", 0x0410, "02FF0000"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
        fake_hidpp.Response("09FF0000", 0x0410, "09FF0000"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
        fake_hidpp.Response("0AFF0000", 0x0410, "0AFF0000"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
        fake_hidpp.Response("02FF000009FF00000AFF0000", 0x0410, "02FF000009FF00000AFF0000"),  # write three values
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
    ),
    Setup(
        FeatureTest(
            settings_templates.PerKeyLighting,
            {1: -1, 2: -1, 9: -1, 10: -1, 113: -1, 114: -1},
            {1: 0xFF0000, 2: 0xFF0000, 9: 0xFF0000, 10: 0xFF0000, 113: 0x00FF00, 114: 0xFF0000},
            15,
            4,
            0,
            1,
        ),
        {
            common.NamedInt(1, "A"): special_keys.COLORSPLUS,
            common.NamedInt(2, "B"): special_keys.COLORSPLUS,
            common.NamedInt(9, "I"): special_keys.COLORSPLUS,
            common.NamedInt(10, "J"): special_keys.COLORSPLUS,
            common.NamedInt(113, "KEY 113"): special_keys.COLORSPLUS,
            common.NamedInt(114, "KEY 114"): special_keys.COLORSPLUS,
        },
        fake_hidpp.Response("00000606000000000000000000000000", 0x0400, "0000"),  # first group of keys
        fake_hidpp.Response("00000600000000000000000000000000", 0x0400, "0001"),  # second group of keys
        fake_hidpp.Response("00000000000000000000000000000000", 0x0400, "0002"),  # last group of keys
        fake_hidpp.Response("01FF0000", 0x0410, "01FF0000"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
        fake_hidpp.Response("02FF0000", 0x0410, "02FF0000"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
        fake_hidpp.Response("09FF0000", 0x0410, "09FF0000"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
        fake_hidpp.Response("0AFF0000", 0x0410, "0AFF0000"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
        fake_hidpp.Response("7100FF00", 0x0410, "7100FF00"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
        fake_hidpp.Response("72FF0000", 0x0410, "72FF0000"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
        fake_hidpp.Response("FF00000102090A72", 0x460, "FF00000102090A72"),  # write one value for five keys
        fake_hidpp.Response("7100FF00", 0x0410, "7100FF00"),  # write one value
        fake_hidpp.Response("00", 0x0470, "00"),  # finish
    ),
    Setup(
        FeatureTest(settings_templates.ExtendedAdjustableDpi, {0: 256}, {0: 512}, 2, offset=0x9),
        {common.NamedInt(0, "X"): common.NamedInts.list([256, 512])},
        fake_hidpp.Response("000000", 0x0910, "00"),  # no y direction, no lod
        fake_hidpp.Response("0000000100e10002000000", 0x0920, "000000"),
        fake_hidpp.Response("00010000000000000000", 0x0950),
        fake_hidpp.Response("000100000000", 0x0960, "000100000000"),
        fake_hidpp.Response("000200000000", 0x0960, "000200000000"),
    ),
    Setup(
        FeatureTest(settings_templates.ExtendedAdjustableDpi, {0: 0x64, 1: 0xE4}, {0: 0x164}, 2, offset=0x9),
        {
            common.NamedInt(0, "X"): common.NamedInts.list([0x064, 0x074, 0x084, 0x0A4, 0x0C4, 0x0E4, 0x0124, 0x0164, 0x01C4]),
            common.NamedInt(1, "Y"): common.NamedInts.list([0x064, 0x074, 0x084, 0x0A4, 0x0C4, 0x0E4, 0x0124, 0x0164]),
        },
        fake_hidpp.Response("000001", 0x0910, "00"),  # supports y direction, no lod
        fake_hidpp.Response("0000000064E0100084E02000C4E02000", 0x0920, "000000"),
        fake_hidpp.Response("000001E4E0400124E0400164E06001C4", 0x0920, "000001"),
        fake_hidpp.Response("00000000000000000000000000000000", 0x0920, "000002"),
        fake_hidpp.Response("0000000064E0100084E02000C4E02000", 0x0920, "000100"),
        fake_hidpp.Response("000001E4E0400124E040016400000000", 0x0920, "000101"),
        fake_hidpp.Response("000064007400E4007400", 0x0950),
        fake_hidpp.Response("00006400E400", 0x0960, "00006400E400"),
        fake_hidpp.Response("00016400E400", 0x0960, "00016400E400"),
    ),
    Setup(
        FeatureTest(settings_templates.ExtendedAdjustableDpi, {0: 0x64, 1: 0xE4, 2: 1}, {1: 0x164}, 2, offset=0x9),
        {
            common.NamedInt(0, "X"): common.NamedInts.list([0x064, 0x074, 0x084, 0x0A4, 0x0C4, 0x0E4, 0x0124, 0x0164, 0x01C4]),
            common.NamedInt(1, "Y"): common.NamedInts.list([0x064, 0x074, 0x084, 0x0A4, 0x0C4, 0x0E4, 0x0124, 0x0164]),
            common.NamedInt(2, "LOD"): common.NamedInts(LOW=0, MEDIUM=1, HIGH=2),
        },
        fake_hidpp.Response("000003", 0x0910, "00"),  # supports y direction and lod
        fake_hidpp.Response("0000000064E0100084E02000C4E02000", 0x0920, "000000"),
        fake_hidpp.Response("000001E4E0400124E0400164E06001C4", 0x0920, "000001"),
        fake_hidpp.Response("00000000000000000000000000000000", 0x0920, "000002"),
        fake_hidpp.Response("0000000064E0100084E02000C4E02000", 0x0920, "000100"),
        fake_hidpp.Response("000001E4E0400124E040016400000000", 0x0920, "000101"),
        fake_hidpp.Response("000064007400E4007401", 0x0950),
        fake_hidpp.Response("00006400E401", 0x0960, "00006400E401"),
        fake_hidpp.Response("000064016401", 0x0960, "000064016401"),
    ),
]


@pytest.mark.parametrize("test", key_tests)
def test_key_template(test, mocker):
    tst = test.test
    device = fake_hidpp.Device(responses=test.responses, feature=tst.sclass.feature, offset=tst.offset, version=tst.version)
    spy_request = mocker.spy(device, "request")

    print("FEATURE", tst.sclass.feature)
    setting = settings_templates.check_feature(device, tst.sclass)
    assert setting is not None
    if isinstance(setting, list):
        setting = setting[0]
    if isinstance(test.choices, list):
        assert setting._validator.min_value == test.choices[0]
        assert setting._validator.max_value == test.choices[1]
    elif test.choices is not None:
        assert setting.choices == test.choices

    value = setting.read(cached=False)
    assert value == tst.initial_value

    write_value = setting.write(value)
    assert write_value == tst.initial_value

    for key, val in tst.write_value.items():
        write_value = setting.write_key_value(key, val)
        assert write_value == val
        value[key] = val

    if tst.rewrite:
        write_value = setting.write(value)

    fake_hidpp.match_requests(tst.matched_calls, test.responses, spy_request.call_args_list)


@pytest.mark.parametrize(
    "responses, currentSpeed, newSpeed",
    [
        (fake_hidpp.responses_speedchange, 100, 200),
        (fake_hidpp.responses_speedchange, None, 250),
    ],
)
def test_SpeedChange_action(responses, currentSpeed, newSpeed, mocker):
    device = fake_hidpp.Device(responses=responses, feature=hidpp20_constants.SupportedFeature.POINTER_SPEED)
    spy_setting_callback = mocker.spy(device, "setting_callback")
    settings_templates.check_feature_settings(device, device.settings)  # need to set up all the settings
    device.persister = {"pointer_speed": currentSpeed, "_speed-change": newSpeed}
    speed_setting = next(filter(lambda s: s.name == "speed-change", device.settings), None)
    pointer_setting = next(filter(lambda s: s.name == "pointer_speed", device.settings), None)

    speed_setting.write(237)
    speed_setting._rw.press_action()

    if newSpeed is not None and speed_setting is not None:
        spy_setting_callback.assert_any_call(device, type(pointer_setting), [newSpeed])
    assert device.persister["_speed-change"] == currentSpeed
    assert device.persister["pointer_speed"] == newSpeed


@pytest.mark.parametrize("test", simple_tests + key_tests)
def test_check_feature_settings(test, mocker):
    tst = test.test
    device = fake_hidpp.Device(responses=test.responses, feature=tst.sclass.feature, offset=tst.offset, version=tst.version)

    already_known = []
    setting = settings_templates.check_feature_settings(device, already_known)

    assert setting is True
    assert already_known


@pytest.mark.parametrize(
    "test",
    [
        Setup(
            FeatureTest(settings_templates.K375sFnSwap, False, True, offset=0x06),
            fake_hidpp.Response("FF0001", 0x0600, "FF"),
            fake_hidpp.Response("FF0101", 0x0610, "FF01"),
        )
    ],
)
def test_check_feature_setting(test, mocker):
    tst = test.test
    device = fake_hidpp.Device(responses=test.responses, feature=tst.sclass.feature, offset=tst.offset, version=tst.version)

    setting = settings_templates.check_feature_setting(device, tst.sclass.name)

    assert setting
