//! This mainly concerns converting collected data into things that the canvas
//! can actually handle.

use std::collections::HashMap;

use crate::{
    app::{data_farmer, data_harvester, App},
    utils::gen_util::{get_exact_byte_values, get_simple_byte_values},
};

type Point = (f64, f64);

#[derive(Default, Debug)]
pub struct ConvertedBatteryData {
    pub battery_name: String,
    pub charge_percentage: f64,
    pub watt_consumption: String,
    pub duration_until_full: Option<String>,
    pub duration_until_empty: Option<String>,
    pub health: String,
}

#[derive(Default, Debug)]
pub struct ConvertedNetworkData {
    pub rx: Vec<Point>,
    pub tx: Vec<Point>,
    pub rx_display: String,
    pub tx_display: String,
    pub total_rx_display: Option<String>,
    pub total_tx_display: Option<String>,
}

#[derive(Clone, Default, Debug)]
pub struct ConvertedProcessData {
    pub pid: u32,
    pub name: String,
    pub cpu_usage: f64,
    pub mem_usage: f64,
    pub group_pids: Vec<u32>,
    pub read_per_sec: String,
    pub write_per_sec: String,
    pub total_read: String,
    pub total_write: String,
    pub rps_f64: f64,
    pub wps_f64: f64,
    pub tr_f64: f64,
    pub tw_f64: f64,
    pub process_states: String,
}

#[derive(Clone, Default, Debug)]
pub struct SingleProcessData {
    pub pid: u32,
    pub cpu_usage: f64,
    pub mem_usage: f64,
    pub group_pids: Vec<u32>,
    pub read_per_sec: u64,
    pub write_per_sec: u64,
    pub total_read: u64,
    pub total_write: u64,
    pub process_state: String,
}

#[derive(Clone, Default, Debug)]
pub struct ConvertedCpuData {
    pub cpu_name: String,
    /// Tuple is time, value
    pub cpu_data: Vec<Point>,
    /// Represents the value displayed on the legend.
    pub legend_value: String,
}

pub fn convert_temp_row(app: &App) -> Vec<Vec<String>> {
    let mut sensor_vector: Vec<Vec<String>> = Vec::new();

    let current_data = &app.data_collection;
    let temp_type = &app.app_config_fields.temperature_type;

    if current_data.temp_harvest.is_empty() {
        sensor_vector.push(vec!["No Sensors Found".to_string(), "".to_string()])
    } else {
        for sensor in &current_data.temp_harvest {
            sensor_vector.push(vec![
                sensor.component_name.to_string(),
                (sensor.temperature.ceil() as u64).to_string()
                    + match temp_type {
                        data_harvester::temperature::TemperatureType::Celsius => "C",
                        data_harvester::temperature::TemperatureType::Kelvin => "K",
                        data_harvester::temperature::TemperatureType::Fahrenheit => "F",
                    },
            ]);
        }
    }

    sensor_vector
}

pub fn convert_disk_row(current_data: &data_farmer::DataCollection) -> Vec<Vec<String>> {
    let mut disk_vector: Vec<Vec<String>> = Vec::new();
    current_data
        .disk_harvest
        .iter()
        .zip(&current_data.io_labels_and_prev)
        .for_each(|(disk, (io_label, _io_prev))| {
            let converted_read = get_simple_byte_values(io_label.0, false);
            let converted_write = get_simple_byte_values(io_label.1, false);
            let io_activity = (
                format!("{:.*}{}/s", 0, converted_read.0, converted_read.1),
                format!("{:.*}{}/s", 0, converted_write.0, converted_write.1),
            );

            let converted_free_space = get_simple_byte_values(disk.free_space, false);
            let converted_total_space = get_simple_byte_values(disk.total_space, false);
            disk_vector.push(vec![
                disk.name.to_string(),
                disk.mount_point.to_string(),
                format!(
                    "{:.0}%",
                    disk.used_space as f64 / disk.total_space as f64 * 100_f64
                ),
                format!("{:.*}{}", 0, converted_free_space.0, converted_free_space.1),
                format!(
                    "{:.*}{}",
                    0, converted_total_space.0, converted_total_space.1
                ),
                io_activity.0,
                io_activity.1,
            ]);
        });

    disk_vector
}

