"""
dataset.py — RadioMapSeer loader for radio coverage prediction.

Input  (2 channels): city building map + transmitter location
Target (1 channel) : radio coverage / path-loss map ("gain")

Expected layout under data_root:
    png/buildings_complete/{map}.png       buildings vs background
    png/antennas/{map}_{tx}.png            transmitter marker
    gain/DPM/{map}_{tx}.png                coverage heatmap (target)

Naming: map id 0..699, tx id 0..79  ->  "{map}_{tx}.png"
"""

from pathlib import Path

import numpy as np
import torch
from torch.utils.data import Dataset
from PIL import Image

# dataset.py lives in src/, so the project's data/ folder is one level up.
DEFAULT_DATA_ROOT = Path(r"D:\dataset\RadioMapSeer")


def _load_gray(path: Path, img_size: int | None = None) -> np.ndarray:
    """Load a PNG as float32 in [0, 1]; optionally resize to img_size."""
    img = Image.open(path).convert("L")
    if img_size is not None and img.size != (img_size, img_size):
        img = img.resize((img_size, img_size), Image.BILINEAR)
    return np.asarray(img, dtype=np.float32) / 255.0


class RadioMapSeerDataset(Dataset):
    """Returns (input[2,H,W], target[1,H,W]) tensors in [0, 1]."""

    SPLITS = {
        "train": range(0, 500),
        "val":   range(500, 600),
        "test":  range(600, 700),
    }

    def __init__(self, data_root=DEFAULT_DATA_ROOT, split="train",
                 sim="DPM", img_size=256, max_samples=None, add_distance=False):
        self.data_root = Path(data_root)
        self.img_size = img_size
        self.add_distance = add_distance      # v2: distance-to-Tx channel
        self._build_cache = {}                # cache shared building maps

        self.gain_dir  = self.data_root / "gain" / sim
        self.build_dir = self.data_root / "png" / "buildings_complete"
        self.ant_dir   = self.data_root / "png" / "antennas"

        for d in (self.gain_dir, self.build_dir, self.ant_dir):
            if not d.is_dir():
                raise FileNotFoundError(f"Expected folder not found: {d}")

        if split not in self.SPLITS:
            raise ValueError(f"split must be train/val/test, got '{split}'")
        allowed = set(self.SPLITS[split])

        # Build the sample index by scanning the gain folder for "{map}_{tx}.png".
        self.samples = []
        for f in sorted(self.gain_dir.glob("*.png")):
            try:
                map_id, tx_id = (int(x) for x in f.stem.split("_"))
            except ValueError:
                continue
            if map_id in allowed:
                self.samples.append((map_id, tx_id))

        if not self.samples:
            raise RuntimeError(f"No samples found in {self.gain_dir} — check naming.")

        if max_samples is not None:
            self.samples = self.samples[:max_samples]

    def __len__(self):
        return len(self.samples)

    def __getitem__(self, idx):
        map_id, tx_id = self.samples[idx]

        # buildings are shared across a map's 80 transmitters -> cache to cut I/O
        buildings = self._build_cache.get(map_id)
        if buildings is None:
            buildings = _load_gray(self.build_dir / f"{map_id}.png", self.img_size)
            self._build_cache[map_id] = buildings

        antenna = _load_gray(self.ant_dir  / f"{map_id}_{tx_id}.png", self.img_size)
        gain    = _load_gray(self.gain_dir / f"{map_id}_{tx_id}.png", self.img_size)

        channels = [buildings, antenna]
        if self.add_distance:                              # v2 third channel
            H = W = self.img_size
            r, c = np.unravel_index(int(np.argmax(antenna)), antenna.shape)
            yy, xx = np.mgrid[0:H, 0:W]
            dist = np.sqrt((yy - r) ** 2 + (xx - c) ** 2).astype(np.float32)
            dist /= (2 ** 0.5) * H                          # normalize to ~[0, 1]
            channels.append(dist)

        x = np.stack(channels, axis=0)
        y = gain[None, :, :]
        return torch.from_numpy(x), torch.from_numpy(y)


if __name__ == "__main__":
    # Self-test: confirms paths, naming, shapes, and value ranges.
    print("Data root:", DEFAULT_DATA_ROOT)
    ds = RadioMapSeerDataset(split="train", img_size=256)
    print(f"Train samples: {len(ds)}")

    x, y = ds[0]
    print("Input shape :", tuple(x.shape), "| min/max:", float(x.min()), float(x.max()))
    print("Target shape:", tuple(y.shape), "| min/max:", float(y.min()), float(y.max()))

    for s in ("val", "test"):
        print(f"{s} samples:", len(RadioMapSeerDataset(split=s, img_size=256)))