use std::fs::File;
use std::os::unix::fs::symlink;
use bevy::tasks::ParallelSlice;
use crate::system::spaces::square_grid::{Grid2D, Grid3D};
use itertools::{Itertools, MinMaxResult};
use clap::Parser;
use crate::BoxCountCli;
use crate::cli::cli::OutputFormat;
use crate::system::{GriddedPosition, Position};
use crate::system::model::HistoryLine;
use crate::tools::read;

fn bb(data: &Vec<Grid2D>) -> ((i32, i32), (i32, i32)) {
    let x = data
        .iter().minmax_by(|a, b| a.x.cmp(&b.x));

    let y = data
        .iter().minmax_by(|a, b| a.x.cmp(&b.x));

    match (x, y) {
        (MinMaxResult::MinMax(min_x, max_x), MinMaxResult::MinMax(min_y, max_y)) => {
            ((min_x.x, min_y.y), (max_x.x, max_y.y))
        },

        _ => panic!("Cannot determine bounding box")
    }
}

fn box_count_2d(data: &Vec<Grid2D>, box_number: u32) -> usize {
    let n = data.len();
    let ((x_min, x_max), (y_min, y_max)) = bb(data);

    let x_range = (x_max - x_min) as f64;
    let y_range = (y_max - y_min) as f64;

    let w: f64 = x_range / (box_number as f64);

    data.iter()
        .map(|Grid2D { x, y }| [((x - x_min) as f64 / w) as u32, ((y - y_min) as f64 / w) as u32])
        .unique()
        .count()
}

fn box_count_3d(data: &Vec<Grid3D>, size: u32) -> usize {
    let n = data.len();

    let x_min = data
        .iter()
        .min_by(|Grid3D { x: x1, .. }, Grid3D { x: x2, .. }| x1.cmp(x2))
        .unwrap().x;

    let x_max = data
        .iter()
        .max_by(|Grid3D { x: x1, .. }, Grid3D { x: x2, .. }| x1.cmp(x2))
        .unwrap().x;

    let y_min = data
        .iter()
        .min_by(|Grid3D { y: v1, .. }, Grid3D { y: v2, .. }| v1.cmp(v2))
        .unwrap().y;

    let y_max = data
        .iter()
        .max_by(|Grid3D { y: v1, .. }, Grid3D { y: v2, .. }| v1.cmp(v2))
        .unwrap().y;

    let z_min = data
        .iter()
        .min_by(|Grid3D { z: v1, .. }, Grid3D { z: v2, .. }| v1.cmp(v2))
        .unwrap().y;

    let z_max = data
        .iter()
        .max_by(|Grid3D { z: v1, .. }, Grid3D { z: v2, .. }| v1.cmp(v2))
        .unwrap().y;

    let x_range = (x_max - x_min) as f64;
    let y_range = (y_max - y_min) as f64;
    let z_range = (z_max - z_min) as f64;

    let w: f64 = x_range / (size as f64);

    let grid_points = data.iter()
        .map(|Grid3D { x, y, z }| [
            ((x - x_min) as f64 / w) as i32,
            ((y - y_min) as f64 / w) as i32,
            ((z - z_min) as f64 / w) as i32,
        ])
        .collect::<Vec<_>>();

    return grid_points.iter()
        .unique()
        .count();
}

fn box_count_nd<const N: usize>(data: &Vec<[f32; N]>, size: u32) -> usize {
    let ranges = (0..N).map(|n|
        match data.iter()
            .minmax_by(|a, b| a[n].total_cmp(&b[n])) {
            MinMaxResult::NoElements => panic!("No data"),
            MinMaxResult::OneElement(_) => panic!("Needs more than one point to compute boxcount"),
            MinMaxResult::MinMax(min, max) => [min[n], min[n]],
        }).collect::<Vec<_>>();

    let w: f32 = (ranges[0][1] - ranges[0][0]) / (size as f32);

    return data.iter()
        .map(|point| -> [i32; N] { std::array::from_fn(|n| ((point[n] - ranges[n][0]) / w) as i32) })
        .unique()
        .count();
}

pub fn main(cli: &BoxCountCli) {
    let particles = read(&cli.path, cli.format);
}
