#[cfg(feature = "throughput")]
use byte_unit::AdjustedByte;
#[cfg(feature = "rtt")]
use chrono::Duration;
use clap::Parser;
use rocket::routes;
use std::{
    collections::BTreeMap,
    io::Error,
    sync::{Arc, Condvar, Mutex},
    time::{SystemTime, UNIX_EPOCH},
};
use tokio::{fs::File, io::AsyncWriteExt, sync::mpsc};
use tracing::{debug, error, info, trace};

use crate::cli::Cli;
use crate::endpoints::{get_counter, start_demo};

#[cfg(feature = "throughput")]
mod bps;
mod cli;
mod endpoints;
#[cfg(feature = "throughput")]
mod ffmpeg;
mod gps;
#[cfg(feature = "rtt")]
mod rttps;

/// The maximum length of a entry/line in the csv file
const MAX_CSV_ENTRY_LENGTH: usize = 55; // 55 is the realistic upper bound 100 to be safe

/// The buffer that stores the data entries before they are sent out to the http endpoint
const ENTRIES_BUFFER_LENGTH: usize = 100;

type CsvEntries = [CsvEntry; ENTRIES_BUFFER_LENGTH];
type CsvEntry = [char; MAX_CSV_ENTRY_LENGTH];

pub type SharedCounter = Arc<(Mutex<State>, Condvar)>;

/// The state of the app, specifically used for the API endpoint
#[cfg(all(feature = "throughput", feature = "rtt"))]
#[derive(Debug)]
pub struct State {
    // Whether program should be started
    started: bool,
    // To configure IP of the endpoint that should receive the collected data
    endpoint_ip: Option<Vec<String>>,
    // To configure IP of the ping-target after starting
    ping_ip: Option<String>,
    // To configure IP of the stream url for ffmpeg
    stream_url: Option<String>,
    // Amount of counted data packages
    counter: usize,
    // Time of last check on endpoint
    last_check: u64,
    // Push Data
    entries: CsvEntries,
    // Amount of counted data packages
    entries_counter: usize,
}

/// The state of the app, specifically used for the API endpoint
#[cfg(all(not(feature = "throughput"), feature = "rtt"))]
#[derive(Debug)]
pub struct State {
    // Whether program should be started
    started: bool,
    // To configure IP of the endpoint that should receive the collected data
    endpoint_ip: Option<Vec<String>>,
    // To configure IP of the ping-target after starting
    ping_ip: Option<String>,
    // Amount of counted data packages
    counter: usize,
    // Time of last check on endpoint
    last_check: u64,
    // Push Data
    entries: CsvEntries,
    // Amount of counted data packages
    entries_counter: usize,
}

/// The state of the app, specifically used for the API endpoint
#[cfg(all(feature = "throughput", not(feature = "rtt")))]
#[derive(Debug)]
pub struct State {
    // Whether program should be started
    started: bool,
    // To configure IP of the endpoint that should receive the collected data
    endpoint_ip: Option<Vec<String>>,
    // To configure IP of the stream url for ffmpeg
    stream_url: Option<String>,
    // Amount of counted data packages
    counter: usize,
    // Time of last check on endpoint
    last_check: u64,
    // Push Data
    entries: CsvEntries,
    // Amount of counted data packages
    entries_counter: usize,
}

#[cfg(all(feature = "throughput", feature = "rtt"))]
impl Default for State {
    fn default() -> Self {
        State {
            started: false,
            endpoint_ip: None,
            ping_ip: None,
            stream_url: None,
            counter: 0,
            last_check: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs(),
            entries: [[' '; MAX_CSV_ENTRY_LENGTH]; ENTRIES_BUFFER_LENGTH],
            entries_counter: 0,
        }
    }
}

#[cfg(all(not(feature = "throughput"), feature = "rtt"))]
impl Default for State {
    fn default() -> Self {
        State {
            started: false,
            endpoint_ip: None,
            ping_ip: None,
            counter: 0,
            last_check: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs(),
            entries: [[' '; MAX_CSV_ENTRY_LENGTH]; ENTRIES_BUFFER_LENGTH],
            entries_counter: 0,
        }
    }
}

