use crate::{State, TILES};
use anyhow::{Result, anyhow};
use hurrycurry_protocol::{
    TileIndex,
    glam::{IVec2, ivec2},
};
use serde::{Deserialize, Serialize};
use std::{
    collections::{HashMap, HashSet},
    hash::RandomState,
};

#[rustfmt::skip]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MapDecl {
    map: Vec<String>,
    tiles: HashMap<char, String>,
    items: HashMap<char, String>,
    collider: Vec<String>,
    walkable: Vec<String>,
    chef_spawn: char,
    customer_spawn: char,
    score_baseline: i64,
}

pub fn export_state(state: &State) -> String {
    let mut cmin = IVec2::MAX;
    let mut cmax = IVec2::MIN;
    for pos in state.tiles.keys().copied() {
        cmin = cmin.min(pos);
        cmax = cmax.max(pos);
    }

    let mut map = Vec::new();
    let mut tiles = HashMap::new();
    let mut collider = HashSet::new();
    let mut walkable = HashSet::new();
    for y in cmin.y..=cmax.y {
        let mut line = String::new();
        for x in cmin.x..=cmax.x {
            let p = ivec2(x, y);
            line.push(if let Some(t) = state.tiles.get(&p) {
                let c = if p == state.chef_spawn {
                    '~'
                } else if p == state.customer_spawn {
                    '!'
                } else {
                    TILES[t.0].1
                };
                if TILES[t.0].2 == 2 {
                    collider.insert(TILES[t.0].0.to_string());
                }
                if TILES[t.0].2 == 1 {
                    walkable.insert(TILES[t.0].0.to_string());
                }
                tiles.insert(c, TILES[t.0].0.to_string());
                c
            } else {
                ' '
            })
        }
        map.push(line);
    }

    let decl: MapDecl = MapDecl {
        map,
        tiles,
        items: HashMap::new(),
        collider: collider.into_iter().collect(),
        walkable: walkable.into_iter().collect(),
        chef_spawn: '~',
        customer_spawn: '!',
        score_baseline: 200,
    };
    serde_yaml_ng::to_string(&decl).unwrap()
}

pub fn import_state(state: &mut State, s: &str) -> Result<()> {
    let decl: MapDecl = serde_yaml_ng::from_str(s)?;

    let name_to_tile = HashMap::<_, _, RandomState>::from_iter(
        TILES
            .iter()
            .enumerate()
            .map(|(i, (name, _, _))| ((*name).to_owned(), i)),
    );

    for (y, line) in decl.map.iter().enumerate() {
        for (x, char) in line.chars().enumerate() {
            let pos = ivec2(x as i32, y as i32);
            if char == ' ' {
                continue;
            }
            if char == decl.customer_spawn {
                state.customer_spawn = pos;
            }
            if char == decl.chef_spawn {
                state.chef_spawn = pos;
            }
            let tile = decl
                .tiles
                .get(&char)
                .ok_or(anyhow!("char undefined {char:?}"))?;
            let tile = name_to_tile
                .get(tile)
                .ok_or(anyhow!("unknown tile {tile:?}"))?;
            state.set_tile(pos, TileIndex(*tile));
        }
    }

    Ok(())
}
