/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2026 Hurry Curry! Contributors

    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 <https://www.gnu.org/licenses/>.

*/
use crate::{GameConfig, Gamedata, ItemIndex, Recipe, RecipeIndex, TileIndex};

impl Gamedata {
    pub fn tile_name(&self, index: TileIndex) -> &str {
        &self.tile_names[index.0]
    }
    pub fn item_name(&self, index: ItemIndex) -> &str {
        &self.item_names[index.0]
    }
    pub fn recipe(&self, index: RecipeIndex) -> &Recipe {
        &self.recipes[index.0]
    }
    pub fn get_tile_by_name(&self, name: &str) -> Option<TileIndex> {
        self.tile_names
            .iter()
            .position(|t| t == name)
            .map(TileIndex)
    }
    pub fn get_item_by_name(&self, name: &str) -> Option<ItemIndex> {
        self.item_names
            .iter()
            .position(|t| t == name)
            .map(ItemIndex)
    }
    pub fn recipes(&self) -> impl Iterator<Item = (RecipeIndex, &Recipe)> {
        self.recipes
            .iter()
            .enumerate()
            .map(|(i, e)| (RecipeIndex(i), e))
    }
    pub fn can_place_item(&self, tiles: &[TileIndex], item: ItemIndex) -> bool {
        for t in tiles.iter().rev() {
            if let Some(whitelist) = self.tile_placeable_items.get(t) {
                return whitelist.contains(&item);
            }
            if self.tile_placeable_any.contains(t) {
                return true;
            }
        }
        false
    }
    pub fn walkable(&self, tiles: &[TileIndex]) -> bool {
        !tiles.iter().any(|t| self.tile_collide.contains(t))
    }
    pub fn walkable_preferred(&self, tiles: &[TileIndex]) -> bool {
        self.walkable(tiles)
            && !tiles
                .iter()
                .any(|p| self.tile_avoid_pathfinding.contains(p))
    }
}

impl Recipe {
    pub fn tile(&self) -> Option<TileIndex> {
        match self {
            Recipe::Passive { tile, .. } => *tile,
            Recipe::Active { tile, .. } => *tile,
            Recipe::Instant { tile, .. } => *tile,
        }
    }
    pub fn speed(&self) -> Option<f32> {
        match self {
            Recipe::Passive { speed, .. } => Some(*speed),
            Recipe::Active { speed, .. } => Some(*speed),
            _ => None,
        }
    }
    pub fn revert_speed(&self) -> Option<f32> {
        match self {
            Recipe::Passive { revert_speed, .. } => *revert_speed,
            _ => None,
        }
    }
    pub fn warn(&self) -> bool {
        match self {
            Recipe::Passive { warn, .. } => *warn,
            _ => false,
        }
    }
    pub fn inputs(&self) -> impl Iterator<Item = ItemIndex> {
        let (a, b) = match self {
            Recipe::Passive { input, .. } | Recipe::Active { input, .. } => (Some(input), None),
            Recipe::Instant { inputs, .. } => (inputs[0].as_ref(), inputs[1].as_ref()),
        };

        a.into_iter().chain(b).copied()
    }
    pub fn outputs(&self) -> impl Iterator<Item = ItemIndex> {
        let (a, b) = match self {
            Recipe::Passive { output, .. } => (output, &None),
            Recipe::Active { outputs, .. } | Recipe::Instant { outputs, .. } => {
                (&outputs[0], &outputs[1])
            }
        };

        a.iter().chain(b.iter()).copied()
    }
    pub fn supports_tile(&self, tile: TileIndex) -> bool {
        if let Some(tile_constraint) = self.tile() {
            tile == tile_constraint
        } else {
            true
        }
    }
}

mod display {
    use crate::*;
    use std::fmt::Display;

    impl Display for ItemLocation {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                ItemLocation::Tile(pos) => write!(f, "tile({}, {})", pos.x, pos.y),
                ItemLocation::Player(player, hand) => write!(f, "{player}-{hand}"),
            }
        }
    }
    impl Display for Hand {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "hand#{}", self.0)
        }
    }
    impl Display for PlayerID {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "player#{}", self.0)
        }
    }
    impl Display for TileIndex {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "tile#{}", self.0)
        }
    }
    impl Display for ItemIndex {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "item#{}", self.0)
        }
    }
    impl Display for RecipeIndex {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "recipe#{}", self.0)
        }
    }
}

mod enum_is {
    use crate::*;

    impl PlayerClass {
        pub fn is_cheflike(&self) -> bool {
            matches!(self, Self::Bot | Self::Chef)
        }
    }

    impl ItemLocation {
        pub fn is_tile(&self) -> bool {
            matches!(self, Self::Tile(..))
        }
        pub fn is_player(&self) -> bool {
            matches!(self, Self::Player(..))
        }
    }
}

pub mod deser {
    use crate::*;
    use serde::Deserializer;
    use std::collections::BTreeMap;

    pub(crate) fn deser_i64<'de, D: Deserializer<'de>>(deserializer: D) -> Result<i64, D::Error> {
        let x = f64::deserialize(deserializer)?;
        Ok(x.trunc() as i64)
    }
    pub(crate) fn deser_i32<'de, D: Deserializer<'de>>(deserializer: D) -> Result<i32, D::Error> {
        let x = f64::deserialize(deserializer)?;
        Ok(x.trunc() as i32)
    }
    pub(crate) fn deser_usize<'de, D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<usize, D::Error> {
        let x = f64::deserialize(deserializer)?;
        Ok(x.trunc() as usize)
    }
    pub(crate) fn deser_tile_index_map<'de, D: Deserializer<'de>>(
        deserializer: D,
    ) -> Result<BTreeMap<TileIndex, HashSet<ItemIndex>>, D::Error> {
        let x = BTreeMap::<String, HashSet<ItemIndex>>::deserialize(deserializer)?;
        Ok(x.into_iter()
            .map(|(k, v)| (TileIndex(k.parse().ok().unwrap_or_default()), v))
            .collect())
    }
}

impl GameConfig {
    pub fn new_map(map: &str) -> Self {
        Self {
            map: map.to_owned(),
            ..Default::default()
        }
    }
}
