// Copyright (C) 2026 alto555
// SPDX-License-Identifier: GPL-2.0-only

//! Format readers, behind one trait.
//!
//! The ngspice raw reader is the first implementation, not the only intended
//! one.  The CSV reader exists specifically to keep this seam honest: a
//! second implementation written early is the only proof that the trait is a
//! real abstraction rather than the raw format spelled differently.

use std::io;
use wave_core::{Unit, ValueKind, XScale};

pub mod csv;
pub mod raw;

pub use csv::CsvReader;
pub use raw::RawReader;

/// One column of a waveform file.
#[derive(Clone, Debug)]
pub struct Variable {
    pub index: usize,
    pub name: String,
    pub unit: Unit,
    /// ngspice `grid=<GRIDTYPE>` annotation, when present.  Only meaningful
    /// on the X variable.
    pub grid: Option<i32>,
}

/// Random-access source of sample rows.
///
/// Row-major ("point-major") is the native layout of every SPICE raw file:
/// all variables for t0, then all for t1.  The trait exposes that honestly
/// rather than pretending columns are cheap, because the amplification factor
/// is what the indexer has to be designed around.
///
/// Implementations must be safe to call concurrently from many threads — use
/// positional reads, never a shared file cursor.  The parallel indexer
/// depends on it.
pub trait WaveReader: Send + Sync {
    fn plot_name(&self) -> &str;
    fn variables(&self) -> &[Variable];
    fn n_points(&self) -> usize;
    fn kind(&self) -> ValueKind;

    /// X axis scaling as declared by the file.
    fn x_scale(&self) -> XScale {
        match self.variables().first().and_then(|v| v.grid) {
            // GRID_LOGLOG = 2, GRID_XLOG = 3 (ngspice grid.h GRIDTYPE)
            Some(2) | Some(3) => XScale::Log,
            _ => XScale::Linear,
        }
    }

    /// Components stored per value: 1 for real data, 2 for complex (`.ac`).
    fn components(&self) -> usize {
        match self.kind() {
            ValueKind::Complex => 2,
            _ => 1,
        }
    }

    fn n_vars(&self) -> usize {
        self.variables().len()
    }

    /// Values per row, counting complex components.
    fn row_len(&self) -> usize {
        self.n_vars() * self.components()
    }

    /// Fill `out` with `n_rows` consecutive rows starting at `first_row`.
    ///
    /// `out.len()` must be at least `n_rows * row_len()`.  Returns the number
    /// of rows actually read (short only at end of file).
    fn read_rows(&self, first_row: usize, n_rows: usize, out: &mut [f64]) -> io::Result<usize>;
}

pub(crate) fn invalid<T>(msg: impl Into<String>) -> io::Result<T> {
    Err(io::Error::new(io::ErrorKind::InvalidData, msg.into()))
}
