import numpy as np
import pytest
from scipy.spatial.transform import Rotation as R

import magpylib as magpy
from magpylib._src.obj_classes.class_BaseTransform import apply_move
from magpylib._src.obj_classes.class_BaseTransform import apply_rotation


@pytest.mark.parametrize(
    (
        "description",
        "old_position",
        "displacement",
        "new_position",
        "start",
    ),
    [
        # SCALAR INPUT
        ("01_ with start='auto'", (0, 0, 0), (1, 2, 3), (1, 2, 3), "auto"),
        ("02_ with start=0", (1, 2, 3), (1, 2, 3), (2, 4, 6), 0),
        ("03_ with start=-1", (2, 4, 6), (1, 2, 3), (3, 6, 9), -1),
        ("04_ pad behind", (3, 6, 9), (-1, -2, -3), [(3, 6, 9), (2, 4, 6)], 1),
        (
            "05_ whole path",
            [(3, 6, 9), (2, 4, 6)],
            (-1, -2, -3),
            [(2, 4, 6), (1, 2, 3)],
            "auto",
        ),
        (
            "06_ pad before",
            [(2, 4, 6), (1, 2, 3)],
            (-1, -2, -3),
            [(1, 2, 3), (1, 2, 3), (0, 0, 0)],
            -3,
        ),
        (
            "07_ whole path starting in the middle",
            [(1, 2, 3), (1, 2, 3), (0, 0, 0)],
            (1, 2, 3),
            [(1, 2, 3), (2, 4, 6), (1, 2, 3)],
            1,
        ),
        (
            "08_ whole path starting in the middle with negative start",
            [(1, 2, 3), (2, 4, 6), (1, 2, 3)],
            (1, 2, 3),
            [(1, 2, 3), (3, 6, 9), (2, 4, 6)],
            -2,
        ),
        # VECTOR INPUT
        (
            "17_ vector + start=0: simple append",
            (0, 0, 0),
            [(1, 2, 3)],
            [(0, 0, 0), (1, 2, 3)],
            "auto",
        ),
        (
            "18_ vector + start in middle: merge",
            [(0, 0, 0), (1, 2, 3)],
            [(1, 2, 3)],
            [(0, 0, 0), (2, 4, 6)],
            1,
        ),
        (
            "19_ vector + start in middle: merge + pad behind",
            [(0, 0, 0), (2, 4, 6)],
            [(-1, -2, -3), (-2, -4, -6)],
            [(0, 0, 0), (1, 2, 3), (0, 0, 0)],
            1,
        ),
        (
            "20_ vector + start before: merge + pad before",
            [(0, 0, 0), (1, 2, 3), (0, 0, 0)],
            [(1, 2, 3), (1, 2, 3)],
            [(1, 2, 3), (1, 2, 3), (1, 2, 3), (0, 0, 0)],
            -4,
        ),
    ],
)
def test_apply_move(description, old_position, displacement, new_position, start):
    """v4 path functionality tests"""
    print(description)
    s = magpy.Sensor(position=old_position)
    apply_move(s, displacement, start=start)
    np.testing.assert_array_equal(s.position, np.array(new_position))


