gled 2.28.5

gled is an application for creating animations and effects on artnet or dmx installations
use crate::storage::asset::midi_controller::{
    MidiControllerMapping, MidiInputAction, MidiInputBinding, MidiNamedSceneColorMapping,
    MidiOutputBinding, MidiOutputBindingKind, MidiValueOutput, MidiValueSource,
};

use super::value_source_converters::{color_source_label, value_source_label};

pub(super) fn input_action_label(action: &MidiInputAction) -> &'static str {
    match action {
        MidiInputAction::Tap => "Tap",
        MidiInputAction::SetMainDimmer => "Set Main Dimmer",
        MidiInputAction::SetBlackout => "Set Blackout",
        MidiInputAction::SetSpeedAdd => "Set Speed Add",
        MidiInputAction::SetSpeedMultiply => "Set Speed Multiply",
        MidiInputAction::SelectSceneDistributed => "Select Scene (Distributed 0-127)",
        MidiInputAction::SelectScene { .. } => "Select Scene",
        MidiInputAction::ToggleSceneActive { .. } => "Toggle Scene Active",
        MidiInputAction::SetSceneActive { .. } => "Set Scene Active",
        MidiInputAction::FlashScene { .. } => "Flash Scene",
        MidiInputAction::SetSceneOpacity { .. } => "Set Scene Opacity",
        MidiInputAction::SetSceneInputDimmer { .. } => "Set Scene Input Dimmer",
        MidiInputAction::SetSceneBeatOffset { .. } => "Set Scene Beat Offset",
        MidiInputAction::SetSceneIgnoreMainDimmer { .. } => "Set Scene Ignore Main Dimmer",
        MidiInputAction::SetSceneSetOffsetOnFlash { .. } => "Set Scene Set Offset On Flash",
        MidiInputAction::SetSceneEffectSettingF32 { .. } => "Set Effect Setting (n,n) f32",
        MidiInputAction::SetSceneEffectOpacity { .. } => "Set Effect Opacity",
        MidiInputAction::SetSceneEffectColorShift { .. } => "Set Effect Color Shift",
        MidiInputAction::SetSceneEffectBeatProgression { .. } => "Set Effect Beat Progression",
        MidiInputAction::SetSceneEffectBeatOffset { .. } => "Set Effect Beat Offset",
        MidiInputAction::SetSceneEffectSpeedExponent { .. } => "Set Effect Speed Exponent",
        MidiInputAction::SetSceneEffectGroupIndex { .. } => "Set Effect Group Index",
    }
}

pub(super) fn input_binding_matches_filter(binding: &MidiInputBinding, filter: &str) -> bool {
    if filter.is_empty() {
        return true;
    }
    let f = filter.to_lowercase();
    binding.name.to_lowercase().contains(&f)
        || binding.trigger.status.to_string().contains(filter)
        || binding.trigger.data1.to_string().contains(filter)
        || input_action_label(&binding.action)
            .to_lowercase()
            .contains(&f)
}

pub(super) fn output_binding_matches_filter(binding: &MidiOutputBinding, filter: &str) -> bool {
    if filter.is_empty() {
        return true;
    }
    let f = filter.to_lowercase();
    if binding.name.to_lowercase().contains(&f) {
        return true;
    }
    match &binding.kind {
        MidiOutputBindingKind::Value(v) => {
            v.status.to_string().contains(filter)
                || v.data1.to_string().contains(filter)
                || value_source_label(&v.source).to_lowercase().contains(&f)
        }
        MidiOutputBindingKind::SceneColorValue(v) => {
            v.status.to_string().contains(filter)
                || v.data1.to_string().contains(filter)
                || color_source_label(&v.source).to_lowercase().contains(&f)
                || "scene color value".contains(&f)
        }
    }
}

