#![allow(clippy::pedantic, clippy::nursery, clippy::cargo)]

use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;

use rshogi::board::{self, Move32List};
use rshogi::movegen::{
    generate_legal_all_move32, generate_moves_move32, EvasionsAll, NonEvasionsAll,
};

#[derive(Debug, Clone)]
struct PositionInput {
    label: String,
    sfen: String,
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct MoveBreakdown {
    total: usize,
    normal: usize,
    promotion: usize,
    drop: usize,
}

impl MoveBreakdown {
    fn from_move32_list(list: &Move32List) -> Self {
        let mut out = Self::default();
        for mv in list.iter() {
            out.total += 1;
            if mv.is_drop() {
                out.drop += 1;
            } else if mv.is_promotion() {
                out.promotion += 1;
            } else {
                out.normal += 1;
            }
        }
        out
    }

    fn saturating_sub(self, rhs: Self) -> Self {
        Self {
            total: self.total.saturating_sub(rhs.total),
            normal: self.normal.saturating_sub(rhs.normal),
            promotion: self.promotion.saturating_sub(rhs.promotion),
            drop: self.drop.saturating_sub(rhs.drop),
        }
    }

    fn add_assign(&mut self, rhs: Self) {
        self.total += rhs.total;
        self.normal += rhs.normal;
        self.promotion += rhs.promotion;
        self.drop += rhs.drop;
    }
}

fn load_positions(path: &PathBuf) -> Result<Vec<PositionInput>, Box<dyn std::error::Error>> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    let mut positions = Vec::new();

    for line in reader.lines() {
        let line = line?;
        let trimmed = line.trim();
        if trimmed.is_empty() || trimmed.starts_with('#') {
            continue;
        }
        let Some((label, sfen)) = trimmed.split_once(',') else {
            continue;
        };
        let label = label.trim();
        let sfen = sfen.trim();
        if !label.is_empty() && !sfen.is_empty() {
            positions.push(PositionInput { label: label.to_string(), sfen: sfen.to_string() });
        }
    }

    Ok(positions)
}

fn parse_args() -> Result<PathBuf, Box<dyn std::error::Error>> {
    let mut positions_path: Option<PathBuf> = None;

    let mut args = env::args().skip(1);
    while let Some(arg) = args.next() {
        if arg.as_str() == "--positions" {
            let value = args.next().ok_or("--positions requires a value")?;
            positions_path = Some(PathBuf::from(value));
        }
    }

    Ok(positions_path.unwrap_or_else(|| PathBuf::from("data/bench/diverse_positions.csv")))
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let positions_path = parse_args()?;
    let positions = load_positions(&positions_path)?;
    if positions.is_empty() {
        return Err("no positions loaded from CSV".into());
    }

    println!("engine,rshogi");
    println!(
        "label,in_check,pseudo_total,pseudo_normal,pseudo_promotion,pseudo_drop,legal_total,legal_normal,legal_promotion,legal_drop,filtered_total,filtered_normal,filtered_promotion,filtered_drop"
    );

    let mut pseudo_sum = MoveBreakdown::default();
    let mut legal_sum = MoveBreakdown::default();
    let mut filtered_sum = MoveBreakdown::default();
    let mut in_check_count = 0usize;

    for pos in positions {
        let position =
            board::position_from_sfen(&pos.sfen).map_err(|e| format!("invalid sfen: {e}"))?;
        let in_check = !position.checkers().is_empty();
        if in_check {
            in_check_count += 1;
        }

        let mut pseudo = Move32List::new();
        if in_check {
            generate_moves_move32::<EvasionsAll>(&position, &mut pseudo);
        } else {
            generate_moves_move32::<NonEvasionsAll>(&position, &mut pseudo);
        }
        let pseudo_counts = MoveBreakdown::from_move32_list(&pseudo);

        let mut legal = Move32List::new();
        generate_legal_all_move32(&position, &mut legal);
        let legal_counts = MoveBreakdown::from_move32_list(&legal);

        let filtered_counts = pseudo_counts.saturating_sub(legal_counts);

        pseudo_sum.add_assign(pseudo_counts);
        legal_sum.add_assign(legal_counts);
        filtered_sum.add_assign(filtered_counts);

        println!(
            "{},{},{},{},{},{},{},{},{},{},{},{},{},{}",
            pos.label,
            if in_check { 1 } else { 0 },
            pseudo_counts.total,
            pseudo_counts.normal,
            pseudo_counts.promotion,
            pseudo_counts.drop,
            legal_counts.total,
            legal_counts.normal,
            legal_counts.promotion,
            legal_counts.drop,
            filtered_counts.total,
            filtered_counts.normal,
            filtered_counts.promotion,
            filtered_counts.drop
        );
    }

    println!(
        "total,{},{},{},{},{},{},{},{},{},{},{},{},{}",
        in_check_count,
        pseudo_sum.total,
        pseudo_sum.normal,
        pseudo_sum.promotion,
        pseudo_sum.drop,
        legal_sum.total,
        legal_sum.normal,
        legal_sum.promotion,
        legal_sum.drop,
        filtered_sum.total,
        filtered_sum.normal,
        filtered_sum.promotion,
        filtered_sum.drop
    );

    Ok(())
}