pub fn convert_cpu_data_points(
    current_data: &data_farmer::DataCollection, is_frozen: bool,
) -> Vec<ConvertedCpuData> {
    let mut cpu_data_vector: Vec<ConvertedCpuData> = Vec::new();
    let current_time = if is_frozen {
        if let Some(frozen_instant) = current_data.frozen_instant {
            frozen_instant
        } else {
            current_data.current_instant
        }
    } else {
        current_data.current_instant
    };

    for (time, data) in &current_data.timed_data_vec {
        let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();

        for (itx, cpu) in data.cpu_data.iter().enumerate() {
            // Check if the vector exists yet
            if cpu_data_vector.len() <= itx {
                let mut new_cpu_data = ConvertedCpuData::default();
                new_cpu_data.cpu_name = if let Some(cpu_harvest) = current_data.cpu_harvest.get(itx)
                {
                    cpu_harvest.cpu_name.to_string()
                } else {
                    String::default()
                };
                cpu_data_vector.push(new_cpu_data);
            }

            if let Some(cpu_data) = cpu_data_vector.get_mut(itx) {
                cpu_data.legend_value = format!("{:.0}%", cpu.round());
                cpu_data.cpu_data.push((-time_from_start, *cpu));
            }
        }

        if *time == current_time {
            break;
        }
    }

    let mut extended_vec = vec![ConvertedCpuData {
        cpu_name: "All".to_string(),
        cpu_data: vec![],
        legend_value: String::new(),
    }];
    extended_vec.extend(cpu_data_vector);
    extended_vec
}

pub fn convert_mem_data_points(
    current_data: &data_farmer::DataCollection, is_frozen: bool,
) -> Vec<Point> {
    let mut result: Vec<Point> = Vec::new();
    let current_time = if is_frozen {
        if let Some(frozen_instant) = current_data.frozen_instant {
            frozen_instant
        } else {
            current_data.current_instant
        }
    } else {
        current_data.current_instant
    };

    for (time, data) in &current_data.timed_data_vec {
        let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
        result.push((-time_from_start, data.mem_data));
        if *time == current_time {
            break;
        }
    }

    result
}

pub fn convert_swap_data_points(
    current_data: &data_farmer::DataCollection, is_frozen: bool,
) -> Vec<Point> {
    let mut result: Vec<Point> = Vec::new();
    let current_time = if is_frozen {
        if let Some(frozen_instant) = current_data.frozen_instant {
            frozen_instant
        } else {
            current_data.current_instant
        }
    } else {
        current_data.current_instant
    };

    for (time, data) in &current_data.timed_data_vec {
        let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
        result.push((-time_from_start, data.swap_data));
        if *time == current_time {
            break;
        }
    }

    result
}

pub fn convert_mem_labels(current_data: &data_farmer::DataCollection) -> (String, String) {
    let mem_label = "RAM:".to_string()
        + &format!(
            "{:3.0}%",
            match current_data.memory_harvest.mem_total_in_mb {
                0 => 0.0,
                _ =>
                    current_data.memory_harvest.mem_used_in_mb as f64 * 100.0
                        / current_data.memory_harvest.mem_total_in_mb as f64,
            }
        )
        + &format!(
            "   {:.1}GB/{:.1}GB",
            current_data.memory_harvest.mem_used_in_mb as f64 / 1024.0,
            (current_data.memory_harvest.mem_total_in_mb as f64 / 1024.0)
        );

    let swap_label = "SWP:".to_string()
        + &format!(
            "{:3.0}%",
            match current_data.swap_harvest.mem_total_in_mb {
                0 => 0.0,
                _ =>
                    current_data.swap_harvest.mem_used_in_mb as f64 * 100.0
                        / current_data.swap_harvest.mem_total_in_mb as f64,
            }
        )
        + &format!(
            "   {:.1}GB/{:.1}GB",
            current_data.swap_harvest.mem_used_in_mb as f64 / 1024.0,
            (current_data.swap_harvest.mem_total_in_mb as f64 / 1024.0)
        );

    (mem_label, swap_label)
}