#[cfg(all(feature = "throughput", not(feature = "rtt")))]
impl Default for State {
    fn default() -> Self {
        State {
            started: false,
            endpoint_ip: None,
            stream_url: None,
            counter: 0,
            last_check: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs(),
            entries: [[' '; MAX_CSV_ENTRY_LENGTH]; ENTRIES_BUFFER_LENGTH],
            entries_counter: 0,
        }
    }
}

#[derive(Debug)]
struct DataMsg {
    timestamp: u64,
    entry: DataEntry,
}

#[derive(Debug)]
#[cfg(all(feature = "throughput", feature = "rtt"))]
struct DataEntry {
    lat: Option<f64>,
    lon: Option<f64>,
    gps_count: u64,
    byte: Option<AdjustedByte>,
    rtt: Option<Duration>,
}

#[derive(Debug)]
#[cfg(all(not(feature = "throughput"), feature = "rtt"))]
struct DataEntry {
    lat: Option<f64>,
    lon: Option<f64>,
    gps_count: u64,
    rtt: Option<Duration>,
}

#[derive(Debug)]
#[cfg(all(feature = "throughput", not(feature = "rtt")))]
struct DataEntry {
    lat: Option<f64>,
    lon: Option<f64>,
    gps_count: u64,
    byte: Option<AdjustedByte>,
}

