use std::any::{Any, TypeId};
use std::collections::HashMap;

use crate::world::World;

pub trait StateAgent {}

pub trait State: Any
{
    fn get_state_name(&self) -> &'static str;
    fn on_state_enter(&mut self, world: &mut World) {}
    fn on_state_exit(&mut self, world: &mut World) {}
    fn on_state_update(&mut self, world: &mut World, delta: f32) {}
    fn on_state_physics_update(&mut self, world: &mut World, delta: f32) {}
}

impl dyn State
{
    fn dyn_type_id(&self) -> std::any::TypeId
    {
        Any::type_id(self)
    }
}

pub struct StateTransition
{
    to_state_id: TypeId,
    condition: Box<dyn Fn(&World) -> bool>,
}

pub struct StateMachine
{
    state_transitions: HashMap<TypeId, Vec<StateTransition>>,
    current_state_id: TypeId,
    states: HashMap<TypeId, Box<dyn State>>,
    pub time_in_state: f32,
}

impl StateMachine
{
    pub fn new(enter_state: Box<dyn State>) -> Self
    {
        let state_id = enter_state.dyn_type_id();
        let mut states = HashMap::new();
        states.insert(state_id, enter_state);

        Self {
            state_transitions: HashMap::new(),
            current_state_id: state_id,
            states,
            time_in_state: 0.0,
        }
    }

    pub fn update(&mut self, world: &mut World, delta: f32)
    {
        if let Some(next_state_id) = self.get_transition_state_id(world)
        {
            self.time_in_state = 0.0;
            self.transition_to(world, next_state_id);
        }

        if let Some(current_state) = self.states.get_mut(&self.current_state_id)
        {
            current_state.on_state_update(world, delta);
        }

        self.time_in_state += delta;
    }

    fn get_transition_state_id(&self, world: &World) -> Option<TypeId>
    {
        if let Some(transitions) = self.state_transitions.get(&self.current_state_id)
        {
            for transition in transitions
            {
                if (transition.condition)(world)
                {
                    return Some(transition.to_state_id);
                }
            }
        }
        None
    }

    fn transition_to(&mut self, world: &mut World, new_state_id: TypeId)
    {
        if let Some(current_state) = self.states.get_mut(&self.current_state_id)
        {
            current_state.on_state_exit(world);
        }

        self.current_state_id = new_state_id;

        if let Some(new_state) = self.states.get_mut(&self.current_state_id)
        {
            new_state.on_state_enter(world);
        }
    }

    pub fn get_current_state(&self) -> Option<&dyn State>
    {
        self.states.get(&self.current_state_id).map(|b| b.as_ref())
    }

    pub fn get_current_state_mut(&mut self) -> Option<&mut dyn State>
    {
        self.states
            .get_mut(&self.current_state_id)
            .map(|b| b.as_mut())
    }

    pub fn add_state<T: State + 'static>(&mut self, state: T)
    {
        let state_id = TypeId::of::<T>();
        self.states.insert(state_id, Box::new(state));
    }

    pub fn add_transition<TFrom: State + 'static, TTo: State + 'static>(
        &mut self,
        condition: impl Fn(&World) -> bool + 'static,
    )
    {
        let from_id = TypeId::of::<TFrom>();
        let to_id = TypeId::of::<TTo>();

        let transitions = self.state_transitions.entry(from_id).or_default();
        transitions.push(StateTransition {
            to_state_id: to_id,
            condition: Box::new(condition),
        });
    }

    pub fn get_available_transitions_count(&self) -> usize
    {
        self.state_transitions
            .get(&self.current_state_id)
            .map(|transitions| transitions.len())
            .unwrap_or(0)
    }
}
