# CelerisLab/common/preprocess.py
"""Host-side geometric helpers for immersed-boundary preprocessing.

Responsibilities:
    Circle–ray intersection along fluid-to-neighbour links and simple
    discrete sensor-area counting used when building IBM masks and probes.
"""

import math
import numpy as np


def find_circle_intersection(x, y, x_neb, y_neb, xc, yc, r0):
    """Return (px, py) of the closest intersection along fluid→neighbour ray,
    or None if no valid root in [0, 1]."""
    dx, dy = x_neb - x, y_neb - y
    a = dx ** 2 + dy ** 2
    b = 2 * (dx * (x - xc) + dy * (y - yc))
    c = (x - xc) ** 2 + (y - yc) ** 2 - r0 ** 2
    det = b ** 2 - 4 * a * c

    if det < 0:
        return None

    t1 = (-b - math.sqrt(det)) / (2 * a)
    t2 = (-b + math.sqrt(det)) / (2 * a)

    # Pick the smallest valid t (closest to fluid node).
    for t in (t1, t2):
        if 0 <= t <= 1:
            return x + t * dx, y + t * dy
    return None


def find_sphere_ray_segment(px, py, pz, qx, qy, qz, cx, cy, cz, r0):
    """Closest intersection of segment P→Q with sphere (center C, radius r0).

    Returns:
        (ix, iy, iz) on the segment with parameter in [0, 1], or None.
    """
    dx, dy, dz = qx - px, qy - py, qz - pz
    a = dx * dx + dy * dy + dz * dz
    if a == 0.0:
        return None
    b = 2.0 * (dx * (px - cx) + dy * (py - cy) + dz * (pz - cz))
    c = (px - cx) ** 2 + (py - cy) ** 2 + (pz - cz) ** 2 - r0 ** 2
    det = b * b - 4.0 * a * c
    if det < 0.0:
        return None
    sqrt_det = math.sqrt(det)
    t1 = (-b - sqrt_det) / (2.0 * a)
    t2 = (-b + sqrt_det) / (2.0 * a)
    for t in (t1, t2):
        if 0.0 <= t <= 1.0:
            return px + t * dx, py + t * dy, pz + t * dz
    return None


def find_sensor_area(radius):
    area = 0
    for i in range(int(np.floor(-radius)), int(np.ceil(radius))):
        for j in range(int(np.floor(-radius)), int(np.ceil(radius))):
            if i ** 2 + j ** 2 <= radius ** 2:
                area += 1
    return area
    