use jack::RawMidi;
use kanal::{Receiver, Sender};
use std::io::{self, Write};

#[derive(Debug)]
enum MidiCommand {
    ControlChange { cc: u8, value: u8 },
    Quit,
}

struct MidiSender {
    midi_out: jack::Port<jack::MidiOut>,
    command_receiver: Receiver<MidiCommand>,
}

impl MidiSender {
    fn new(
        client: &jack::Client,
        command_receiver: Receiver<MidiCommand>,
    ) -> Result<Self, Box<dyn std::error::Error>> {
        let midi_out = client
            .register_port("midi_out", jack::MidiOut::default())
            .map_err(|e| format!("Could not create MIDI output port: {}", e))?;

        Ok(Self {
            midi_out,
            command_receiver,
        })
    }
}

impl jack::ProcessHandler for MidiSender {
    fn process(&mut self, _client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control {
        let mut midi_writer = self.midi_out.writer(ps);

        // Process all pending commands
        while let Ok(Some(command)) = self.command_receiver.try_recv() {
            match command {
                MidiCommand::ControlChange { cc, value } => {
                    let midi_data = [0xB0, cc, value]; // Control Change on channel 1
                    if let Err(e) = midi_writer.write(&RawMidi {
                        time: 0,
                        bytes: &midi_data,
                    }) {
                        eprintln!("Failed to send MIDI: {}", e);
                    }
                }
                MidiCommand::Quit => return jack::Control::Quit,
            }
        }

        jack::Control::Continue
    }
}

fn show_help() {
    println!("\nFCB1010 MIDI simulator");
    println!("=====================================");
    println!("Button mappings:");
    println!("1 - Button 1 (CC 20): Record/Arm / Tap Tempo");
    println!("2 - Button 2 (CC 21): Play/Mute Track / Click Toggle");
    println!("3 - Button 3 (CC 22): Solo Track");
    println!("4 - Button 4 (CC 23): Overdub");
    println!("5 - Button 5 (CC 24): Clear Track / Clear Column");
    println!("6 - Button 6 (CC 25): Column 1 Select");
    println!("7 - Button 7 (CC 26): Column 2 Select");
    println!("8 - Button 8 (CC 27): Column 3 Select");
    println!("9 - Button 9 (CC 28): Column 4 Select");
    println!("0 - Button 10 (CC 29): Column 5 Select");
    println!("u - UP (CC 30): Row Up / Mode Switch");
    println!("d - DOWN (CC 31): Row Down / Mode Switch");
    println!("e - Expression A (CC 1): Track/Click Volume (0-127)");
    println!("m - Master Volume (CC 7): Master Volume (0-127)");
    println!("h - Show this help");
    println!("q - Quit\n");
}

fn get_expression_value(prompt: &str) -> Result<u8, Box<dyn std::error::Error>> {
    print!("{}", prompt);
    io::stdout().flush()?;

    let mut input = String::new();
    io::stdin().read_line(&mut input)?;

    let value: u8 = input
        .trim()
        .parse()
        .map_err(|_| "Invalid number. Please enter 0-127")?;

    if value > 127 {
        return Err("Value must be 0-127".into());
    }

    Ok(value)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("Starting FCB1010 MIDI Simulator...");

    // Create JACK client
    let (client, status) = jack::Client::new("FCB1010", jack::ClientOptions::NO_START_SERVER)?;
    if !status.is_empty() {
        eprintln!("JACK client status: {:?}", status);
    }

    // Create command channel
    let (command_sender, command_receiver): (Sender<MidiCommand>, Receiver<MidiCommand>) =
        kanal::bounded(32);

    // Create MIDI sender
    let midi_sender = MidiSender::new(&client, command_receiver)?;

    // Activate client
    let _active_client = client.activate_async((), midi_sender)?;

    show_help();

    // Main input loop
    loop {
        print!("Command (h for help): ");
        io::stdout().flush()?;

        let mut input = String::new();
        io::stdin().read_line(&mut input)?;
        let input = input.trim().to_lowercase();

        if input.is_empty() {
            continue;
        }

        let first_char = input.chars().next().unwrap();

        let command = match first_char {
            '1' => Some(MidiCommand::ControlChange { cc: 20, value: 127 }),
            '2' => Some(MidiCommand::ControlChange { cc: 21, value: 127 }),
            '3' => Some(MidiCommand::ControlChange { cc: 22, value: 127 }),
            '4' => Some(MidiCommand::ControlChange { cc: 23, value: 127 }),
            '5' => Some(MidiCommand::ControlChange { cc: 24, value: 127 }),
            '6' => Some(MidiCommand::ControlChange { cc: 25, value: 127 }),
            '7' => Some(MidiCommand::ControlChange { cc: 26, value: 127 }),
            '8' => Some(MidiCommand::ControlChange { cc: 27, value: 127 }),
            '9' => Some(MidiCommand::ControlChange { cc: 28, value: 127 }),
            '0' => Some(MidiCommand::ControlChange { cc: 29, value: 127 }),
            'u' => Some(MidiCommand::ControlChange { cc: 30, value: 127 }),
            'd' => Some(MidiCommand::ControlChange { cc: 31, value: 127 }),
            'e' => match get_expression_value("Expression A value (0-127): ") {
                Ok(value) => Some(MidiCommand::ControlChange { cc: 1, value }),
                Err(e) => {
                    eprintln!("Error: {}", e);
                    None
                }
            },
            'm' => match get_expression_value("Master volume value (0-127): ") {
                Ok(value) => Some(MidiCommand::ControlChange { cc: 7, value }),
                Err(e) => {
                    eprintln!("Error: {}", e);
                    None
                }
            },
            'h' => {
                show_help();
                None
            }
            'q' => {
                println!("Exiting...");
                command_sender.send(MidiCommand::Quit)?;
                break;
            }
            _ => {
                println!("Unknown command '{}'. Press 'h' for help.", first_char);
                None
            }
        };

        if let Some(cmd) = command {
            if let MidiCommand::ControlChange { cc, value } = &cmd {
                println!("Sending CC {} with value {}", cc, value);
            }
            command_sender.send(cmd)?;
        }
    }

    Ok(())
}
