use super::*;

mod background;
#[path = "strategy/mod.rs"]
pub mod processor_strategy;

pub use background::*;
pub use processor_strategy::GameProcessorStrategy;

pub struct GameProcessor<G: Game> {
    seed: Option<u64>,
    strategy: Box<dyn GameProcessorStrategy<G>>,
    players: Vec<Option<Box<dyn Player<G>>>>,
    player_comments: Vec<Option<String>>,
    ticks_processed: usize,
    tick_handler: Option<Box<dyn FnMut(Option<&Vec<G::Event>>, &G) + Send>>,
    results_handler: Option<Box<dyn FnOnce(FullResults<G>) + Send>>,
}

impl<G: Game + 'static> GameProcessor<G> {
    pub fn new_full(full_options: FullOptions<G>) -> Self {
        Self::new(
            full_options.seed,
            full_options.game.into(),
            futures::executor::block_on(futures::future::join_all(
                full_options.players.iter().map(|options| options.get()),
            ))
            .into_iter()
            .map(|result| match result {
                Ok(player) => player,
                Err(e) => Box::new(ErroredPlayer(e.to_string())),
            })
            .collect(),
        )
    }
    pub fn new(
        seed: Option<u64>,
        options: GameInitOptions<G>,
        players: Vec<Box<dyn Player<G>>>,
    ) -> Self {
        let seed = seed.unwrap_or_else(|| global_rng().gen());
        let mut rng = <rand::rngs::StdRng as rand::SeedableRng>::seed_from_u64(seed);
        let game = match options {
            GameInitOptions::Ready(game) => game,
            GameInitOptions::New(options) => G::init(&mut rng, players.len(), options),
        };
        let player_comments = vec![None; players.len()];
        Self {
            seed: Some(seed),
            strategy: Box::new(processor_strategy::Standard::new(game, rng)),
            players: players.into_iter().map(|player| Some(player)).collect(),
            player_comments,
            ticks_processed: 0,
            tick_handler: None,
            results_handler: None,
        }
    }
    pub fn repeat_full(
        full_options: FullOptions<G>,
        reader: impl std::io::Read + Send + 'static,
    ) -> Self {
        Self::repeat(
            reader,
            futures::executor::block_on(futures::future::join_all(
                full_options.players.iter().map(|options| options.get()),
            ))
            .into_iter()
            .map(|result| match result {
                Ok(player) => player,
                Err(e) => Box::new(ErroredPlayer(e.to_string())),
            })
            .collect(),
        )
    }
    pub fn repeat(
        reader: impl std::io::Read + Send + 'static,
        players: Vec<Box<dyn Player<G>>>,
    ) -> Self {
        let player_comments = vec![None; players.len()];
        Self {
            seed: None,
            strategy: Box::new(processor_strategy::Repeat::new(Box::new(
                std::io::BufReader::new(reader),
            ))),
            players: players.into_iter().map(|player| Some(player)).collect(),
            player_comments,
            ticks_processed: 0,
            tick_handler: None,
            results_handler: None,
        }
    }

    pub fn set_tick_handler(
        &mut self,
        mut handler: Box<dyn FnMut(Option<&Vec<G::Event>>, &G) + Send>,
    ) {
        handler(None, self.strategy.game());
        self.tick_handler = Some(handler);
    }
    pub fn set_results_handler(&mut self, handler: Box<dyn FnOnce(FullResults<G>) + Send>) {
        self.results_handler = Some(handler);
    }

    pub(crate) fn debug_update(
        &mut self,
        game_state: Option<&G>,
        debug_interface: &DebugInterface<G>,
    ) {
        for (index, player_cell) in self.players.iter_mut().enumerate() {
            if let Some(player) = player_cell {
                if let Err(e) = player.debug_update(
                    &game_state
                        .unwrap_or(self.strategy.game())
                        .player_view(index),
                    &debug_interface.for_player(index, true),
                ) {
                    *player_cell = None;
                    warn!("Player error: {}", e);
                    self.player_comments[index] = Some(format!("Player crashed: {}", e));
                }
            }
        }
    }

    pub(crate) fn process_tick(
        &mut self,
        debug_interface: Option<&DebugInterface<G>>,
    ) -> Vec<G::Event> {
        assert!(!self.finished());
        let views: Vec<_> = (0..self.players.len())
            .map(|index| self.strategy.game().player_view(index))
            .collect();
        let action_results = self
            .players
            .iter_mut()
            .zip(views.into_iter())
            .enumerate()
            .filter_map(|(index, (player, view))| {
                player.as_mut().map(|player| {
                    (
                        index,
                        player.get_action(
                            &view,
                            debug_interface
                                .map(|debug_interface| debug_interface.for_player(index, false))
                                .as_ref(),
                        ),
                    )
                })
            });
        let player_comments = &mut self.player_comments;
        let actions: HashMap<usize, G::Action> = action_results
            .filter_map(|(index, result)| {
                if let Err(e) = &result {
                    warn!("Player error: {}", e);
                    player_comments[index] = Some(format!("Player crashed: {}", e));
                }
                result.ok().map(|action| (index, action))
            })
            .collect();
        for (index, player) in self.players.iter_mut().enumerate() {
            if !actions.contains_key(&index) {
                *player = None;
            }
        }
        let events = self.strategy.process_turn(actions);
        if let Some(handler) = &mut self.tick_handler {
            handler(Some(&events), self.strategy.game());
        }
        if self.finished() {
            let results = self.strategy.game().results();
            if let Some(handler) = self.results_handler.take() {
                handler(FullResults {
                    players: self
                        .players
                        .iter()
                        .zip(self.player_comments.iter())
                        .map(|(player, comment)| PlayerResult {
                            crashed: player.is_none(),
                            comment: comment.clone(),
                        })
                        .collect(),
                    results,
                    seed: self.seed,
                });
            }
        }
        self.ticks_processed += 1;
        trace!("Processed {} ticks", self.ticks_processed);
        events
    }

    pub fn run(mut self, debug_interface: Option<&DebugInterface<G>>) {
        while !self.finished() {
            self.process_tick(debug_interface);
            if let Some(debug_interface) = debug_interface {
                self.debug_update(None, debug_interface);
            }
        }
    }

    pub fn game(&self) -> &G {
        self.strategy.game()
    }

    pub fn finished(&self) -> bool {
        self.strategy.finished()
    }

    pub fn player_count(&self) -> usize {
        self.players.len()
    }
}
