/*
    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 .
*/
#![feature(map_many_mut)]
pub mod network;
pub mod spatial_index;
use hurrycurry_protocol::{
    glam::IVec2, movement::MovementBase, Gamedata, ItemIndex, ItemLocation, Message,
    MessageTimeout, PacketC, PlayerID, RecipeIndex, Score, TileIndex,
};
use spatial_index::SpatialIndex;
use std::{
    collections::{HashMap, HashSet},
    sync::Arc,
    time::Instant,
};
#[derive(Debug, PartialEq)]
pub struct Involvement {
    pub progress: f32,
    pub recipe: RecipeIndex,
    pub working: usize,
    pub warn: bool,
}
#[derive(Debug, PartialEq)]
pub struct Item {
    pub kind: ItemIndex,
    pub active: Option,
}
pub struct Tile {
    pub kind: TileIndex,
    pub item: Option- ,
}
pub struct Player {
    pub name: String,
    pub character: i32,
    pub interacting: Option,
    pub item: Option- ,
    pub communicate_persist: Option<(Message, MessageTimeout)>,
    pub movement: MovementBase,
}
pub struct Game {
    pub data: Arc,
    pub tiles: HashMap,
    pub walkable: HashSet,
    pub players: HashMap,
    pub players_spatial_index: SpatialIndex,
    pub end: Option,
    pub lobby: bool,
    pub environment_effects: HashSet,
    pub score: Score,
}
impl Default for Game {
    fn default() -> Self {
        Self {
            data: Default::default(),
            tiles: HashMap::new(),
            walkable: HashSet::new(),
            players: HashMap::new(),
            players_spatial_index: SpatialIndex::default(),
            end: None,
            lobby: false,
            environment_effects: HashSet::new(),
            score: Score::default(),
        }
    }
}
impl Game {
    pub fn apply_packet(&mut self, packet: PacketC) {
        match packet {
            PacketC::Data { data } => {
                self.data = Arc::new(data);
            }
            PacketC::AddPlayer {
                id,
                position,
                character,
                name,
            } => {
                self.players.insert(
                    id,
                    Player {
                        name,
                        character,
                        interacting: None,
                        item: None,
                        communicate_persist: None,
                        movement: MovementBase::new(position),
                    },
                );
            }
            PacketC::RemovePlayer { id } => {
                self.players.remove(&id);
            }
            PacketC::Movement {
                player,
                pos,
                rot,
                boost,
                dir,
            } => {
                if let Some(p) = self.players.get_mut(&player) {
                    p.movement.input(dir, boost);
                    p.movement.position = pos;
                    p.movement.rotation = rot;
                }
            }
            PacketC::MoveItem { from, to } => {
                *self.get_item(to) = self.get_item(from).take();
            }
            PacketC::SetItem { location, item } => {
                *self.get_item(location) = item.map(|kind| Item { kind, active: None });
            }
            PacketC::SetProgress {
                item,
                progress,
                warn,
            } => {
                self.get_item(item).as_mut().unwrap().active =
                    progress.map(|progress| Involvement {
                        working: 1,
                        warn,
                        progress,
                        recipe: RecipeIndex(0),
                    });
            }
            PacketC::UpdateMap {
                tile,
                kind,
                neighbors: _,
            } => {
                if let Some(kind) = kind {
                    self.tiles.insert(tile, Tile { kind, item: None });
                    if self.data.tile_collide[kind.0] {
                        self.walkable.remove(&tile);
                    } else {
                        self.walkable.insert(tile);
                    }
                } else {
                    self.tiles.remove(&tile);
                    self.walkable.remove(&tile);
                }
            }
            PacketC::Communicate {
                player,
                message,
                timeout,
            } => {
                if let Some(timeout) = &timeout {
                    if let Some(player) = self.players.get_mut(&player) {
                        player.communicate_persist = message.to_owned().map(|m| (m, *timeout));
                    }
                }
            }
            PacketC::Score(score) => {
                self.score = score;
            }
            PacketC::SetIngame { state: _, lobby } => {
                self.lobby = lobby;
            }
            PacketC::Environment { effects } => {
                self.environment_effects = effects;
            }
            _ => (),
        }
    }
    pub fn tick(&mut self, dt: f32) {
        self.score.time_remaining -= dt as f64;
        self.score.time_remaining -= self.score.time_remaining.max(0.);
        for (&pid, player) in &mut self.players {
            player.movement.update(&self.walkable, dt);
            if let Some((_, timeout)) = &mut player.communicate_persist {
                timeout.remaining -= dt;
                if timeout.remaining < 0. {
                    player.communicate_persist = None;
                }
            }
            self.players_spatial_index
                .update_entry(pid, player.movement.position);
        }
        self.players_spatial_index.all(|p1, pos1| {
            self.players_spatial_index.query(pos1, 2., |p2, _pos2| {
                if let Some([a, b]) = self.players.get_many_mut([&p1, &p2]) {
                    a.movement.collide(&mut b.movement, dt)
                }
            })
        });
    }
    pub fn get_item(&mut self, location: ItemLocation) -> &mut Option-  {
        match location {
            ItemLocation::Tile(pos) => &mut self.tiles.get_mut(&pos).unwrap().item,
            ItemLocation::Player(pid) => &mut self.players.get_mut(&pid).unwrap().item,
        }
    }
}
impl From for Tile {
    fn from(kind: TileIndex) -> Self {
        Self { kind, item: None }
    }
}