use crate::attribute_set::EvdevEnum;
use crate::compat::{ff_condition_effect, ff_envelope, ff_replay, ff_trigger};
use crate::constants::FFEffectCode;
use crate::sys;

/// Describes a generic force feedback effect envelope.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FFEnvelope {
    /// How long the attack should last in milliseconds.
    pub attack_length: u16,
    /// The level of the attack at the beginning of the attack.
    pub attack_level: u16,
    /// How long the fade should last in milliseconds.
    pub fade_length: u16,
    /// The level of the fade at the end of the fade.
    pub fade_level: u16,
}

impl From<ff_envelope> for FFEnvelope {
    fn from(value: ff_envelope) -> Self {
        Self {
            attack_length: value.attack_length,
            attack_level: value.attack_level,
            fade_length: value.fade_length,
            fade_level: value.fade_level,
        }
    }
}

impl From<FFEnvelope> for ff_envelope {
    fn from(other: FFEnvelope) -> Self {
        ff_envelope {
            attack_length: other.attack_length,
            attack_level: other.attack_level,
            fade_length: other.fade_length,
            fade_level: other.fade_level,
        }
    }
}

/// Describes the waveform for periodic force feedback effects.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FFWaveform {
    /// Square waveform.
    Square,
    /// Triangle waveform.
    Triangle,
    /// Sine waveform.
    Sine,
    /// Sawtooth up waveform.
    SawUp,
    /// Sawtooth down waveform.
    SawDown,
}

impl From<FFWaveform> for FFEffectCode {
    fn from(other: FFWaveform) -> Self {
        match other {
            FFWaveform::Square => FFEffectCode::FF_SQUARE,
            FFWaveform::Triangle => FFEffectCode::FF_TRIANGLE,
            FFWaveform::Sine => FFEffectCode::FF_SINE,
            FFWaveform::SawUp => FFEffectCode::FF_SAW_UP,
            FFWaveform::SawDown => FFEffectCode::FF_SAW_DOWN,
        }
    }
}

/// Describes a spring or friction force feedback effect.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FFCondition {
    /// The maximum level when the joystick is moved all the way to the right.
    pub right_saturation: u16,
    /// The maximum level when the joystick is moved all the way to the left.
    pub left_saturation: u16,
    /// The coefficient that controls how fast the force grows when the joystick moves to the
    /// right.
    pub right_coefficient: i16,
    /// The coefficient that controls how fast the force grows when the joystick moves to the left.
    pub left_coefficient: i16,
    /// The size of the dead zone, which is the zone where no force is produced.
    pub deadband: u16,
    /// The position of the dead zone.
    pub center: i16,
}

impl From<ff_condition_effect> for FFCondition {
    fn from(value: ff_condition_effect) -> Self {
        Self {
            right_saturation: value.right_saturation,
            left_saturation: value.left_saturation,
            right_coefficient: value.right_coeff,
            left_coefficient: value.left_coeff,
            deadband: value.deadband,
            center: value.center,
        }
    }
}

