"""
Star calculation module for ATD sensors.

Implements geometric calculations for determining positions
based on sensor network configurations (catena/chain calculations).
"""

import numpy as np
import logging
from typing import Tuple, List
import pandas as pd
from pathlib import Path

logger = logging.getLogger(__name__)


def load_star_configuration(
    control_unit_id: str,
    chain: str
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """
    Load star calculation configuration from Excel file.

    Converts MATLAB star.m function.

    Args:
        control_unit_id: Control unit identifier
        chain: Chain identifier

    Returns:
        Tuple of:
        - verso: Direction array (1=clockwise, -1=counterclockwise, 0=both)
        - segmenti: Segment definition array (which nodes to calculate between)
        - peso: Weight array for averaging clockwise/counterclockwise calculations
        - pos_ini_end: Initial and final position (for closed chain, they coincide)
        - punti_noti: Known points
        - antiorario: Counterclockwise calculation array
    """
    config_file = Path(f"{control_unit_id}-{chain}.xlsx")

    if not config_file.exists():
        logger.warning(f"Configuration file {config_file} not found")
        # Return empty arrays
        return (np.array([]), np.array([]), np.array([]),
                np.array([]), np.array([]), np.array([]))

    try:
        # Read sheets from Excel file
        verso = pd.read_excel(config_file, sheet_name=0, header=None).values
        segmenti = pd.read_excel(config_file, sheet_name=1, header=None).values
        peso = pd.read_excel(config_file, sheet_name=2, header=None).values
        pos_ini_end = pd.read_excel(config_file, sheet_name=3, header=None).values
        punti_noti = pd.read_excel(config_file, sheet_name=4, header=None).values
        antiorario = pd.read_excel(config_file, sheet_name=5, header=None).values

        logger.info("Star configuration loaded successfully")

        return verso, segmenti, peso, pos_ini_end, punti_noti, antiorario

    except Exception as e:
        logger.error(f"Error loading star configuration: {e}")
        return (np.array([]), np.array([]), np.array([]),
                np.array([]), np.array([]), np.array([]))


def calculate_star_positions(
    displacement_n: np.ndarray,
    displacement_e: np.ndarray,
    displacement_z: np.ndarray,
    verso: np.ndarray,
    segmenti: np.ndarray,
    peso: np.ndarray,
    pos_ini_end: np.ndarray,
    punti_noti: np.ndarray,
    antiorario: np.ndarray
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    """
    Calculate node positions using star method.

    Implements geometric positioning based on displacement measurements
    and network configuration.

    Args:
        displacement_n: North displacements array (timestamps x nodes)
        displacement_e: East displacements array
        displacement_z: Vertical displacements array
        verso: Direction array
        segmenti: Segment definitions
        peso: Weight array
        pos_ini_end: Initial/final positions
        punti_noti: Known points
        antiorario: Counterclockwise array

    Returns:
        Tuple of (north_positions, east_positions, vertical_positions)
    """
    logger.info("Calculating star positions")

    n_timestamps = displacement_n.shape[0]
    n_nodes = displacement_n.shape[1]

    # Initialize position arrays
    pos_n = np.zeros((n_timestamps, n_nodes + 1))
    pos_e = np.zeros((n_timestamps, n_nodes + 1))
    pos_z = np.zeros((n_timestamps, n_nodes + 1))

    # Set initial positions
    if len(pos_ini_end) > 0:
        pos_n[:, 0] = pos_ini_end[0, 0]
        pos_e[:, 0] = pos_ini_end[1, 0]
        pos_z[:, 0] = pos_ini_end[2, 0]

    # Calculate positions for each timestamp
    for t in range(n_timestamps):
        # Iterate through segments
        for seg_idx in range(segmenti.shape[0]):
            seg = segmenti[seg_idx]
            direction = verso[seg_idx]

            # Get nodes in segment
            node_start = int(seg[0])
            node_end = int(seg[1])

            # Calculate position based on direction
            if direction == 1:  # Clockwise
                # Accumulate displacements
                for node in range(node_start, node_end):
                    pos_n[t, node + 1] = pos_n[t, node] + displacement_n[t, node]
                    pos_e[t, node + 1] = pos_e[t, node] + displacement_e[t, node]
                    pos_z[t, node + 1] = pos_z[t, node] + displacement_z[t, node]

            elif direction == -1:  # Counterclockwise
                # Accumulate in reverse
                for node in range(node_end - 1, node_start - 1, -1):
                    pos_n[t, node] = pos_n[t, node + 1] - displacement_n[t, node]
                    pos_e[t, node] = pos_e[t, node + 1] - displacement_e[t, node]
                    pos_z[t, node] = pos_z[t, node + 1] - displacement_z[t, node]

            elif direction == 0:  # Both directions - average
                # Calculate both ways and average with weights
                w1, w2 = peso[seg_idx, 0], peso[seg_idx, 1]

                # Clockwise calculation
                pos_n_cw = np.zeros(node_end - node_start + 1)
                pos_e_cw = np.zeros(node_end - node_start + 1)
                pos_z_cw = np.zeros(node_end - node_start + 1)

                pos_n_cw[0] = pos_n[t, node_start]
                pos_e_cw[0] = pos_e[t, node_start]
                pos_z_cw[0] = pos_z[t, node_start]

                for i, node in enumerate(range(node_start, node_end)):
                    pos_n_cw[i + 1] = pos_n_cw[i] + displacement_n[t, node]
                    pos_e_cw[i + 1] = pos_e_cw[i] + displacement_e[t, node]
                    pos_z_cw[i + 1] = pos_z_cw[i] + displacement_z[t, node]

                # Counterclockwise calculation
                pos_n_ccw = np.zeros(node_end - node_start + 1)
                pos_e_ccw = np.zeros(node_end - node_start + 1)
                pos_z_ccw = np.zeros(node_end - node_start + 1)

                pos_n_ccw[-1] = pos_n[t, node_end]
                pos_e_ccw[-1] = pos_e[t, node_end]
                pos_z_ccw[-1] = pos_z[t, node_end]

                for i, node in enumerate(range(node_end - 1, node_start - 1, -1)):
                    idx = node_end - node - 1
                    pos_n_ccw[idx] = pos_n_ccw[idx + 1] - displacement_n[t, node]
                    pos_e_ccw[idx] = pos_e_ccw[idx + 1] - displacement_e[t, node]
                    pos_z_ccw[idx] = pos_z_ccw[idx + 1] - displacement_z[t, node]

                # Weighted average
                for i, node in enumerate(range(node_start, node_end + 1)):
                    pos_n[t, node] = w1 * pos_n_cw[i] + w2 * pos_n_ccw[i]
                    pos_e[t, node] = w1 * pos_e_cw[i] + w2 * pos_e_ccw[i]
                    pos_z[t, node] = w1 * pos_z_cw[i] + w2 * pos_z_ccw[i]

    logger.info("Star position calculation completed")

    return pos_n, pos_e, pos_z