fn output_source_from_input_action(action: &MidiInputAction) -> Option<MidiValueSource> {
    match action {
        MidiInputAction::SetMainDimmer => Some(MidiValueSource::MainDimmer),
        MidiInputAction::SetBlackout => Some(MidiValueSource::Blackout {
            inverted: false,
            blink: false,
        }),
        MidiInputAction::SelectSceneDistributed => Some(MidiValueSource::SelectedSceneDistributed),
        MidiInputAction::SetSceneActive { target } => Some(MidiValueSource::SceneActive {
            target: target.clone(),
        }),
        MidiInputAction::FlashScene { target } => Some(MidiValueSource::SceneFlashed {
            target: target.clone(),
        }),
        MidiInputAction::SetSceneOpacity { target } => Some(match target {
            crate::storage::asset::midi_controller::MidiSceneTarget::Selected => {
                MidiValueSource::SelectedSceneOpacity
            }
            _ => MidiValueSource::SceneOpacity {
                target: target.clone(),
            },
        }),
        MidiInputAction::SetSceneInputDimmer { target } => Some(match target {
            crate::storage::asset::midi_controller::MidiSceneTarget::Selected => {
                MidiValueSource::SelectedSceneInputDimmer
            }
            _ => MidiValueSource::SceneInputDimmer {
                target: target.clone(),
            },
        }),
        MidiInputAction::SetSceneBeatOffset { target } => Some(match target {
            crate::storage::asset::midi_controller::MidiSceneTarget::Selected => {
                MidiValueSource::SelectedSceneBeatOffset
            }
            _ => MidiValueSource::SceneBeatOffset {
                target: target.clone(),
            },
        }),
        MidiInputAction::SetSceneIgnoreMainDimmer { target } => Some(match target {
            crate::storage::asset::midi_controller::MidiSceneTarget::Selected => {
                MidiValueSource::SelectedSceneIgnoreMainDimmer
            }
            _ => MidiValueSource::SceneIgnoreMainDimmer {
                target: target.clone(),
            },
        }),
        MidiInputAction::SetSceneSetOffsetOnFlash { target } => Some(match target {
            crate::storage::asset::midi_controller::MidiSceneTarget::Selected => {
                MidiValueSource::SelectedSceneSetOffsetOnFlash
            }
            _ => MidiValueSource::SceneSetOffsetOnFlash {
                target: target.clone(),
            },
        }),
        MidiInputAction::SetSceneEffectSettingF32 {
            target,
            effect_index,
            setting_index,
        } => Some(MidiValueSource::SceneEffectSettingF32 {
            target: target.clone(),
            effect_index: *effect_index,
            setting_index: *setting_index,
        }),
        MidiInputAction::SetSceneEffectOpacity {
            target,
            effect_index,
        } => Some(MidiValueSource::SceneEffectOpacity {
            target: target.clone(),
            effect_index: *effect_index,
        }),
        MidiInputAction::SetSceneEffectColorShift {
            target,
            effect_index,
        } => Some(MidiValueSource::SceneEffectColorShift {
            target: target.clone(),
            effect_index: *effect_index,
        }),
        MidiInputAction::SetSceneEffectBeatProgression {
            target,
            effect_index,
        } => Some(MidiValueSource::SceneEffectBeatProgression {
            target: target.clone(),
            effect_index: *effect_index,
        }),
        MidiInputAction::SetSceneEffectBeatOffset {
            target,
            effect_index,
        } => Some(MidiValueSource::SceneEffectBeatOffset {
            target: target.clone(),
            effect_index: *effect_index,
        }),
        MidiInputAction::SetSceneEffectSpeedExponent {
            target,
            effect_index,
        } => Some(MidiValueSource::SceneEffectSpeedExponent {
            target: target.clone(),
            effect_index: *effect_index,
        }),
        MidiInputAction::SetSceneEffectGroupIndex {
            target,
            effect_index,
        } => Some(MidiValueSource::SceneEffectGroupIndex {
            target: target.clone(),
            effect_index: *effect_index,
        }),
        MidiInputAction::Tap
        | MidiInputAction::SetSpeedAdd
        | MidiInputAction::SetSpeedMultiply
        | MidiInputAction::SelectScene { .. }
        | MidiInputAction::ToggleSceneActive { .. } => None,
    }
}

pub(super) fn add_matching_output_binding(
    mapping: &mut MidiControllerMapping,
    input_name: &str,
    input_status: u8,
    input_data1: u8,
    action: &MidiInputAction,
) -> bool {
    let source =
        output_source_from_input_action(action).unwrap_or(MidiValueSource::SelectedSceneOpacity);

    let already_exists = mapping.output_bindings.iter().any(|binding| {
        matches!(
            &binding.kind,
            MidiOutputBindingKind::Value(value)
                if value.status == input_status
                    && value.data1 == input_data1
                    && value.source == source
        )
    });

    if already_exists {
        return false;
    }

    mapping.output_bindings.insert(
        0,
        MidiOutputBinding {
            name: if input_name.is_empty() {
                "Output".to_owned()
            } else {
                format!("{} Output", input_name)
            },
            kind: MidiOutputBindingKind::Value(MidiValueOutput {
                status: input_status,
                data1: input_data1,
                min: 0,
                max: 127,
                active_value: 127,
                source,
            }),
        },
    );

    true
}

pub(super) fn output_binding_status_data1(
    binding: &MidiOutputBinding,
    _color_mappings: &[MidiNamedSceneColorMapping],
) -> Option<(u8, u8)> {
    match &binding.kind {
        MidiOutputBindingKind::Value(value) => Some((value.status, value.data1)),
        MidiOutputBindingKind::SceneColorValue(scene_color) => {
            Some((scene_color.status, scene_color.data1))
        }
    }
}

pub(super) fn set_output_binding_status_data1(
    binding: &mut MidiOutputBinding,
    _color_mappings: &mut [MidiNamedSceneColorMapping],
    status: u8,
    data1: u8,
) -> bool {
    match &mut binding.kind {
        MidiOutputBindingKind::Value(value) => {
            let changed = value.status != status || value.data1 != data1;
            value.status = status;
            value.data1 = data1;
            changed
        }
        MidiOutputBindingKind::SceneColorValue(scene_color) => {
            let changed = scene_color.status != status || scene_color.data1 != data1;
            scene_color.status = status;
            scene_color.data1 = data1;
            changed
        }
    }
}

pub(super) fn midi_status_label(status: u8) -> String {
    if status >= 0xF0 {
        return format!("System (0x{status:02X})");
    }

    let channel = (status & 0x0F) + 1;
    let kind = match status & 0xF0 {
        0x80 => "Note Off",
        0x90 => "Note On",
        0xA0 => "Poly Aftertouch",
        0xB0 => "Control Change",
        0xC0 => "Program Change",
        0xD0 => "Channel Pressure",
        0xE0 => "Pitch Bend",
        _ => "Unknown",
    };

    format!("{kind} (ch {channel}, 0x{status:02X})")
}