"""
Geometric calculation functions for tilt sensors.

Includes axis transformations, rotations, and quaternion operations.
"""

import numpy as np
import logging
from typing import Tuple

logger = logging.getLogger(__name__)


def asse_a(
    ax: np.ndarray,
    angle: float,
    spe_tl: np.ndarray,
    i: int,
    j: int
) -> Tuple[float, float, float]:
    """
    Calculate axis A displacement components.

    Converts MATLAB ASSEa.m function.

    Args:
        ax: Acceleration/inclination data for axis X
        angle: Installation angle in degrees
        spe_tl: Sensor spacing/length array
        i: Sensor index
        j: Time index

    Returns:
        Tuple of (North component, East component, Vertical component)
    """
    # Convert angle to radians
    angle_rad = angle * 2 * np.pi / 360

    if ax[i, j] >= 0:
        na = spe_tl[i] * ax[i, j] * np.cos(angle_rad)
        ea = -spe_tl[i] * ax[i, j] * np.sin(angle_rad)
    else:
        na = -spe_tl[i] * ax[i, j] * np.cos(angle_rad)
        ea = spe_tl[i] * ax[i, j] * np.sin(angle_rad)

    # Calculate cosine of inclination angle
    cos_beta = np.sqrt(1 - ax[i, j]**2)
    z = spe_tl[i] * cos_beta
    za = spe_tl[i] - z  # Lowering is POSITIVE

    return na, ea, za


def asse_a_hr(
    ax: np.ndarray,
    angle: float,
    spe_tl: np.ndarray,
    i: int,
    j: int
) -> Tuple[float, float, float]:
    """
    Calculate axis A displacement components for high-resolution sensors.

    Converts MATLAB ASSEa_HR.m function.

    Args:
        ax: Angle data for axis X (in degrees)
        angle: Installation angle in degrees
        spe_tl: Sensor spacing/length array
        i: Sensor index
        j: Time index

    Returns:
        Tuple of (North component, East component, Vertical component)
    """
    # Convert angles to radians
    angle_rad = angle * np.pi / 180
    ax_rad = ax[i, j] * np.pi / 180

    # Calculate displacement components
    na = spe_tl[i] * np.sin(ax_rad) * np.cos(angle_rad)
    ea = -spe_tl[i] * np.sin(ax_rad) * np.sin(angle_rad)

    # Vertical component
    za = spe_tl[i] * (1 - np.cos(ax_rad))

    return na, ea, za


def asse_b(
    ay: np.ndarray,
    angle: float,
    spe_tl: np.ndarray,
    i: int,
    j: int
) -> Tuple[float, float, float]:
    """
    Calculate axis B displacement components.

    Converts MATLAB ASSEb.m function.

    Args:
        ay: Acceleration/inclination data for axis Y
        angle: Installation angle in degrees
        spe_tl: Sensor spacing/length array
        i: Sensor index
        j: Time index

    Returns:
        Tuple of (North component, East component, Vertical component)
    """
    # Convert angle to radians
    angle_rad = angle * 2 * np.pi / 360

    if ay[i, j] >= 0:
        nb = -spe_tl[i] * ay[i, j] * np.sin(angle_rad)
        eb = -spe_tl[i] * ay[i, j] * np.cos(angle_rad)
    else:
        nb = spe_tl[i] * ay[i, j] * np.sin(angle_rad)
        eb = spe_tl[i] * ay[i, j] * np.cos(angle_rad)

    # Calculate cosine of inclination angle
    cos_beta = np.sqrt(1 - ay[i, j]**2)
    z = spe_tl[i] * cos_beta
    zb = spe_tl[i] - z  # Lowering is POSITIVE

    return nb, eb, zb


def asse_b_hr(
    ay: np.ndarray,
    angle: float,
    spe_tl: np.ndarray,
    i: int,
    j: int
) -> Tuple[float, float, float]:
    """
    Calculate axis B displacement components for high-resolution sensors.

    Converts MATLAB ASSEb_HR.m function.

    Args:
        ay: Angle data for axis Y (in degrees)
        angle: Installation angle in degrees
        spe_tl: Sensor spacing/length array
        i: Sensor index
        j: Time index

    Returns:
        Tuple of (North component, East component, Vertical component)
    """
    # Convert angles to radians
    angle_rad = angle * np.pi / 180
    ay_rad = ay[i, j] * np.pi / 180

    # Calculate displacement components
    nb = -spe_tl[i] * np.sin(ay_rad) * np.sin(angle_rad)
    eb = -spe_tl[i] * np.sin(ay_rad) * np.cos(angle_rad)

    # Vertical component
    zb = spe_tl[i] * (1 - np.cos(ay_rad))

    return nb, eb, zb


