#![feature(array_zip)]
#![feature(generic_const_exprs)]
#![feature(let_chains)]

use clap::Parser;
use rand::prelude::*;
use crate::cli::{drive_system};
use crate::cli::cli::{StickProbabilityCli, InitialCli, BallsCli, PCM, ModelCli, SurfaceProbabilityMeasureCli};
use crate::cli::cli::PCM::Balls2d;
use crate::cli::output::write;
use crate::surface_probability_measure::{LoggerSticker, ReadOnlyVectorStorage};
use crate::system::model::DLASystem;
use crate::system::spaces::continuous_3d::{ContinuousSticker, ContinuousStorage, ContinuousWalker};
use crate::system::spaces::hexagonal::HexPosition;
use crate::system::spaces::kd_grid::{KDSpace};
use crate::system::spaces::square_grid::{Grid2D, Grid3D};
use crate::system::spaces::VectorStorage;
use crate::system::spawner::UniformSpawner;
use crate::system::sticker::{ProbabilisticSticking, SimpleSticking};
use crate::system::walker::{DiagonalRandomWalker, LocalRandomWalker};

mod system;
mod surface_probability_measure;
mod cli;

fn main() {
    let cli = ModelCli::parse();

    println!("Running: {:?}", cli);

    match cli.preconfigured_model {
        PCM::Initial(InitialCli { grid_size }) => {
            let mut sys = DLASystem::<_, Grid2D, _, _, _, _>::new(
                SmallRng::seed_from_u64(cli.seed),
                VectorStorage::new(grid_size, 2),
                LocalRandomWalker,
                UniformSpawner,
                SimpleSticking,
                cli.max_particles,
            );

            drive_system(&mut sys, cli.max_frames, cli.notify_every);
            write(&sys, cli.format, &cli.output);
        }

        PCM::StickProbability(StickProbabilityCli { grid_size, stick_probability }) => {
            let mut sys = DLASystem::<_, Grid2D, _, _, _, _>::new(
                SmallRng::seed_from_u64(cli.seed),
                VectorStorage::new(grid_size, 2),
                LocalRandomWalker,
                UniformSpawner,
                ProbabilisticSticking::new(stick_probability).unwrap(),
                cli.max_particles,
            );

            drive_system(&mut sys, cli.max_frames, cli.notify_every);
            write(&sys, cli.format, &cli.output);
        }

        PCM::Grid3KD(StickProbabilityCli { grid_size, stick_probability }) => {
            let mut sys = DLASystem::<_, Grid3D, _, _, _, _>::new(
                SmallRng::seed_from_u64(cli.seed),
                KDSpace::new(),
                LocalRandomWalker,
                UniformSpawner,
                ProbabilisticSticking::new(stick_probability).unwrap(),
                cli.max_particles,
            );

            drive_system(&mut sys, cli.max_frames, cli.notify_every);
            write(&sys, cli.format, &cli.output);
        }

        PCM::Grid3KDTsang(StickProbabilityCli { grid_size, stick_probability }) => {
            let mut sys = DLASystem::<_, Grid3D, _, _, _, _>::new(
                SmallRng::seed_from_u64(cli.seed),
                KDSpace::new(),
                DiagonalRandomWalker,
                UniformSpawner,
                ProbabilisticSticking::new(stick_probability).unwrap(),
                cli.max_particles,
            );

            drive_system(&mut sys, cli.max_frames, cli.notify_every);
            write(&sys, cli.format, &cli.output);
        }

        PCM::Grid3(StickProbabilityCli { grid_size, stick_probability }) => {
            let mut sys = DLASystem::<_, Grid3D, _, _, _, _>::new(
                SmallRng::seed_from_u64(cli.seed),
                VectorStorage::new(grid_size, 3),
                LocalRandomWalker,
                UniformSpawner,
                ProbabilisticSticking::new(stick_probability).unwrap(),
                cli.max_particles,
            );

            drive_system(&mut sys, cli.max_frames, cli.notify_every);
            write(&sys, cli.format, &cli.output);
        }

        PCM::Hex(StickProbabilityCli { grid_size, stick_probability }) => {
            let mut sys = DLASystem::<_, HexPosition, _, _, _, _>::new(
                SmallRng::seed_from_u64(cli.seed),
                VectorStorage::new(grid_size, 2),
                LocalRandomWalker,
                UniformSpawner,
                ProbabilisticSticking::new(stick_probability).unwrap(),
                cli.max_particles,
            );

            drive_system(&mut sys, cli.max_frames, cli.notify_every);
            write(&sys, cli.format, &cli.output);
        }

        PCM::Balls2d(BallsCli { ball_radius, stick_distance, walk_step }) => {
            use system::spaces::continuous_2d;

            let mut sys = DLASystem::new(
                SmallRng::seed_from_u64(cli.seed),
                continuous_2d::ContinuousStorage { inner: kiddo::KdTree::new(), ball_radius_sq: ball_radius * ball_radius },
                continuous_2d::ContinuousWalker { walk_step },
                UniformSpawner,
                continuous_2d::ContinuousSticker { range_sq: stick_distance * stick_distance },
                cli.max_particles,
            );

            drive_system(&mut sys, cli.max_frames, cli.notify_every);
            write(&sys, cli.format, &cli.output);
        }

        PCM::Balls(BallsCli { ball_radius, stick_distance, walk_step }) => {
            let mut sys = DLASystem::new(
                SmallRng::seed_from_u64(cli.seed),
                ContinuousStorage { inner: kiddo::KdTree::new(), ball_radius_sq: ball_radius * ball_radius },
                ContinuousWalker { walk_step },
                UniformSpawner,
                ContinuousSticker { range_sq: stick_distance * stick_distance },
                cli.max_particles,
            );

            drive_system(&mut sys, cli.max_frames, cli.notify_every);
            write(&sys, cli.format, &cli.output);
        }

        PCM::SurfaceProbabilityMeasure(SurfaceProbabilityMeasureCli { grid_size, stick_probability, particles, initial_data }) => {
            let logger_sticker = LoggerSticker::new(stick_probability);

            let mut sys = DLASystem::new(
                SmallRng::seed_from_u64(cli.seed),
                ReadOnlyVectorStorage::new(&initial_data, grid_size),
                LocalRandomWalker,
                UniformSpawner,
                &logger_sticker,
                cli.max_particles,
            );

            let particles = particles as usize;

            while sys.running && logger_sticker.stick_positions.borrow().len() < particles {
                sys.update();
            }

            let mut writer = csv::Writer::from_path(cli.output)
                .expect("Failed to open output path");

            let stick_positions = logger_sticker.stick_positions.borrow();

            stick_positions
                .iter()
                .for_each(|pos|
                    writer.serialize(pos)
                        .expect("Failed to write position")
                );

            writer.flush()
                .unwrap();
        }
    }
}
