//! Game Parser and Data types
use std::{collections::HashMap, marker::PhantomData};

use character_sheet::{AccessLevel, Character, CharacterShort, EntryType};
use scene::{Party, Scene, TokenInfo};
use serde::{Deserialize, Serialize};

pub mod character_sheet;
pub mod chat_message;
pub mod entry;
pub mod scene;

pub trait GameImpl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Serialize + Deserialize<'a>>
{
    /// Creates a new game
    fn new() -> Self;

    // Character management
    /// Creates a new character, returning the character id
    fn create_character(&mut self) -> usize;
    fn display_character(&self, character_id: usize, access: AccessLevel) -> Vec<Option<(String, EntryType)>>;
    fn characters(&self) -> Vec<usize>;
    fn character_short(&self, character_id: usize) -> Option<CharacterShort>;
    // Scenes
    /// the list of available scenes
    fn scenes(&self) -> Vec<usize>;
    fn get_scene(&self, id: usize) -> Option<&Scene>;
    fn create_token(&mut self, scene_id: usize, character: String, img_source: String, x: f32, y: f32) -> usize;
    fn move_token(&mut self, scene_id: usize, token_id: usize, x: f32, y: f32) -> bool;
    fn token_info(&self, scene: usize, token_id: usize) -> Option<&TokenInfo>;
    fn available_tokens(&self, scene: usize) -> Vec<usize>;
}

#[derive(Serialize, Deserialize)]
pub struct Game<C: Character<A> + Serialize, A: entry::GameEntry + Serialize> {
    _a: PhantomData<A>,
    characters: Vec<(C, CharacterInfo)>,
    scenes: HashMap<usize, Scene>,
}
impl<'a, C: Character<A> + Serialize + Deserialize<'a>, A: entry::GameEntry + Serialize + Deserialize<'a>>
    GameImpl<'a, C, A> for Game<C, A>
{
    fn new() -> Self {
        let mut tokens = HashMap::new();
        tokens.insert(
            0,
            TokenInfo {
                character: "Adams".to_string(),
                img_source: "assets/pf2r/tokens/adams.png".to_string(),
                x: 2.0,
                y: 2.0,
            },
        );
        tokens.insert(
            1,
            TokenInfo {
                character: "Tim".to_string(),
                img_source: "assets/pf2r/tokens/tim.png".to_string(),
                x: 0.0,
                y: 2.0,
            },
        );
        let mut scenes = HashMap::new();
        scenes.insert(
            0,
            Scene {
                title: "Dungeon".to_string(),
                visible_to_users: true,
                map: Some(scene::Map {
                    background: "assets/pf2r/maps/testmap.jpg".to_string(),
                    grid_cell_size: 150.0,
                    grid_offset: [80.0, 35.0],
                    tokens,
                }),
                characters: vec![(0, Party(true))],
            },
        );
        scenes.insert(
            1,
            Scene {
                title: "Basement".to_string(),
                visible_to_users: false,
                map: Some(scene::Map {
                    background: "assets/pf2r/maps/testmap.jpg".to_string(),
                    grid_cell_size: 300.0,
                    grid_offset: [80.0, 35.0],
                    tokens: HashMap::new(),
                }),
                characters: vec![(0, Party(true))],
            },
        );
        Self {
            _a: PhantomData,
            characters: Vec::new(),
            scenes,
        }
    }
    fn create_character(&mut self) -> usize {
        self.characters.push((C::default(), CharacterInfo::default()));
        self.characters.len() - 1
    }
    fn move_token(&mut self, scene: usize, token_id: usize, x: f32, y: f32) -> bool {
        let token = self
            .scenes
            .get_mut(&scene)
            .map(|s| s.map.as_mut())
            .flatten()
            .map(|m| m.tokens.get_mut(&token_id))
            .flatten();
        if let Some(ti) = token {
            ti.x = x;
            ti.y = y;
            true
        } else {
            false
        }
    }
    fn token_info(&self, scene: usize, token_id: usize) -> Option<&TokenInfo> {
        let token = self
            .scenes
            .get(&scene)
            .map(|s| s.map.as_ref())
            .flatten()
            .map(|m| m.tokens.get(&token_id))
            .flatten();
        if let Some(ti) = token { Some(ti) } else { None }
    }

    fn available_tokens(&self, scene: usize) -> Vec<usize> {
        self.scenes
            .get(&scene)
            .map(|s| s.map.as_ref())
            .flatten()
            // this map feels stupid but keys() turns into a &usize iterator so :shrug:
            .map(|m| m.tokens.keys().into_iter().map(|k| *k).collect())
            .unwrap_or(Vec::new())
    }

    fn create_token(&mut self, scene: usize, character: String, img_source: String, x: f32, y: f32) -> usize {
        let mut id = 0;
        let tokens = self
            .scenes
            .get_mut(&scene)
            .map(|s| s.map.as_mut())
            .flatten()
            .map(|m| &mut m.tokens);
        if let Some(tokens) = tokens {
            while tokens.contains_key(&id) {
                id += 1;
            }
            tokens.insert(
                id,
                TokenInfo {
                    character,
                    img_source,
                    x,
                    y,
                },
            );
            id
        } else {
            0
        }
    }

    fn display_character(&self, character_id: usize, access: AccessLevel) -> Vec<Option<(String, EntryType)>> {
        self.characters
            .get(character_id)
            .map(|c| c.0.display(access))
            .unwrap_or(Vec::new())
    }

    fn characters(&self) -> Vec<usize> {
        // this is stupid i should redo the characters data struct to actually have ids rather than use an index
        // (as deletion of a character f-s up the ids)
        (0_usize..self.characters.len()).collect()
    }

    fn character_short(&self, character_id: usize) -> Option<CharacterShort> {
        self.characters.get(character_id).map(|c| c.0.short(character_id))
    }

    fn scenes(&self) -> Vec<usize> {
        self.scenes.keys().map(|k| *k).collect()
    }

    fn get_scene(&self, id: usize) -> Option<&Scene> {
        self.scenes.get(&id)
    }
}

#[derive(Serialize, Deserialize, Default, Debug, Clone)]
struct CharacterInfo {
    pub owner: String,
    pub party: Party,
}