@pytest.mark.parametrize(
    (
        "description",
        "old_position",
        "new_position",
        "old_orientation_rotvec",
        "rotvec_to_apply",
        "new_orientation_rotvec",
        "start",
        "anchor",
    ),
    [
        # SCALAR INPUT
        (
            "01_ with start='auto'",
            (0, 0, 0),
            (0, 0, 0),
            (0, 0, 0),
            (0.1, 0.2, 0.3),
            (0.1, 0.2, 0.3),
            "auto",
            None,
        ),
        (
            "02_ with start=0",
            (0, 0, 0),
            (0, 0, 0),
            (0.1, 0.2, 0.3),
            (0.1, 0.2, 0.3),
            (0.2, 0.4, 0.6),
            0,
            None,
        ),
        (
            "03_ with start=-1",
            (0, 0, 0),
            (0, 0, 0),
            (0.2, 0.4, 0.6),
            (-0.2, -0.4, -0.6),
            (0, 0, 0),
            -1,
            None,
        ),
        (
            "04_  with anchor",
            (0, 0, 0),
            (1, -1, 0),
            (0, 0, 0),
            (0, 0, np.pi / 2),
            (0, 0, np.pi / 2),
            -1,
            (1, 0, 0),
        ),
        (
            "05_  pad behind",
            (1, -1, 0),
            [(1, -1, 0), (2, 0, 0)],
            (0, 0, np.pi / 2),
            (0, 0, np.pi / 2),
            [(0, 0, np.pi / 2), (0, 0, np.pi)],
            1,
            (1, 0, 0),
        ),
        (
            "06_  whole path",
            [(1, -1, 0), (2, 0, 0)],
            [(2, 0, 0), (1, 1, 0)],
            [(0, 0, np.pi / 2), (0, 0, np.pi)],
            (0, 0, np.pi / 2),
            [(0, 0, np.pi), (0, 0, -np.pi / 2)],
            "auto",
            (1, 0, 0),
        ),
        (
            "07_ pad before",
            [(2, 0, 0), (1, 1, 0)],
            [(1, 1, 0), (1, 1, 0), (0, 0, 0)],
            [(0, 0, np.pi), (0, 0, -np.pi / 2)],
            (0, 0, np.pi / 2),
            [(0, 0, -np.pi / 2), (0, 0, -np.pi / 2), (0, 0, 0)],
            -3,
            (1, 0, 0),
        ),
        (
            "08_ whole path starting in the middle",
            [(1, 1, 0), (1, 1, 0), (0, 0, 0)],
            [(1, 1, 0), (0, 0, 0), (1, -1, 0)],
            [(0, 0, -np.pi / 2), (0, 0, -np.pi / 2), (0, 0, 0)],
            (0, 0, np.pi / 2),
            [(0, 0, -np.pi / 2), (0, 0, 0), (0, 0, np.pi / 2)],
            1,
            (1, 0, 0),
        ),
        (
            "09_ whole path starting in the middle without anchor",
            [(1, 1, 0), (0, 0, 0), (1, -1, 0)],
            [(1, 1, 0), (0, 0, 0), (1, -1, 0)],
            [(0, 0, -np.pi / 2), (0, 0, 0), (0, 0, np.pi / 2)],
            ((0, 0, np.pi / 4)),
            [(0, 0, -np.pi / 2), (0, 0, np.pi / 4), (0, 0, 3 * np.pi / 4)],
            1,
            None,
        ),
        # VECTOR INPUT
        (
            "11_ simple append start=auto behavior",
            (0, 0, 0),
            [(0, 0, 0), (1, -1, 0)],
            (0, 0, 0),
            [(0, 0, np.pi / 2)],
            [(0, 0, 0), (0, 0, np.pi / 2)],
            "auto",
            (1, 0, 0),
        ),
        (
            "12_ vector + start=0: simple merge",
            [(0, 0, 0), (1, -1, 0)],
            [(1, -1, 0), (1, -1, 0)],
            [(0, 0, 0), (0, 0, np.pi / 2)],
            [(0, 0, np.pi / 2)],
            [(0, 0, np.pi / 2), (0, 0, np.pi / 2)],
            0,
            (1, 0, 0),
        ),
        (
            "13_ vector + start in middle: merge + pad behind",
            [(1, -1, 0), (1, -1, 0)],
            [(1, -1, 0), (1, 1, 0), (1, 1, 0)],
            [(0, 0, np.pi / 2), (0, 0, np.pi / 2)],
            [(0, 0, np.pi), (0, 0, np.pi)],
            [(0, 0, np.pi / 2), (0, 0, -np.pi / 2), (0, 0, -np.pi / 2)],
            1,
            (1, 0, 0),
        ),
        (
            "14_ vector + start before: merge + pad before",
            [(1, -1, 0), (1, 1, 0), (1, 1, 0)],
            [(1, -1, 0), (1, 1, 0), (1, 1, 0), (1, 1, 0)],
            [(0, 0, np.pi / 2), (0, 0, -np.pi / 2), (0, 0, -np.pi / 2)],
            [(0, 0, 0), (0, 0, np.pi)],
            [
                (0, 0, np.pi / 2),
                (0, 0, -np.pi / 2),
                (0, 0, -np.pi / 2),
                (0, 0, -np.pi / 2),
            ],
            -4,
            (1, 0, 0),
        ),
    ],
)
def test_apply_rotation(
    description,
    old_position,
    new_position,
    old_orientation_rotvec,
    rotvec_to_apply,
    new_orientation_rotvec,
    start,
    anchor,
):
    """v4 path functionality tests"""
    print(description)
    s = magpy.Sensor(
        position=old_position, orientation=R.from_rotvec(old_orientation_rotvec)
    )
    apply_rotation(s, R.from_rotvec(rotvec_to_apply), start=start, anchor=anchor)

    np.testing.assert_allclose(
        s.position, np.array(new_position), rtol=1e-05, atol=1e-08
    )
    np.testing.assert_allclose(
        s.orientation.as_matrix(),
        R.from_rotvec(new_orientation_rotvec).as_matrix(),
        rtol=1e-05,
        atol=1e-08,
    )