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

//! A deliberately trivial second reader.
//!
//! This exists to keep [`WaveReader`] honest.  A trait with exactly one
//! implementation is not an abstraction, it is the first format spelled
//! differently — and you only find that out when the second format arrives
//! six months later and does not fit.  Sixty lines now buys that answer
//! immediately.
//!
//! Format: one header line of comma-separated names, first column is X, all
//! remaining lines numeric.

use std::fs::File;
use std::io::Read;
use std::path::Path;

use wave_core::{Unit, ValueKind};

use crate::{invalid, Variable, WaveReader};

pub struct CsvReader {
    plot_name: String,
    vars: Vec<Variable>,
    n_points: usize,
    values: Vec<f64>,
}

impl CsvReader {
    pub fn open<P: AsRef<Path>>(path: P) -> std::io::Result<CsvReader> {
        let mut text = String::new();
        File::open(path.as_ref())?.read_to_string(&mut text)?;

        let mut lines = text.lines();
        let header = match lines.next() {
            Some(h) => h,
            None => return invalid("empty CSV"),
        };
        let vars: Vec<Variable> = header
            .split(',')
            .enumerate()
            .map(|(i, name)| {
                let name = name.trim();
                Variable {
                    index: i,
                    name: name.to_string(),
                    unit: guess_unit(name, i),
                    grid: None,
                }
            })
            .collect();
        if vars.len() < 2 {
            return invalid("CSV needs an X column and at least one signal");
        }

        let mut values = Vec::new();
        for line in lines {
            if line.trim().is_empty() {
                continue;
            }
            let mut n = 0;
            for f in line.split(',') {
                values.push(f.trim().parse::<f64>().unwrap_or(f64::NAN));
                n += 1;
            }
            if n != vars.len() {
                return invalid("CSV row width does not match header");
            }
        }
        let n_points = values.len() / vars.len();

        Ok(CsvReader {
            plot_name: path
                .as_ref()
                .file_stem()
                .map(|s| s.to_string_lossy().into_owned())
                .unwrap_or_else(|| "csv".into()),
            vars,
            n_points,
            values,
        })
    }
}

fn guess_unit(name: &str, col: usize) -> Unit {
    let l = name.to_ascii_lowercase();
    if col == 0 {
        if l.contains("freq") {
            return Unit::Hertz;
        }
        return Unit::Second;
    }
    if l.starts_with("v(") || l.starts_with("v_") {
        Unit::Volt
    } else if l.starts_with("i(") || l.starts_with("i_") {
        Unit::Ampere
    } else {
        Unit::Unknown
    }
}

impl WaveReader for CsvReader {
    fn plot_name(&self) -> &str {
        &self.plot_name
    }
    fn variables(&self) -> &[Variable] {
        &self.vars
    }
    fn n_points(&self) -> usize {
        self.n_points
    }
    fn kind(&self) -> ValueKind {
        ValueKind::Real
    }

    fn read_rows(&self, first_row: usize, n_rows: usize, out: &mut [f64]) -> std::io::Result<usize> {
        if first_row >= self.n_points {
            return Ok(0);
        }
        let w = self.vars.len();
        let rows = std::cmp::min(n_rows, self.n_points - first_row);
        let start = first_row * w;
        out[..rows * w].copy_from_slice(&self.values[start..start + rows * w]);
        Ok(rows)
    }
}