impl DataEntry {
    fn combine(&self, other: &DataEntry) -> DataEntry {
        // trace!("Compare: Self: {:?}, Other: {:?}", self, other);

        let lat = match (self.lat, other.lat) {
            (Some(lat1), Some(lat2)) => Some(lat1 + lat2),
            (None, Some(lat2)) => Some(lat2),
            (Some(lat1), None) => Some(lat1),
            (None, None) => None,
        };

        let lon = match (self.lon, other.lon) {
            (Some(lon1), Some(lon2)) => Some(lon1 + lon2),
            (None, Some(lon2)) => Some(lon2),
            (Some(lon1), None) => Some(lon1),
            (None, None) => None,
        };

        let gps_count = self.gps_count + other.gps_count;

        #[cfg(all(feature = "throughput", feature = "rtt"))]
        {
            let byte = self.byte.or(other.byte);

            let rtt = match (self.rtt, other.rtt) {
                (Some(rtt1), Some(rtt2)) => Some((rtt1 + rtt2) / 2),
                (Some(rtt1), _) => Some(rtt1),
                (None, Some(rtt2)) => Some(rtt2),
                (None, None) => None,
            };

            DataEntry {
                lat,
                lon,
                gps_count,
                byte,
                rtt,
            }
        }

        #[cfg(all(not(feature = "throughput"), feature = "rtt"))]
        {
            let rtt = match (self.rtt, other.rtt) {
                (Some(rtt1), Some(rtt2)) => Some((rtt1 + rtt2) / 2),
                (Some(rtt1), _) => Some(rtt1),
                (None, Some(rtt2)) => Some(rtt2),
                (None, None) => None,
            };

            DataEntry {
                lat,
                lon,
                gps_count,
                rtt,
            }
        }

        #[cfg(all(feature = "throughput", not(feature = "rtt")))]
        {
            let byte = self.byte.or(other.byte);

            DataEntry {
                lat,
                lon,
                gps_count,
                byte,
            }
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing_subscriber::fmt::init();

    let args = Cli::parse();

    #[cfg(feature = "throughput")]
    let pcap_file = &args.pcap;

    let state: SharedCounter = Arc::new((Mutex::new(State::default()), Condvar::new()));

    debug!("Starting API...");
    let state_api = state.clone();
    let api_handler = rocket::build()
        .mount("/", routes![get_counter])
        .mount("/", routes![start_demo])
        .manage(state_api)
        .launch();

    let _api_join_handle = tokio::spawn(api_handler);
    {
        info!("Waiting for GET to /demo/start...");
        let state_started = state.clone();
        let (lock, cvar) = &*state_started;
        let mut started = lock.lock().unwrap();
        while !started.started {
            started = cvar.wait(started).unwrap()
        }
    }

    let endpoint_ip: Vec<String> = {
        let state_endpoint = state.clone();
        let (lock, _) = &*state_endpoint;
        let local_state = lock.lock().unwrap();
        let e = if let Some(endpoint) = args.endpoint {
            local_state.endpoint_ip.clone().unwrap_or(vec![endpoint])
        } else {
            local_state.endpoint_ip.clone().unwrap()
        };
        info!("Endpoint to upload data to is: {:?}", e);
        e
    };

    debug!("Creating mpscs...");
    let (gps_sender, mut gps_receiver) = mpsc::unbounded_channel();

    #[cfg(feature = "rtt")]
    let (rttps_sender, mut rttps_receiver) = mpsc::unbounded_channel();

    #[cfg(feature = "rtt")]
    let ping_ip: String = {
        let state_endpoint = state.clone();
        let (lock, _) = &*state_endpoint;
        let local_state = lock.lock().unwrap();
        let p = local_state.ping_ip.clone().or(args.target).unwrap();
        info!("Endpoint to measure latency at: {}", p);
        p
    };

    #[cfg(feature = "throughput")]
    let (bps_sender, mut bps_receiver) = mpsc::unbounded_channel();

    #[cfg(feature = "throughput")]
    let stream_url: String = {
        let state_endpoint = state.clone();
        let (lock, _) = &*state_endpoint;
        let local_state = lock.lock().unwrap();
        let s = local_state.stream_url.clone().or(args.stream_url).unwrap();
        info!("Endpoint to stream video from: {}", s);
        s
    };

    #[cfg(feature = "throughput")]
    debug!("Running ffmpeg...");
    #[cfg(feature = "throughput")]
    let ffmpeg_handler = ffmpeg::run_ffmpeg(stream_url.clone());
    #[cfg(feature = "throughput")]
    debug!("Running bps...");
    #[cfg(feature = "throughput")]
    let bps_handler = bps::run_bandwidth_eval(pcap_file, bps_sender);

    // wait here until api request comes in.
    debug!("Running gps...");
    let gps_handler = gps::run_gpsd_eval(gps_sender, args.dry_gps);
    #[cfg(feature = "rtt")]
    debug!("Running rttps...");
    #[cfg(feature = "rtt")]
    let rttps_handler = rttps::run_rtt_eval(rttps_sender, ping_ip);

    let (tx, mut rx) = mpsc::unbounded_channel();

    let gps_tx = tx.clone();

    #[cfg(all(feature = "throughput", feature = "rtt"))]
    let gps_channel_handler = tokio::spawn(async move {
        while let Some(msg) = gps_receiver.recv().await {
            if let Some(data) = msg {
                debug!("GpsData: {:?}", data);
                gps_tx
                    .send(DataMsg {
                        timestamp: data.timestamp,
                        entry: DataEntry {
                            lat: data.lat,
                            lon: data.lon,
                            gps_count: 1,
                            byte: None,
                            rtt: None,
                        },
                    })
                    .map_err(|err| error!("Failed to send data via GPS channel: {}", err))
                    .ok();
            }
        }
    });

    #[cfg(all(not(feature = "throughput"), feature = "rtt"))]
    let gps_channel_handler = tokio::spawn(async move {
        while let Some(msg) = gps_receiver.recv().await {
            if let Some(data) = msg {
                debug!("GpsData: {:?}", data);
                gps_tx
                    .send(DataMsg {
                        timestamp: data.timestamp,
                        entry: DataEntry {
                            lat: data.lat,
                            lon: data.lon,
                            gps_count: 1,
                            rtt: None,
                        },
                    })
                    .map_err(|err| error!("Failed to send data via GPS channel: {}", err))
                    .ok();
            }
        }
    });

    #[cfg(all(feature = "throughput", not(feature = "rtt")))]
    let gps_channel_handler = tokio::spawn(async move {
        while let Some(msg) = gps_receiver.recv().await {
            if let Some(data) = msg {
                debug!("GpsData: {:?}", data);
                gps_tx
                    .send(DataMsg {
                        timestamp: data.timestamp,
                        entry: DataEntry {
                            lat: data.lat,
                            lon: data.lon,
                            gps_count: 1,
                            byte: None,
                        },
                    })
                    .map_err(|err| error!("Failed to send data via GPS channel: {}", err))
                    .ok();
            }
        }
    });

    #[cfg(feature = "throughput")]
    let bps_tx = tx.clone();

    #[cfg(all(feature = "throughput", feature = "rtt"))]
    let bps_channel_handler = tokio::spawn(async move {
        while let Some(msg) = bps_receiver.recv().await {
            if let Some(data) = msg {
                debug!("BPSData: {:?}", data);
                bps_tx
                    .send(DataMsg {
                        timestamp: data.timestamp,
                        entry: DataEntry {
                            lat: None,
                            lon: None,
                            gps_count: 0,
                            byte: Some(data.data),
                            rtt: None,
                        },
                    })
                    .map_err(|err| error!("Failed to send data via BPS channel: {}", err))
                    .ok();
            }
        }
    });

    #[cfg(all(feature = "throughput", not(feature = "rtt")))]
    let bps_channel_handler = tokio::spawn(async move {
        while let Some(msg) = bps_receiver.recv().await {
            if let Some(data) = msg {
                debug!("BPSData: {:?}", data);
                bps_tx
                    .send(DataMsg {
                        timestamp: data.timestamp,
                        entry: DataEntry {
                            lat: None,
                            lon: None,
                            gps_count: 0,
                            byte: Some(data.data),
                        },
                    })
                    .map_err(|err| error!("Failed to send data via BPS channel: {}", err))
                    .ok();
            }
        }
    });

    #[cfg(feature = "rtt")]
    let rttps_tx = tx.clone();

    #[cfg(all(feature = "throughput", feature = "rtt"))]
    let rttps_channel_handler = tokio::spawn(async move {
        while let Some(msg) = rttps_receiver.recv().await {
            if let Some(data) = msg {
                debug!("RTTps: {:?}", data);
                rttps_tx
                    .send(DataMsg {
                        timestamp: data.timestamp,
                        entry: DataEntry {
                            lat: None,
                            lon: None,
                            gps_count: 0,
                            byte: None,
                            rtt: Some(data.rtt),
                        },
                    })
                    .map_err(|err| error!("Failed to send data via RTTps channel: {}", err))
                    .ok();
            }
        }
    });

    #[cfg(all(not(feature = "throughput"), feature = "rtt"))]
    let rttps_channel_handler = tokio::spawn(async move {
        while let Some(msg) = rttps_receiver.recv().await {
            if let Some(data) = msg {
                debug!("RTTps: {:?}", data);
                rttps_tx
                    .send(DataMsg {
                        timestamp: data.timestamp,
                        entry: DataEntry {
                            lat: None,
                            lon: None,
                            gps_count: 0,
                            rtt: Some(data.rtt),
                        },
                    })
                    .map_err(|err| error!("Failed to send data via RTTps channel: {}", err))
                    .ok();
            }
        }
    });

    let mut output_file = File::create(&args.out).await?;
    let mut entries: BTreeMap<u64, DataEntry> = BTreeMap::new();
    let client = reqwest::Client::new();

    let state_csv = state.clone();

    let csv_handler = tokio::spawn(async move {
        while let Some(msg) = rx.recv().await {
            let key = msg.timestamp;
            if entries.contains_key(&key) {
                let former_entry = entries.get(&key).unwrap();
                let new_entry = &msg.entry;
                let combined_entry = former_entry.combine(new_entry);
                entries.insert(key, combined_entry);
            } else {
                entries.insert(key, msg.entry);
            }
            // Write entry to csv if complete
            let entry = entries.get(&key).unwrap();

            #[cfg(all(feature = "throughput", feature = "rtt"))]
            if let (Some(lat), Some(lon), Some(byte), Some(rtt)) =
                (entry.lat, entry.lon, entry.byte, entry.rtt)
            {
                let rtt_ns = rtt.num_nanoseconds().unwrap();
                let rtt_string = format!("{rtt_ns} ns");
                let csv_entry = format!(
                    "{},{:2.8},{:2.8},{},{}\n",
                    &key,
                    lat / (entry.gps_count as f64),
                    lon / (entry.gps_count as f64),
                    byte,
                    rtt_string
                );

                info!("Writing data: {}", &csv_entry.trim());
                output_file.write_all(csv_entry.as_bytes()).await.unwrap();
                let mut char_array: [char; MAX_CSV_ENTRY_LENGTH] = [' '; MAX_CSV_ENTRY_LENGTH];
                // Convert the String into a Vec<char>
                let char_vec: Vec<char> = csv_entry.chars().collect();
                let len = char_vec.len().min(MAX_CSV_ENTRY_LENGTH);
                {
                    let (local_state, _) = &*state_csv;
                    let mut local_state = local_state.lock().unwrap();
                    let counter = local_state.entries_counter;

                    if counter < ENTRIES_BUFFER_LENGTH {
                        char_array[..len].copy_from_slice(&char_vec[..len]);
                        local_state.entries[counter] = char_array;
                        local_state.counter += 1;
                        local_state.entries_counter += 1;
                    }
                }
                let request_body: Option<String> = {
                    let (local_state, _) = &*state_csv;
                    let mut local_state = local_state.lock().unwrap();
                    if local_state.entries_counter >= ENTRIES_BUFFER_LENGTH {
                        let body = local_state
                            .entries
                            .iter()
                            .map(|r| r.iter().collect::<String>().trim().to_string())
                            .filter(|l| !l.is_empty())
                            .collect::<Vec<String>>()
                            .join("\n");
                        {
                            local_state.entries_counter = 0;
                            local_state.entries =
                                [[' '; MAX_CSV_ENTRY_LENGTH]; ENTRIES_BUFFER_LENGTH];
                        }
                        info!("Sending {} to {:?}", body.clone(), endpoint_ip);
                        Some(body)
                    } else {
                        info!("counter: {}", local_state.entries_counter);
                        None
                    }
                };
                if let Some(rb) = request_body {
                    info!("Trying to send data...");
                    for e in endpoint_ip.clone().iter() {
                        if let Ok(response) = client.post(e.clone()).body(rb.clone()).send().await {
                            info!(
                                "Sucessfully sent data to {}. Response: {:?}",
                                e.clone(),
                                response
                            );
                        } else {
                            error!("Couldn't send data to {}", e.clone());
                        }
                    }
                }
            } else {
                trace!(
                    "Building data: {{{}: {:?}}} (unfinished)",
                    &key,
                    entries.get(&key)
                );
            }

            #[cfg(all(not(feature = "throughput"), feature = "rtt"))]
            if let (Some(lat), Some(lon), Some(rtt)) = (entry.lat, entry.lon, entry.rtt) {
                let rtt_ns = rtt.num_nanoseconds().unwrap();
                let rtt_string = format!("{rtt_ns} ns");
                let csv_entry = format!(
                    "{},{:2.8},{:2.8},{}\n",
                    &key,
                    lat / (entry.gps_count as f64),
                    lon / (entry.gps_count as f64),
                    rtt_string
                );

                info!("Writing data: {}", &csv_entry.trim());
                output_file.write_all(csv_entry.as_bytes()).await.unwrap();
                let mut char_array: [char; MAX_CSV_ENTRY_LENGTH] = [' '; MAX_CSV_ENTRY_LENGTH];
                // Convert the String into a Vec<char>
                let char_vec: Vec<char> = csv_entry.chars().collect();
                let len = char_vec.len().min(MAX_CSV_ENTRY_LENGTH);
                {
                    let (local_state, _) = &*state_csv;
                    let mut local_state = local_state.lock().unwrap();
                    let counter = local_state.entries_counter;

                    if counter < ENTRIES_BUFFER_LENGTH {
                        char_array[..len].copy_from_slice(&char_vec[..len]);
                        local_state.entries[counter] = char_array;
                        local_state.counter += 1;
                        local_state.entries_counter += 1;
                    }
                }
                let request_body: Option<String> = {
                    let (local_state, _) = &*state_csv;
                    let mut local_state = local_state.lock().unwrap();
                    if local_state.entries_counter >= ENTRIES_BUFFER_LENGTH {
                        let body = local_state
                            .entries
                            .iter()
                            .map(|r| r.iter().collect::<String>().trim().to_string())
                            .filter(|l| !l.is_empty())
                            .collect::<Vec<String>>()
                            .join("\n");
                        let mut new_entries: [[char; MAX_CSV_ENTRY_LENGTH]; ENTRIES_BUFFER_LENGTH] =
                            local_state.entries;
                        new_entries.copy_within(1.., 0);
                        new_entries[ENTRIES_BUFFER_LENGTH - 1] = [' '; MAX_CSV_ENTRY_LENGTH];
                        {
                            local_state.entries_counter = ENTRIES_BUFFER_LENGTH - 1;
                            local_state.entries = new_entries;
                        }
                        info!("Sending {} to {:?}", body.clone(), endpoint_ip);
                        Some(body)
                    } else {
                        info!("counter: {}", local_state.entries_counter);
                        None
                    }
                };
                if let Some(rb) = request_body {
                    info!("Trying to send data...");
                    for e in endpoint_ip.clone().iter() {
                        if let Ok(response) = client.post(e.clone()).body(rb.clone()).send().await {
                            info!(
                                "Sucessfully sent data to {}. Response: {:?}",
                                e.clone(),
                                response
                            );
                        } else {
                            error!("Couldn't send data to {}", e.clone());
                        }
                    }
                }
            } else {
                trace!(
                    "Building data: {{{}: {:?}}} (unfinished)",
                    &key,
                    entries.get(&key)
                );
            }

            #[cfg(all(feature = "throughput", not(feature = "rtt")))]
            if let (Some(lat), Some(lon), Some(byte)) = (entry.lat, entry.lon, entry.byte) {
                let csv_entry = format!(
                    "{:2.8},{:2.8},{}\n",
                    lat / (entry.gps_count as f64),
                    lon / (entry.gps_count as f64),
                    byte,
                );

                info!("Writing data: {}", &csv_entry.trim());
                output_file.write_all(csv_entry.as_bytes()).await.unwrap();
                let mut char_array: [char; MAX_CSV_ENTRY_LENGTH] = [' '; MAX_CSV_ENTRY_LENGTH];
                // Convert the String into a Vec<char>
                let char_vec: Vec<char> = csv_entry.chars().collect();
                let len = char_vec.len().min(MAX_CSV_ENTRY_LENGTH);
                {
                    let (local_state, _) = &*state_csv;
                    let mut local_state = local_state.lock().unwrap();
                    let counter = local_state.entries_counter;

                    if counter < ENTRIES_BUFFER_LENGTH {
                        char_array[..len].copy_from_slice(&char_vec[..len]);
                        local_state.entries[counter] = char_array;
                        local_state.counter += 1;
                        local_state.entries_counter += 1;
                    }
                }
                let request_body: Option<String> = {
                    let (local_state, _) = &*state_csv;
                    let mut local_state = local_state.lock().unwrap();
                    if local_state.entries_counter >= ENTRIES_BUFFER_LENGTH {
                        let body = local_state
                            .entries
                            .iter()
                            .map(|r| r.iter().collect::<String>().trim().to_string())
                            .filter(|l| !l.is_empty())
                            .collect::<Vec<String>>()
                            .join("\n");
                        {
                            local_state.entries_counter = 0;
                            local_state.entries =
                                [[' '; MAX_CSV_ENTRY_LENGTH]; ENTRIES_BUFFER_LENGTH];
                        }
                        info!("Sending {} to {:?}", body.clone(), endpoint_ip);
                        Some(body)
                    } else {
                        info!("counter: {}", local_state.entries_counter);
                        None
                    }
                };
                if let Some(rb) = request_body {
                    info!("Trying to send data...");
                    for e in endpoint_ip.clone().iter() {
                        if let Ok(response) = client.post(e.clone()).body(rb.clone()).send().await {
                            info!(
                                "Sucessfully sent data to {}. Response: {:?}",
                                e.clone(),
                                response
                            );
                        } else {
                            error!("Couldn't send data to {}", e.clone());
                        }
                    }
                }
            } else {
                trace!(
                    "Building data: {{{}: {:?}}} (unfinished)",
                    &key,
                    entries.get(&key)
                );
            }
        }
    });

    #[cfg(all(feature = "throughput", feature = "rtt"))]
    let _handler = tokio::join!(
        ffmpeg_handler,
        bps_handler,
        gps_handler,
        rttps_handler,
        gps_channel_handler,
        bps_channel_handler,
        rttps_channel_handler,
        csv_handler,
    );

    #[cfg(all(feature = "throughput", not(feature = "rtt")))]
    let _handler = tokio::join!(
        ffmpeg_handler,
        bps_handler,
        gps_handler,
        gps_channel_handler,
        bps_channel_handler,
        csv_handler,
    );

    #[cfg(all(not(feature = "throughput"), feature = "rtt"))]
    let _handler = tokio::join!(
        gps_handler,
        rttps_handler,
        gps_channel_handler,
        rttps_channel_handler,
        csv_handler,
    );

    Ok(())
}