impl From<FFCondition> for ff_condition_effect {
    fn from(other: FFCondition) -> Self {
        ff_condition_effect {
            right_saturation: other.right_saturation,
            left_saturation: other.left_saturation,
            right_coeff: other.right_coefficient,
            left_coeff: other.left_coefficient,
            deadband: other.deadband,
            center: other.center,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FFEffectKind {
    Damper,
    Inertia,
    Constant {
        /// The strength of the effect.
        level: i16,
        /// Envelope data.
        envelope: FFEnvelope,
    },
    Ramp {
        /// The strength at the beginning of the effect.
        start_level: i16,
        /// The strength at the end of the effect.
        end_level: i16,
        /// Envelope data.
        envelope: FFEnvelope,
    },
    Periodic {
        /// The kind of waveform to use for the force feedback effect.
        waveform: FFWaveform,
        /// The period of the wave in milliseconds.
        period: u16,
        /// The peak value or amplitude of the wave.
        magnitude: i16,
        /// The mean value of the wave (roughly).
        offset: i16,
        /// The horizontal shift.
        phase: u16,
        /// Envelope data.
        envelope: FFEnvelope,
    },
    Spring {
        /// Condition data for each axis.
        condition: [FFCondition; 2],
    },
    Friction {
        /// Condition data for each axis.
        condition: [FFCondition; 2],
    },
    Rumble {
        /// The magnitude of the heavy motor.
        strong_magnitude: u16,
        /// The magnitude of the light motor.
        weak_magnitude: u16,
    },
}

impl From<FFEffectKind> for FFEffectCode {
    fn from(other: FFEffectKind) -> Self {
        match other {
            FFEffectKind::Damper => FFEffectCode::FF_DAMPER,
            FFEffectKind::Inertia => FFEffectCode::FF_INERTIA,
            FFEffectKind::Constant { .. } => FFEffectCode::FF_CONSTANT,
            FFEffectKind::Ramp { .. } => FFEffectCode::FF_RAMP,
            FFEffectKind::Periodic { .. } => FFEffectCode::FF_PERIODIC,
            FFEffectKind::Spring { .. } => FFEffectCode::FF_SPRING,
            FFEffectKind::Friction { .. } => FFEffectCode::FF_FRICTION,
            FFEffectKind::Rumble { .. } => FFEffectCode::FF_RUMBLE,
        }
    }
}

/// Trigger information for the force feedback effect.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct FFTrigger {
    /// The button number that triggers the force feedback effect.
    pub button: u16,
    /// How long to wait before the force feedback effect can be triggered again in milliseconds.
    pub interval: u16,
}

impl From<ff_trigger> for FFTrigger {
    fn from(value: ff_trigger) -> Self {
        Self {
            button: value.button,
            interval: value.interval,
        }
    }
}

impl From<FFTrigger> for ff_trigger {
    fn from(other: FFTrigger) -> Self {
        ff_trigger {
            button: other.button,
            interval: other.interval,
        }
    }
}

/// Scheduling information for the force feedback effect.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct FFReplay {
    /// How long the force feedback effect should last in milliseconds.
    pub length: u16,
    /// How long to wait before the force feedback effect should play in milliseconds.
    pub delay: u16,
}

impl From<ff_replay> for FFReplay {
    fn from(value: ff_replay) -> Self {
        Self {
            length: value.length,
            delay: value.delay,
        }
    }
}

impl From<FFReplay> for ff_replay {
    fn from(other: FFReplay) -> Self {
        ff_replay {
            length: other.length,
            delay: other.delay,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct FFEffectData {
    /// The direction of the force feedback effect.
    pub direction: u16,
    /// Trigger conditions.
    pub trigger: FFTrigger,
    /// Scheduling of the effect.
    pub replay: FFReplay,
    /// The type of force feedback effect and any associated parameters.
    pub kind: FFEffectKind,
}

impl From<sys::ff_effect> for FFEffectData {
    fn from(value: sys::ff_effect) -> Self {
        let kind = match FFEffectCode::from_index(value.type_ as usize) {
            FFEffectCode::FF_DAMPER => FFEffectKind::Damper,
            FFEffectCode::FF_INERTIA => FFEffectKind::Inertia,
            FFEffectCode::FF_CONSTANT => {
                let constant = unsafe { value.u.constant };

                FFEffectKind::Constant {
                    level: constant.level,
                    envelope: constant.envelope.into(),
                }
            }
            FFEffectCode::FF_RAMP => {
                let ramp = unsafe { value.u.ramp };

                FFEffectKind::Ramp {
                    start_level: ramp.start_level,
                    end_level: ramp.end_level,
                    envelope: ramp.envelope.into(),
                }
            }
            FFEffectCode::FF_PERIODIC => {
                let periodic = unsafe { value.u.periodic };

                FFEffectKind::Periodic {
                    waveform: match FFEffectCode::from_index(periodic.waveform as usize) {
                        FFEffectCode::FF_SQUARE => FFWaveform::Square,
                        FFEffectCode::FF_TRIANGLE => FFWaveform::Triangle,
                        FFEffectCode::FF_SINE => FFWaveform::Sine,
                        FFEffectCode::FF_SAW_UP => FFWaveform::SawUp,
                        FFEffectCode::FF_SAW_DOWN => FFWaveform::SawDown,
                        _ => unreachable!(),
                    },
                    period: periodic.period,
                    magnitude: periodic.magnitude,
                    offset: periodic.offset,
                    phase: periodic.phase,
                    envelope: periodic.envelope.into(),
                }
            }
            FFEffectCode::FF_SPRING => {
                let condition = unsafe { value.u.condition };

                FFEffectKind::Spring {
                    condition: [condition[0].into(), condition[1].into()],
                }
            }
            FFEffectCode::FF_FRICTION => {
                let condition = unsafe { value.u.condition };

                FFEffectKind::Friction {
                    condition: [condition[0].into(), condition[1].into()],
                }
            }
            FFEffectCode::FF_RUMBLE => {
                let rumble = unsafe { value.u.rumble };

                FFEffectKind::Rumble {
                    strong_magnitude: rumble.strong_magnitude,
                    weak_magnitude: rumble.weak_magnitude,
                }
            }
            _ => unreachable!(),
        };

        Self {
            direction: value.direction,
            trigger: value.trigger.into(),
            replay: value.replay.into(),
            kind,
        }
    }
}

impl From<FFEffectData> for sys::ff_effect {
    fn from(other: FFEffectData) -> Self {
        let mut effect: sys::ff_effect = unsafe { std::mem::zeroed() };

        let type_: FFEffectCode = other.kind.into();
        effect.type_ = type_.0;
        effect.direction = other.direction;
        effect.trigger = other.trigger.into();
        effect.replay = other.replay.into();

        match other.kind {
            FFEffectKind::Constant { level, envelope } => {
                effect.u.constant.level = level;
                effect.u.constant.envelope = envelope.into();
            }
            FFEffectKind::Ramp {
                start_level,
                end_level,
                envelope,
            } => {
                effect.u.ramp.start_level = start_level;
                effect.u.ramp.end_level = end_level;
                effect.u.ramp.envelope = envelope.into();
            }
            FFEffectKind::Periodic {
                waveform,
                period,
                magnitude,
                offset,
                phase,
                envelope,
            } => {
                let waveform: FFEffectCode = waveform.into();
                effect.u.periodic.waveform = waveform.0;
                effect.u.periodic.period = period;
                effect.u.periodic.magnitude = magnitude;
                effect.u.periodic.offset = offset;
                effect.u.periodic.phase = phase;
                effect.u.periodic.envelope = envelope.into();
            }
            FFEffectKind::Spring { condition } | FFEffectKind::Friction { condition } => {
                effect.u.condition = [condition[0].into(), condition[1].into()];
            }
            FFEffectKind::Rumble {
                strong_magnitude,
                weak_magnitude,
            } => {
                effect.u.rumble.strong_magnitude = strong_magnitude;
                effect.u.rumble.weak_magnitude = weak_magnitude;
            }
            _ => (),
        }

        effect
    }
}