pub fn get_rx_tx_data_points(
    current_data: &data_farmer::DataCollection, is_frozen: bool,
) -> (Vec<Point>, Vec<Point>) {
    let mut rx: Vec<Point> = Vec::new();
    let mut tx: Vec<Point> = Vec::new();

    let current_time = if is_frozen {
        if let Some(frozen_instant) = current_data.frozen_instant {
            frozen_instant
        } else {
            current_data.current_instant
        }
    } else {
        current_data.current_instant
    };

    for (time, data) in &current_data.timed_data_vec {
        let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
        rx.push((-time_from_start, data.rx_data));
        tx.push((-time_from_start, data.tx_data));
        if *time == current_time {
            break;
        }
    }

    (rx, tx)
}

pub fn convert_network_data_points(
    current_data: &data_farmer::DataCollection, is_frozen: bool, need_four_points: bool,
) -> ConvertedNetworkData {
    let (rx, tx) = get_rx_tx_data_points(current_data, is_frozen);

    let total_rx_converted_result: (f64, String);
    let rx_converted_result: (f64, String);
    let total_tx_converted_result: (f64, String);
    let tx_converted_result: (f64, String);

    rx_converted_result = get_exact_byte_values(current_data.network_harvest.rx, false);
    total_rx_converted_result = get_exact_byte_values(current_data.network_harvest.total_rx, false);

    tx_converted_result = get_exact_byte_values(current_data.network_harvest.tx, false);
    total_tx_converted_result = get_exact_byte_values(current_data.network_harvest.total_tx, false);

    if need_four_points {
        let rx_display = format!("{:.*}{}", 1, rx_converted_result.0, rx_converted_result.1);
        let total_rx_display = Some(format!(
            "{:.*}{}",
            1, total_rx_converted_result.0, total_rx_converted_result.1
        ));
        let tx_display = format!("{:.*}{}", 1, tx_converted_result.0, tx_converted_result.1);
        let total_tx_display = Some(format!(
            "{:.*}{}",
            1, total_tx_converted_result.0, total_tx_converted_result.1
        ));
        ConvertedNetworkData {
            rx,
            tx,
            rx_display,
            tx_display,
            total_rx_display,
            total_tx_display,
        }
    } else {
        let rx_display = format!(
            "RX: {:<9} All: {:<9}",
            format!("{:.1}{:3}", rx_converted_result.0, rx_converted_result.1),
            format!(
                "{:.1}{:3}",
                total_rx_converted_result.0, total_rx_converted_result.1
            )
        );
        let tx_display = format!(
            "TX: {:<9} All: {:<9}",
            format!("{:.1}{:3}", tx_converted_result.0, tx_converted_result.1),
            format!(
                "{:.1}{:3}",
                total_tx_converted_result.0, total_tx_converted_result.1
            )
        );

        ConvertedNetworkData {
            rx,
            tx,
            rx_display,
            tx_display,
            total_rx_display: None,
            total_tx_display: None,
        }
    }
}

