use amethyst::{
    ecs::{Entities, Join, ReadStorage},
    Error,
};
use game_input::InputControlled;
use game_input_model::{AxisMoveEventData, ControlActionEventData, ControlInputEvent};
use stdio_spi::{MapperSystemData, StdinMapper};
use typename_derive::TypeName;

use crate::{ControlArgs, ControlInputEventArgs, GameInputStdioError};

#[derive(Debug)]
pub struct ControlInputEventStdinMapperData;

impl<'s> MapperSystemData<'s> for ControlInputEventStdinMapperData {
    type SystemData = (Entities<'s>, ReadStorage<'s, InputControlled>);
}

/// Builds a `ControlInputEvent` from stdin tokens.
#[derive(Debug, TypeName)]
pub struct ControlInputEventStdinMapper;

impl StdinMapper for ControlInputEventStdinMapper {
    type SystemData = ControlInputEventStdinMapperData;
    type Event = ControlInputEvent;
    type Args = ControlInputEventArgs;

    fn map(
        (entities, input_controlleds): &<Self::SystemData as MapperSystemData>::SystemData,
        args: Self::Args,
    ) -> Result<Self::Event, Error> {
        let ControlInputEventArgs {
            controller,
            control,
        } = &args;

        (entities, input_controlleds)
            .join()
            .find(|(_e, input_controlled)| input_controlled.controller_id == *controller)
            .map(|(entity, _input_controlled)| match control {
                ControlArgs::Axis { axis, value } => {
                    ControlInputEvent::AxisMoved(AxisMoveEventData {
                        entity,
                        axis: *axis,
                        value: *value,
                    })
                }
                ControlArgs::ActionPressed { action } => {
                    ControlInputEvent::ControlActionPress(ControlActionEventData {
                        entity,
                        control_action: *action,
                    })
                }
                ControlArgs::ActionReleased { action } => {
                    ControlInputEvent::ControlActionRelease(ControlActionEventData {
                        entity,
                        control_action: *action,
                    })
                }
            })
            .ok_or_else(|| {
                let existent_controllers = input_controlleds
                    .join()
                    .map(|input_controlled| input_controlled.controller_id)
                    .collect::<Vec<_>>();
                Error::new(GameInputStdioError::EntityWithControllerIdNotFound {
                    controller_id: *controller,
                    existent_controllers,
                })
            })
    }
}
