/*
    Undercooked - 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 .
    
*/
use crate::{
    interaction::Recipe,
    protocol::{DemandIndex, ItemIndex, RecipeIndex, TileIndex},
};
use glam::{IVec2, Vec2};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::RwLock};
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default)]
#[serde(rename_all = "snake_case")]
pub enum Action {
    #[default]
    Never,
    Passive,
    Active,
    Instant,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RecipeDecl {
    #[serde(default)]
    tile: Option,
    #[serde(default)]
    inputs: Vec,
    #[serde(default)]
    outputs: Vec,
    #[serde(default)]
    action: Action,
    #[serde(default)]
    warn: bool,
    #[serde(default)]
    revert_duration: Option,
    #[serde(default)]
    duration: Option,
}
#[derive(Debug, Clone, Deserialize)]
pub struct InitialMap {
    map: Vec,
    tiles: HashMap,
    items: HashMap,
    collider: Vec,
    walkable: Vec,
    chef_spawn: char,
    customer_spawn: char,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DemandDecl {
    from: String,
    to: String,
    duration: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Demand {
    pub from: ItemIndex,
    pub to: ItemIndex,
    pub duration: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Gamedata {
    recipes: Vec,
    pub demands: Vec,
    pub item_names: Vec,
    pub tile_names: Vec,
    pub tile_collide: Vec,
    pub tile_interact: Vec,
    #[serde(skip)]
    pub initial_map: HashMap)>,
    pub chef_spawn: Vec2,
    pub customer_spawn: Vec2,
}
pub fn build_gamedata(
    recipes_in: Vec,
    map_in: InitialMap,
    demands_in: Vec,
) -> Gamedata {
    let item_names = RwLock::new(Vec::new());
    let tile_names = RwLock::new(Vec::new());
    let mut recipes = Vec::new();
    let mut demands = Vec::new();
    for r in recipes_in {
        let r2 = r.clone();
        let mut inputs = r
            .inputs
            .into_iter()
            .map(|i| ItemIndex(register(&item_names, i)));
        let mut outputs = r
            .outputs
            .into_iter()
            .map(|o| ItemIndex(register(&item_names, o)));
        let tile = r.tile.map(|t| TileIndex(register(&tile_names, t)));
        match r.action {
            Action::Never => {}
            Action::Passive => recipes.push(Recipe::Passive {
                duration: r.duration.expect("duration for passive missing"),
                warn: r.warn,
                tile,
                revert_duration: r.revert_duration,
                input: inputs.next().expect("passive recipe without input"),
                output: outputs.next(),
            }),
            Action::Active => recipes.push(Recipe::Active {
                duration: r.duration.expect("duration for active missing"),
                tile,
                input: inputs.next().expect("active recipe without input"),
                outputs: [outputs.next(), outputs.next()],
            }),
            Action::Instant => {
                recipes.push(Recipe::Instant {
                    tile,
                    inputs: [inputs.next(), inputs.next()],
                    outputs: [outputs.next(), outputs.next()],
                });
            }
        }
        assert_eq!(inputs.next(), None, "{r2:?}");
        assert_eq!(outputs.next(), None, "{r2:?}");
    }
    for d in demands_in {
        demands.push(Demand {
            from: ItemIndex(register(&item_names, d.from)),
            to: ItemIndex(register(&item_names, d.to)),
            duration: d.duration,
        })
    }
    let mut chef_spawn = Vec2::new(0., 0.);
    let mut customer_spawn = Vec2::new(0., 0.);
    let mut initial_map = HashMap::new();
    for (y, line) in map_in.map.iter().enumerate() {
        for (x, tile) in line.trim().char_indices() {
            let pos = IVec2::new(x as i32, y as i32);
            if tile == map_in.chef_spawn {
                chef_spawn = pos.as_vec2() + Vec2::splat(0.5);
            }
            if tile == map_in.customer_spawn {
                customer_spawn = pos.as_vec2() + Vec2::splat(0.5);
            }
            let tilename = map_in.tiles[&tile].clone();
            let itemname = map_in.items.get(&tile).cloned();
            let tile = TileIndex(register(&tile_names, tilename));
            let item = itemname.map(|i| ItemIndex(register(&item_names, i)));
            initial_map.insert(pos, (tile, item));
        }
    }
    let item_names = item_names.into_inner().unwrap();
    let tile_names = tile_names.into_inner().unwrap();
    let tile_collide = tile_names
        .iter()
        .map(|i| !map_in.walkable.contains(i))
        .collect();
    let tile_interact = tile_names
        .iter()
        .map(|i| !map_in.collider.contains(i) && !map_in.walkable.contains(i))
        .collect();
    Gamedata {
        demands,
        tile_collide,
        tile_interact,
        recipes,
        initial_map,
        item_names,
        tile_names,
        chef_spawn,
        customer_spawn,
    }
}
fn register(db: &RwLock>, name: String) -> usize {
    let mut db = db.write().unwrap();
    if let Some(index) = db.iter().position(|e| e == &name) {
        index
    } else {
        let index = db.len();
        db.push(name);
        index
    }
}
impl Gamedata {
    pub fn tile_name(&self, index: TileIndex) -> &String {
        &self.tile_names[index.0]
    }
    pub fn is_tile_colliding(&self, index: TileIndex) -> bool {
        self.tile_collide[index.0]
    }
    pub fn is_tile_interactable(&self, index: TileIndex) -> bool {
        self.tile_interact[index.0]
    }
    pub fn item_name(&self, index: ItemIndex) -> &String {
        &self.item_names[index.0]
    }
    pub fn recipe(&self, index: RecipeIndex) -> &Recipe {
        &self.recipes[index.0]
    }
    pub fn demand(&self, index: DemandIndex) -> &Demand {
        &self.demands[index.0]
    }
    pub fn get_tile_by_name(&self, name: &str) -> Option {
        self.tile_names
            .iter()
            .position(|t| t == name)
            .map(TileIndex)
    }
    pub fn get_item_by_name(&self, name: &str) -> Option {
        self.item_names
            .iter()
            .position(|t| t == name)
            .map(ItemIndex)
    }
    pub fn recipes(&self) -> impl Iterator-  {
        self.recipes
            .iter()
            .enumerate()
            .map(|(i, e)| (RecipeIndex(i), e))
    }
}