pub fn convert_process_data(
    current_data: &data_farmer::DataCollection,
) -> (Vec<ConvertedProcessData>, Vec<ConvertedProcessData>) {
    let mut single_list = Vec::new();

    // cpu, mem, pids
    let mut grouped_hashmap: HashMap<String, SingleProcessData> = std::collections::HashMap::new();

    // Go through every single process in the list... and build a hashmap + single list
    for process in &(current_data).process_harvest {
        let entry = grouped_hashmap
            .entry(process.name.clone())
            .or_insert(SingleProcessData {
                pid: process.pid,
                ..SingleProcessData::default()
            });

        (*entry).cpu_usage += process.cpu_usage_percent;
        (*entry).mem_usage += process.mem_usage_percent;
        (*entry).group_pids.push(process.pid);
        (*entry).read_per_sec += process.read_bytes_per_sec;
        (*entry).write_per_sec += process.write_bytes_per_sec;
        (*entry).total_read += process.total_read_bytes;
        (*entry).total_write += process.total_write_bytes;

        let converted_rps = get_exact_byte_values(process.read_bytes_per_sec, false);
        let converted_wps = get_exact_byte_values(process.write_bytes_per_sec, false);
        let converted_total_read = get_exact_byte_values(process.total_read_bytes, false);
        let converted_total_write = get_exact_byte_values(process.total_write_bytes, false);

        let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1);
        let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1);
        let total_read = format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1);
        let total_write = format!(
            "{:.*}{}",
            0, converted_total_write.0, converted_total_write.1
        );

        single_list.push(ConvertedProcessData {
            pid: process.pid,
            name: process.name.to_string(),
            cpu_usage: process.cpu_usage_percent,
            mem_usage: process.mem_usage_percent,
            group_pids: vec![process.pid],
            read_per_sec,
            write_per_sec,
            total_read,
            total_write,
            rps_f64: process.read_bytes_per_sec as f64,
            wps_f64: process.write_bytes_per_sec as f64,
            tr_f64: process.total_read_bytes as f64,
            tw_f64: process.total_write_bytes as f64,
            process_states: process.process_state.to_owned(),
        });
    }

    let grouped_list: Vec<ConvertedProcessData> = grouped_hashmap
        .iter()
        .map(|(name, process_details)| {
            let p = process_details.clone();
            let converted_rps = get_exact_byte_values(p.read_per_sec, false);
            let converted_wps = get_exact_byte_values(p.write_per_sec, false);
            let converted_total_read = get_exact_byte_values(p.total_read, false);
            let converted_total_write = get_exact_byte_values(p.total_write, false);

            let read_per_sec = format!("{:.*}{}/s", 0, converted_rps.0, converted_rps.1);
            let write_per_sec = format!("{:.*}{}/s", 0, converted_wps.0, converted_wps.1);
            let total_read = format!("{:.*}{}", 0, converted_total_read.0, converted_total_read.1);
            let total_write = format!(
                "{:.*}{}",
                0, converted_total_write.0, converted_total_write.1
            );

            ConvertedProcessData {
                pid: p.pid,
                name: name.to_string(),
                cpu_usage: p.cpu_usage,
                mem_usage: p.mem_usage,
                group_pids: p.group_pids,
                read_per_sec,
                write_per_sec,
                total_read,
                total_write,
                rps_f64: p.read_per_sec as f64,
                wps_f64: p.write_per_sec as f64,
                tr_f64: p.total_read as f64,
                tw_f64: p.total_write as f64,
                process_states: p.process_state,
            }
        })
        .collect::<Vec<_>>();

    (single_list, grouped_list)
}

pub fn convert_battery_harvest(
    current_data: &data_farmer::DataCollection,
) -> Vec<ConvertedBatteryData> {
    current_data
        .battery_harvest
        .iter()
        .enumerate()
        .map(|(itx, battery_harvest)| ConvertedBatteryData {
            battery_name: format!("Battery {}", itx),
            charge_percentage: battery_harvest.charge_percent,
            watt_consumption: format!("{:.2}W", battery_harvest.power_consumption_rate_watts),
            duration_until_empty: if let Some(secs_till_empty) = battery_harvest.secs_until_empty {
                let time = chrono::Duration::seconds(secs_till_empty);
                let num_minutes = time.num_minutes() - time.num_hours() * 60;
                let num_seconds = time.num_seconds() - time.num_minutes() * 60;
                Some(format!(
                    "{} hour{}, {} minute{}, {} second{}",
                    time.num_hours(),
                    if time.num_hours() == 1 { "" } else { "s" },
                    num_minutes,
                    if num_minutes == 1 { "" } else { "s" },
                    num_seconds,
                    if num_seconds == 1 { "" } else { "s" },
                ))
            } else {
                None
            },
            duration_until_full: if let Some(secs_till_full) = battery_harvest.secs_until_full {
                let time = chrono::Duration::seconds(secs_till_full);
                let num_minutes = time.num_minutes() - time.num_hours() * 60;
                let num_seconds = time.num_seconds() - time.num_minutes() * 60;
                Some(format!(
                    "{} hour{}, {} minute{}, {} second{}",
                    time.num_hours(),
                    if time.num_hours() == 1 { "" } else { "s" },
                    num_minutes,
                    if num_minutes == 1 { "" } else { "s" },
                    num_seconds,
                    if num_seconds == 1 { "" } else { "s" },
                ))
            } else {
                None
            },
            health: format!("{:.2}%", battery_harvest.health_percent),
        })
        .collect()
}
