import cv2
import numpy as np

from src.repositories.radar import core


def test_get_coordinate_supports_one_to_one_world_map_scale(mocker):
    rng = np.random.default_rng(6)
    floor = rng.integers(0, 255, (260, 260), dtype=np.uint8)
    radar = rng.integers(0, 255, (109, 106), dtype=np.uint8)
    floor[76:185, 67:173] = radar
    mocker.patch.object(core, 'coordinates', {})
    mocker.patch.object(core, 'floorsImgs', [floor] * 16)
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(1, 2, 3, 4))
    mocker.patch.object(core, 'getRadarImage', return_value=radar)
    mocker.patch.object(core, 'getFloorLevel', return_value=7)

    coordinate = core.getCoordinate(object())

    assert coordinate == (31864, 31106, 7)


def test_get_coordinate_supports_two_times_world_map_scale(mocker):
    rng = np.random.default_rng(7)
    floor = rng.integers(0, 255, (260, 260), dtype=np.uint8)
    player_pixel = (120, 130)
    radar = rng.integers(0, 255, (109, 106), dtype=np.uint8)
    floor[21:239, 14:226] = cv2.resize(
        radar, (212, 218), interpolation=cv2.INTER_NEAREST,
    )
    mocker.patch.object(core, 'coordinates', {})
    mocker.patch.object(core, 'floorsImgs', [floor] * 16)
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(1, 2, 3, 4))
    mocker.patch.object(core, 'getRadarImage', return_value=radar)
    mocker.patch.object(core, 'getFloorLevel', return_value=7)

    coordinate = core.getCoordinate(object())

    assert abs(coordinate[0] - 31864) <= 1
    assert abs(coordinate[1] - 31106) <= 1
    assert coordinate[2] == 7


def test_get_coordinate_can_start_outside_route_window(mocker):
    rng = np.random.default_rng(12)
    floor = rng.integers(0, 255, (260, 260), dtype=np.uint8)
    radar = rng.integers(0, 255, (109, 106), dtype=np.uint8)
    floor[21:239, 14:226] = cv2.resize(
        radar, (212, 218), interpolation=cv2.INTER_NEAREST,
    )
    mocker.patch.object(core, 'coordinates', {})
    mocker.patch.object(core, 'floorsImgs', [floor] * 16)
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(1, 2, 3, 4))
    mocker.patch.object(core, 'getRadarImage', return_value=radar)
    mocker.patch.object(core, 'getFloorLevel', return_value=7)
    waypoints = [{'coordinate': [31750, 30990, 7]}]

    coordinate = core.getCoordinate(object(), waypoints=waypoints)

    assert abs(coordinate[0] - 31864) <= 1
    assert abs(coordinate[1] - 31106) <= 1
    assert coordinate[2] == 7


def test_previous_coordinate_can_resume_outside_route(mocker):
    rng = np.random.default_rng(13)
    floor = rng.integers(0, 255, (260, 260), dtype=np.uint8)
    radar = rng.integers(0, 255, (109, 106), dtype=np.uint8)
    floor[21:239, 14:226] = cv2.resize(
        radar, (212, 218), interpolation=cv2.INTER_NEAREST,
    )
    mocker.patch.object(core, 'coordinates', {})
    mocker.patch.object(core, 'floorsImgs', [floor] * 16)
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(1, 2, 3, 4))
    mocker.patch.object(core, 'getRadarImage', return_value=radar)
    mocker.patch.object(core, 'getFloorLevel', return_value=7)

    coordinate = core.getCoordinate(
        object(),
        previousCoordinate=(31864, 31106, 7),
        waypoints=[{'coordinate': [31750, 30990, 7]}],
    )

    assert abs(coordinate[0] - 31864) <= 1
    assert abs(coordinate[1] - 31106) <= 1
    assert coordinate[2] == 7


def test_get_coordinate_rejects_weak_global_match(mocker):
    rng = np.random.default_rng(8)
    floor = rng.integers(0, 255, (260, 260), dtype=np.uint8)
    radar = rng.integers(0, 255, (109, 106), dtype=np.uint8)
    mocker.patch.object(core, 'coordinates', {})
    mocker.patch.object(core, 'floorsImgs', [floor] * 16)
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(1, 2, 3, 4))
    mocker.patch.object(core, 'getRadarImage', return_value=radar)
    mocker.patch.object(core, 'getFloorLevel', return_value=7)

    assert core.getCoordinate(object()) is None


def test_floor_level_accepts_only_clear_visual_match(mocker):
    rng = np.random.default_rng(9)
    template = rng.integers(0, 255, (67, 2), dtype=np.uint8)
    screenshot = np.zeros((100, 100), dtype=np.uint8)
    screenshot[3:70, 19:21] = template
    alternatives = [
        rng.integers(0, 255, (67, 2), dtype=np.uint8)
        for _ in range(16)
    ]
    alternatives[7] = template
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(10, 10, 1, 1))
    mocker.patch.object(core, 'hashit', return_value='unknown')
    mocker.patch.object(core, 'floorsLevelsImgsHashes', {})
    mocker.patch.object(core, 'floorsLevelsImgs', alternatives)

    assert core.getFloorLevel(screenshot) == 7


def test_floor_level_rejects_ambiguous_visual_match(mocker):
    rng = np.random.default_rng(10)
    template = rng.integers(0, 255, (67, 2), dtype=np.uint8)
    screenshot = np.zeros((100, 100), dtype=np.uint8)
    screenshot[3:70, 19:21] = template
    alternatives = [
        rng.integers(0, 255, (67, 2), dtype=np.uint8)
        for _ in range(16)
    ]
    alternatives[6] = template
    alternatives[7] = template
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(10, 10, 1, 1))
    mocker.patch.object(core, 'hashit', return_value='unknown')
    mocker.patch.object(core, 'floorsLevelsImgsHashes', {})
    mocker.patch.object(core, 'floorsLevelsImgs', alternatives)

    assert core.getFloorLevel(screenshot) is None


def test_local_coordinate_ignores_unexplored_minimap_pixels(mocker):
    rng = np.random.default_rng(11)
    palette = core.RADAR_PALETTE.astype(np.uint8)
    floor = rng.choice(palette, (260, 260)).astype(np.uint8)
    player_pixel = (120, 130)
    radar = rng.choice(palette, (109, 106)).astype(np.uint8)
    floor[21:239, 14:226] = cv2.resize(
        radar, (212, 218), interpolation=cv2.INTER_NEAREST,
    )
    radar[:28] = 0
    mocker.patch.object(core, 'coordinates', {})
    mocker.patch.object(core, 'floorsImgs', [floor] * 16)
    mocker.patch.object(core, 'getRadarToolsPosition', return_value=(1, 2, 3, 4))
    mocker.patch.object(core, 'getRadarImage', return_value=radar)
    mocker.patch.object(core, 'getFloorLevel', return_value=7)

    coordinate = core.getCoordinate(
        object(), previousCoordinate=(31864, 31106, 7),
    )

    assert abs(coordinate[0] - 31864) <= 1
    assert abs(coordinate[1] - 31106) <= 1
    assert coordinate[2] == 7