def arot(
    ax: np.ndarray,
    ay: np.ndarray,
    angle: float,
    spe_tl: np.ndarray,
    i: int,
    j: int
) -> Tuple[float, float, float]:
    """
    Calculate combined rotation displacement.

    Converts MATLAB arot.m function.

    Args:
        ax: Acceleration/inclination data for axis X
        ay: Acceleration/inclination data for axis Y
        angle: Installation angle in degrees
        spe_tl: Sensor spacing/length array
        i: Sensor index
        j: Time index

    Returns:
        Tuple of (North displacement, East displacement, Vertical displacement)
    """
    # Calculate components from both axes
    na, ea, za = asse_a(ax, angle, spe_tl, i, j)
    nb, eb, zb = asse_b(ay, angle, spe_tl, i, j)

    # Combine components
    n_total = na + nb
    e_total = ea + eb
    z_total = za + zb

    return n_total, e_total, z_total


def arot_hr(
    ax: np.ndarray,
    ay: np.ndarray,
    angle: float,
    spe_tl: np.ndarray,
    i: int,
    j: int
) -> Tuple[float, float, float]:
    """
    Calculate combined rotation displacement for high-resolution sensors.

    Converts MATLAB arotHR.m function.

    Args:
        ax: Angle data for axis X (in degrees)
        ay: Angle data for axis Y (in degrees)
        angle: Installation angle in degrees
        spe_tl: Sensor spacing/length array
        i: Sensor index
        j: Time index

    Returns:
        Tuple of (North displacement, East displacement, Vertical displacement)
    """
    # Calculate components from both axes
    na, ea, za = asse_a_hr(ax, angle, spe_tl, i, j)
    nb, eb, zb = asse_b_hr(ay, angle, spe_tl, i, j)

    # Combine components
    n_total = na + nb
    e_total = ea + eb
    z_total = za + zb

    return n_total, e_total, z_total


# Quaternion operations
def q_mult2(q1: np.ndarray, q2: np.ndarray) -> np.ndarray:
    """
    Multiply two quaternions.

    Converts MATLAB q_mult2.m function.

    Args:
        q1: First quaternion [w, x, y, z]
        q2: Second quaternion [w, x, y, z]

    Returns:
        Product quaternion
    """
    w1, x1, y1, z1 = q1
    w2, x2, y2, z2 = q2

    w = w1*w2 - x1*x2 - y1*y2 - z1*z2
    x = w1*x2 + x1*w2 + y1*z2 - z1*y2
    y = w1*y2 - x1*z2 + y1*w2 + z1*x2
    z = w1*z2 + x1*y2 - y1*x2 + z1*w2

    return np.array([w, x, y, z])


def rotate_v_by_q(v: np.ndarray, q: np.ndarray) -> np.ndarray:
    """
    Rotate a vector by a quaternion.

    Converts MATLAB rotate_v_by_q.m function.

    Args:
        v: Vector to rotate [x, y, z]
        q: Quaternion [w, x, y, z]

    Returns:
        Rotated vector
    """
    # Convert vector to quaternion form [0, x, y, z]
    v_quat = np.array([0, v[0], v[1], v[2]])

    # Calculate q * v * q_conjugate
    q_conj = np.array([q[0], -q[1], -q[2], -q[3]])

    temp = q_mult2(q, v_quat)
    result = q_mult2(temp, q_conj)

    # Return vector part
    return result[1:]


def fqa(ax: float, ay: float) -> np.ndarray:
    """
    Calculate quaternion from acceleration angles.

    Converts MATLAB fqa.m function.

    Args:
        ax: Acceleration angle X
        ay: Acceleration angle Y

    Returns:
        Quaternion representation
    """
    # Calculate rotation angles
    theta_x = np.arcsin(ax)
    theta_y = np.arcsin(ay)

    # Build quaternion
    qx = np.array([
        np.cos(theta_x/2),
        np.sin(theta_x/2),
        0,
        0
    ])

    qy = np.array([
        np.cos(theta_y/2),
        0,
        np.sin(theta_y/2),
        0
    ])

    # Combine rotations
    q = q_mult2(qx, qy)

    return q
