/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2025 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 hurrycurry_protocol::{Gamedata, ItemIndex, Recipe, TileIndex};
use log::debug;
use std::{
    collections::{BTreeSet, HashMap},
    sync::RwLock,
};

use crate::{Serverdata, entities::EntityDecl};

#[derive(Default)]
pub(crate) struct ItemTileRegistry {
    tiles: RwLock<Vec<String>>,
    items: RwLock<Vec<String>>,
}

impl ItemTileRegistry {
    pub fn register_tile(&self, name: String) -> TileIndex {
        TileIndex(Self::register(&self.tiles, name))
    }
    pub fn register_item(&self, name: String) -> ItemIndex {
        ItemIndex(Self::register(&self.items, name))
    }
    fn register(db: &RwLock<Vec<String>>, name: String) -> usize {
        let mut db = db.write().unwrap();
        // TODO maybe btreemap for better time complexity
        if let Some(index) = db.iter().position(|e| e == &name) {
            index
        } else {
            let index = db.len();
            db.push(name);
            index
        }
    }
    pub fn finish(self) -> (Vec<String>, Vec<String>) {
        (
            self.items.into_inner().unwrap(),
            self.tiles.into_inner().unwrap(),
        )
    }
}

pub(crate) fn filter_unused_tiles_and_items(data: &mut Gamedata, serverdata: &mut Serverdata) {
    debug!("running unused item/tile filter");
    let mut used_items = BTreeSet::new();
    let mut used_tiles = BTreeSet::new();

    for recipe in &data.recipes {
        used_items.extend(recipe.inputs());
        used_items.extend(recipe.outputs());
        used_tiles.extend(recipe.tile());
    }
    for demand in &data.demands {
        used_items.insert(demand.input);
        used_items.extend(demand.output);
    }
    for rg in serverdata.recipe_groups.values() {
        used_items.extend(rg);
    }
    for &(tile, item) in serverdata.initial_map.values() {
        used_tiles.insert(tile);
        used_items.extend(item);
    }
    for e in &serverdata.entity_decls {
        match e {
            EntityDecl::Gate { blocker_tile: blocker, .. } => used_tiles.insert(*blocker),
            EntityDecl::Customers { unknown_order, .. } => used_items.insert(*unknown_order),
            _ => false,
        };
    }

    let mut item_names = Vec::new();
    let mut item_map = HashMap::new();
    for item in used_items {
        item_map.insert(item, ItemIndex(item_names.len()));
        item_names.push(data.item_name(item).to_string());
    }

    let mut tile_names = Vec::new();
    let mut tile_map = HashMap::new();
    for tile in used_tiles {
        tile_map.insert(tile, TileIndex(tile_names.len()));
        tile_names.push(data.tile_name(tile).to_string());
    }

    debug!(
        "removing {} items and {} tiles from registry",
        data.item_names.len() - item_names.len(),
        data.tile_names.len() - tile_names.len()
    );

    data.item_names = item_names;
    data.tile_names = tile_names;

    for recipe in &mut data.recipes {
        match recipe {
            Recipe::Passive {
                tile,
                input,
                output,
                ..
            } => {
                if let Some(tile) = tile {
                    *tile = tile_map[tile]
                }
                *input = item_map[input];
                if let Some(output) = output {
                    *output = item_map[output];
                }
            }
            Recipe::Active {
                tile,
                input,
                outputs,
                ..
            } => {
                if let Some(tile) = tile {
                    *tile = tile_map[tile]
                }
                *input = item_map[input];
                for output in outputs.iter_mut().flatten() {
                    *output = item_map[output];
                }
            }
            Recipe::Instant {
                tile,
                inputs,
                outputs,
                ..
            } => {
                if let Some(tile) = tile {
                    *tile = tile_map[tile]
                }
                for input in inputs.iter_mut().flatten() {
                    *input = item_map[input];
                }
                for output in outputs.iter_mut().flatten() {
                    *output = item_map[output];
                }
            }
        }
    }
    for demand in &mut data.demands {
        demand.input = item_map[&demand.input];
        if let Some(output) = &mut demand.output {
            *output = item_map[output];
        }
    }
    for rg in serverdata.recipe_groups.values_mut() {
        *rg = rg.clone().into_iter().map(|e| item_map[&e]).collect();
    }
    for (tile, item) in serverdata.initial_map.values_mut() {
        *tile = tile_map[tile];
        if let Some(item) = item {
            *item = item_map[item]
        }
    }
    data.tile_walkable = data
        .tile_walkable
        .clone()
        .into_iter()
        .map(|e| tile_map[&e])
        .collect();
    data.tile_interactable_empty = data
        .tile_interactable_empty
        .clone()
        .into_iter()
        .map(|e| tile_map[&e])
        .collect();
    data.tile_placeable_items = data
        .tile_placeable_items
        .clone()
        .into_iter()
        .map(|(tile, items)| {
            (
                tile_map[&tile],
                items.into_iter().map(|i| item_map[&i]).collect(),
            )
        })
        .collect();

    debug!("done")
}
