"""3 ゾーン空間分析モジュール．

ROI を中心・中間・周辺の 3 同心楕円ゾーンに分割し，各ゾーンの輝度統計を算出する．

References:
    [1] MiniTias-Evaluation SPEC_02 「空間分析」節
        https://github.com/rintoHasegawa/MiniTias-Evaluation
"""

import numpy


def compute_normalized_distance_map(height: int, width: int) -> numpy.ndarray:
    """ROI の各ピクセルについて，中心からの正規化楕円距離マップを算出する．

    正規化楕円距離 $d(x, y)$ を以下の式で定義する [1]:

    $$d(x, y) = \\sqrt{
        \\left(\\frac{x - c_x}{c_x}\\right)^2 + \\left(\\frac{y - c_y}{c_y}\\right)^2
    }$$

    ここで $c_x = W/2$, $c_y = H/2$ は ROI の中心座標．
    楕円距離を用いることで，縦横比が異なる ROI でも等方的な評価が可能となる．

    算出後，値域を $[0, 1]$ に再正規化する（$d \\leftarrow d / d_{\\max}$）．

    Args:
        height: ROI の高さ（ピクセル数）．
        width: ROI の幅（ピクセル数）．

    Returns:
        正規化距離マップ（shape: (height, width), dtype: numpy.float64）．
        値域は [0, 1]．
    """
    cx = width / 2.0
    cy = height / 2.0

    # 各ピクセル座標をグリッドで生成する（ブロードキャストのため列ベクトル・行ベクトル）
    ys = numpy.arange(height, dtype=numpy.float64).reshape(-1, 1)
    xs = numpy.arange(width, dtype=numpy.float64).reshape(1, -1)

    distance_map = numpy.sqrt(((xs - cx) / cx) ** 2 + ((ys - cy) / cy) ** 2)

    # d_max で除算して [0, 1] に正規化する
    d_max = float(numpy.max(distance_map))
    if d_max > 0.0:
        distance_map = distance_map / d_max

    return distance_map


def classify_zones(
    distance_map: numpy.ndarray,
    boundaries: tuple[float, float] = (0.33, 0.66),
) -> numpy.ndarray:
    """正規化距離マップを 3 ゾーンのラベルマップに変換する．

    SPEC_02 の 3 ゾーン分類に従い，以下の閾値でラベルを割り当てる [1]:

    | ゾーン     | ラベル | 距離条件                              |
    |------------|--------|---------------------------------------|
    | Center     | 0      | $d < \\text{boundaries}[0]$           |
    | Middle     | 1      | $\\text{boundaries}[0] \\leq d < \\text{boundaries}[1]$ |
    | Periphery  | 2      | $d \\geq \\text{boundaries}[1]$       |

    Args:
        distance_map: 正規化距離マップ（shape: (H, W), dtype: float）．
        boundaries: ゾーン境界の閾値ペア (lower, upper)．デフォルトは (0.33, 0.66)．

    Returns:
        ゾーンラベルマップ（shape: (H, W), dtype: numpy.int8）．
        値は 0（center）, 1（middle）, 2（periphery）のいずれか．
    """
    lower, upper = boundaries
    zone_map = numpy.full(distance_map.shape, 2, dtype=numpy.int8)
    zone_map[distance_map < lower] = 0
    zone_map[(distance_map >= lower) & (distance_map < upper)] = 1
    return zone_map


def calc_zone_stats(
    luminance: numpy.ndarray,
    zone_map: numpy.ndarray,
) -> dict:
    """各ゾーンの輝度統計と空間分析指標を算出する．

    ゾーン別に平均輝度・標準偏差を算出し，中心/周辺比と勾配量 (%) を導出する [1]:

    $$\\text{C/P Ratio} = \\frac{\\mu_{center}}{\\mu_{periphery}}$$

    $$\\text{Gradient} = \\frac{\\mu_{center} - \\mu_{periphery}}{\\mu_{center}} \\times 100$$

    Args:
        luminance: 輝度画像（shape: (H, W), dtype: float64）．
        zone_map: ゾーンラベルマップ（shape: (H, W), dtype: int8）．
            0=center, 1=middle, 2=periphery．

    Returns:
        空間分析指標を格納した辞書．以下の 8 キーを含む:
        - center_mean: 中心ゾーンの平均輝度
        - center_std: 中心ゾーンの標準偏差
        - middle_mean: 中間ゾーンの平均輝度
        - middle_std: 中間ゾーンの標準偏差
        - periphery_mean: 周辺ゾーンの平均輝度
        - periphery_std: 周辺ゾーンの標準偏差
        - center_periphery_ratio: 中心/周辺比 (μ_center / μ_periphery)
        - gradient_magnitude: 中心-周辺勾配量 (%)

    Raises:
        ValueError: 周辺ゾーンの平均輝度が 0 の場合（ROI 設定の確認を促す）．
    """
    center_pixels = luminance[zone_map == 0]
    middle_pixels = luminance[zone_map == 1]
    periphery_pixels = luminance[zone_map == 2]

    center_mean = float(numpy.mean(center_pixels))
    center_std = float(numpy.std(center_pixels))
    middle_mean = float(numpy.mean(middle_pixels))
    middle_std = float(numpy.std(middle_pixels))
    periphery_mean = float(numpy.mean(periphery_pixels))
    periphery_std = float(numpy.std(periphery_pixels))

    # 周辺輝度が 0 の場合は中心/周辺比が計算不能（ROI 設定の誤りを示す）
    if periphery_mean == 0.0:
        raise ValueError(
            "周辺ゾーンの平均輝度が 0 のため中心/周辺比を計算できません．"
            "ROI の設定を確認してください．"
        )

    center_periphery_ratio = center_mean / periphery_mean
    gradient_magnitude = (center_mean - periphery_mean) / center_mean * 100.0

    return {
        "center_mean": center_mean,
        "center_std": center_std,
        "middle_mean": middle_mean,
        "middle_std": middle_std,
        "periphery_mean": periphery_mean,
        "periphery_std": periphery_std,
        "center_periphery_ratio": center_periphery_ratio,
        "gradient_magnitude": gradient_magnitude,
    }
