/*
    Hurry Curry! - a game about cooking
    Copyright 2024 metamuffin
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3 of the License only.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.
    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see .
*/
pub mod demands;
mod pathfinding;
use super::EntityT;
use crate::{data::Demand, game::Game};
use anyhow::{anyhow, Result};
use fake::{faker, Fake};
use hurrycurry_protocol::{glam::IVec2, DemandIndex, Message, PacketC, PacketS, PlayerID};
use log::{info, warn};
use pathfinding::{find_path, Path};
use rand::{random, thread_rng};
use std::collections::{HashMap, VecDeque};
#[derive(Debug, Clone)]
pub struct Customers {
    demands: Vec,
    cpackets: VecDeque<(PlayerID, PacketS)>,
    chairs: HashMap,
    customer_id_counter: PlayerID,
    customers: HashMap,
    spawn_cooldown: f32,
}
#[derive(Debug, Clone)]
enum CustomerState {
    Entering {
        path: Path,
        chair: IVec2,
    },
    Waiting {
        demand: DemandIndex,
        chair: IVec2,
        timeout: f32,
    },
    Eating {
        demand: DemandIndex,
        target: IVec2,
        progress: f32,
        chair: IVec2,
    },
    Exiting {
        path: Path,
    },
}
impl Customers {
    pub fn new(chairs: HashMap, demands: Vec) -> Self {
        Self {
            chairs,
            customer_id_counter: PlayerID(0),
            customers: Default::default(),
            demands,
            spawn_cooldown: 0.,
            cpackets: VecDeque::new(),
        }
    }
}
impl EntityT for Customers {
    fn tick(&mut self, game: &mut Game, packet_out: &mut VecDeque, dt: f32) -> Result<()> {
        self.spawn_cooldown -= dt;
        self.spawn_cooldown = self.spawn_cooldown.max(0.);
        if self.customers.len() < 5 && self.spawn_cooldown <= 0. {
            self.spawn_cooldown = 10. + random::() * 10.;
            self.customer_id_counter.0 -= 1;
            let id = self.customer_id_counter;
            self.cpackets.push_back((
                id,
                PacketS::Join {
                    name: faker::name::fr_fr::Name().fake(),
                    character: -1 - (random::() as i32),
                },
            ));
            let chair = self.select_chair().ok_or(anyhow!("no free chair found"))?;
            let from = game.data.customer_spawn.as_ivec2();
            let path = find_path(&game.walkable, from, chair)
                .ok_or(anyhow!("no path from {from} to {chair}"))?;
            info!("{id:?} -> entering");
            self.customers
                .insert(id, CustomerState::Entering { path, chair });
        }
        let mut customers_to_remove = Vec::new();
        for (&id, state) in &mut self.customers {
            let Some(player) = game.players.get_mut(&id) else {
                continue;
            };
            match state {
                CustomerState::Entering { path, chair } => {
                    player.direction = path.next_direction(player.position());
                    if path.is_done() {
                        let demand = DemandIndex(random::() % self.demands.len());
                        self.cpackets.push_back((
                            id,
                            PacketS::Communicate {
                                message: Some(Message::Item(self.demands[demand.0].from)),
                                persist: true,
                            },
                        ));
                        info!("{id:?} -> waiting");
                        *state = CustomerState::Waiting {
                            chair: *chair,
                            timeout: 90. + random::() * 60.,
                            demand,
                        };
                    }
                }
                CustomerState::Waiting {
                    chair,
                    demand,
                    timeout,
                } => {
                    player.direction *= 0.;
                    *timeout -= dt;
                    if *timeout <= 0. {
                        self.cpackets.push_back((
                            id,
                            PacketS::Communicate {
                                message: None,
                                persist: true,
                            },
                        ));
                        self.cpackets.push_back((
                            id,
                            PacketS::Communicate {
                                message: Some(Message::Effect("angry".to_string())),
                                persist: false,
                            },
                        ));
                        let path = find_path(
                            &game.walkable,
                            player.position().as_ivec2(),
                            game.data.customer_spawn.as_ivec2(),
                        )
                        .expect("no path to exit");
                        *self.chairs.get_mut(chair).unwrap() = true;
                        game.score.demands_failed += 1;
                        game.score.points -= 1;
                        game.score_changed = true;
                        info!("{id:?} -> exiting");
                        *state = CustomerState::Exiting { path }
                    } else {
                        let demand_data = &self.demands[demand.0];
                        let demand_pos = [IVec2::NEG_X, IVec2::NEG_Y, IVec2::X, IVec2::Y]
                            .into_iter()
                            .find_map(|off| {
                                let pos = *chair + off;
                                if game
                                    .tiles
                                    .get(&pos)
                                    .map(|t| {
                                        t.item
                                            .as_ref()
                                            .map(|i| i.kind == demand_data.from)
                                            .unwrap_or_default()
                                    })
                                    .unwrap_or_default()
                                {
                                    Some(pos)
                                } else {
                                    None
                                }
                            });
                        if let Some(pos) = demand_pos {
                            self.cpackets.push_back((
                                id,
                                PacketS::Communicate {
                                    persist: true,
                                    message: None,
                                },
                            ));
                            self.cpackets.push_back((
                                id,
                                PacketS::Communicate {
                                    message: Some(Message::Effect("satisfied".to_string())),
                                    persist: false,
                                },
                            ));
                            self.cpackets
                                .push_back((id, PacketS::Interact { pos: Some(pos) }));
                            self.cpackets
                                .push_back((id, PacketS::Interact { pos: None }));
                            info!("{id:?} -> eating");
                            *state = CustomerState::Eating {
                                demand: *demand,
                                target: pos,
                                progress: 0.,
                                chair: *chair,
                            }
                        }
                    }
                }
                CustomerState::Eating {
                    demand,
                    target,
                    progress,
                    chair,
                } => {
                    player.direction *= 0.;
                    let demand = &self.demands[demand.0];
                    *progress += dt / demand.duration;
                    if *progress >= 1. {
                        self.cpackets
                            .push_back((id, PacketS::ReplaceHand { item: demand.to }));
                        if demand.to.is_some() {
                            self.cpackets
                                .push_back((id, PacketS::Interact { pos: Some(*target) }));
                            self.cpackets
                                .push_back((id, PacketS::Interact { pos: None }));
                        }
                        let path = find_path(
                            &game.walkable,
                            player.position().as_ivec2(),
                            game.data.customer_spawn.as_ivec2(),
                        )
                        .ok_or(anyhow!("no path to exit"))?;
                        *self.chairs.get_mut(chair).unwrap() = true;
                        game.score.demands_completed += 1;
                        game.score.points += demand.points;
                        game.score_changed = true;
                        info!("{id:?} -> exiting");
                        *state = CustomerState::Exiting { path }
                    }
                }
                CustomerState::Exiting { path } => {
                    player.direction = path.next_direction(player.position());
                    if path.is_done() {
                        info!("{id:?} -> leave");
                        self.cpackets.push_back((id, PacketS::Leave));
                        customers_to_remove.push(id);
                    }
                }
            }
        }
        for c in customers_to_remove {
            self.customers.remove(&c).unwrap();
        }
        for (player, packet) in self.cpackets.drain(..) {
            if let Err(err) = game.packet_in(player, packet, &mut vec![], packet_out) {
                warn!("demand packet {err}");
            }
        }
        Ok(())
    }
}
impl Customers {
    fn select_chair(&mut self) -> Option {
        use rand::seq::IteratorRandom;
        let (chosen, free) = self
            .chairs
            .iter_mut()
            .filter(|(_p, free)| **free)
            .choose(&mut thread_rng())?;
        *free = false;
        Some(*chosen)
    }
}