"""
ODE function for maglev quadrotor dynamics
Ported from quadOdeFunctionHF.m
"""

import numpy as np
from utils import cross_product_equivalent, fmag2


def quad_ode_function_hf(t, X, eaVec, distVec, P):
    """
    Ordinary differential equation function that models quadrotor dynamics 
    -- high-fidelity version. For use with scipy's ODE solvers (e.g., solve_ivp).
    
    Parameters
    ----------
    t : float
        Scalar time input, as required by ODE solver format
    
    X : ndarray, shape (22,)
        Quad state, arranged as:
        X = [rI, vI, RBI(flattened), omegaB, currents]
        
        rI = position vector in I in meters (3 elements)
        vI = velocity vector wrt I and in I, in meters/sec (3 elements)
        RBI = attitude matrix from I to B frame, flattened (9 elements)
        omegaB = angular rate vector of body wrt I, expressed in B, rad/sec (3 elements)
        currents = vector of yoke currents, in amperes (4 elements)
    
    eaVec : ndarray, shape (4,)
        4-element vector of voltages applied to yokes, in volts
    
    distVec : ndarray, shape (3,)
        3-element vector of constant disturbance forces acting on quad's
        center of mass, expressed in Newtons in I
    
    P : dict
        Structure with the following elements:
        - quadParams : QuadParams object
        - constants : Constants object
    
    Returns
    -------
    Xdot : ndarray, shape (22,)
        Time derivative of the input vector X
    """
    # Use individual yoke resistances and inductances
    yokeR = P['quadParams'].yokeR_individual  # 4-element array in ohms
    yokeL = P['quadParams'].yokeL_individual  # 4-element array in henries
    
    # Extract state variables
    currents = X[18:22]  # indices 19:22 in MATLAB (1-indexed) = 18:22 in Python
    currentsdot = np.zeros(4)
    
    R = X[6:15].reshape(3, 3)  # indices 7:15 in MATLAB = 6:15 in Python
    
    w = X[15:18]  # indices 16:18 in MATLAB = 15:18 in Python
    wx = cross_product_equivalent(w)
    rdot = X[3:6]  # indices 4:6 in MATLAB = 3:6 in Python
    
    z = X[2]  # index 3 in MATLAB = 2 in Python (zI of cg)
    
    rl = P['quadParams'].sensor_loc
    gaps = np.abs(z) - np.array([0, 0, 1]) @ R.T @ rl
    gaps = gaps.flatten()
    
    # Calculate magnetic forces
    Fm = fmag2(currents, gaps)
    
    # Handle collision with track
    if np.any(Fm == -1):
        rdot = rdot.copy()
        rdot[2] = 0  # rdot(end) in MATLAB
        Fm = np.zeros(4)
    
    sumF = np.sum(Fm)
    
    # Calculate linear acceleration
    vdot = (np.array([0, 0, -P['quadParams'].m * P['constants'].g]) + 
            np.array([0, 0, sumF]) + 
            distVec) / P['quadParams'].m
    
    # Calculate rotation rate
    Rdot = -wx @ R
    
    # Calculate torques on body
    Nb = np.zeros(3)
    for i in range(4):  # loop through each yoke
        # Voltage-motor modeling using individual R and L for each yoke
        currentsdot[i] = (eaVec[i] - currents[i] * yokeR[i]) / yokeL[i]
        
        NiB = np.zeros(3)  # since yokes can't cause moment by themselves
        FiB = np.array([0, 0, Fm[i]])
        
        # Accumulate torque (rotate FiB to inertial frame since fmag is always towards track)
        Nb = Nb + (NiB + cross_product_equivalent(P['quadParams'].rotor_loc[:, i]) @ R.T @ FiB)
    
    # Calculate angular acceleration
    wdot = P['quadParams'].invJq @ (Nb - wx @ P['quadParams'].Jq @ w)
    
    # Enforce constraint if pod is against track
    if np.any(Fm == -1):
        vdot[2] = 0
    
    # Assemble state derivative
    Xdot = np.concatenate([
        rdot,              # 3 elements
        vdot,              # 3 elements
        Rdot.flatten(),    # 9 elements
        wdot,              # 3 elements
        currentsdot        # 4 elements
    ])
    
    return Xdot
