use crate::{app, error::Error, startup::LoraError};
use cansat_core::{
    nmea::NmeaGga,
    quantity::{Pressure, Temperature},
    Measurements,
};
use cansat_lora::ResponseContent;
use rtic::Mutex;
use rtic_monotonics::systick::Systick;
use stm32f4xx_hal::prelude::*;
use futures::FutureExt as _;
pub async fn measure(mut ctx: app::measure::Context<'_>) {
    loop {
        let future = async {
            let mut writer = csv_core::WriterBuilder::new()
                .delimiter(b',')
                .quote(b'\'')
                .build();
            let measurements = read_measurements(&mut ctx);
            defmt::info!("{}", measurements);
            let csv_record = match serde_csv_core::to_vec(&mut writer, &measurements) {
                Ok(r) => r,
                Err(e) => {
                    defmt::error!(
                        "Failed to create csv byte record: {}",
                        defmt::Display2Format(&e)
                    );
                    return;
                }
            };
            ctx.shared.csv_record.lock(|csv| {
                *csv = csv_record;
                let sd_logger = &mut ctx.local.sd_logger;
                if let Some(sd_logger) = sd_logger {
                    sd_logger.write(csv).unwrap();
                }
            });
            Systick::delay(250.millis()).await;
        };
        futures::select_biased! {
            _ = future.fuse() => {},
            _ = Systick::delay(10.secs()).fuse() => {}
        };
    }
}
fn read_measurements(ctx: &mut app::measure::Context) -> Measurements {
    let i2c1_devices = &mut ctx.local.i2c1_devices;
    let delay = &mut ctx.local.delay;
    let gps = &mut ctx.shared.gps;
    let _tracker = &mut ctx.local.tracker;
    let mut data = Measurements::default();
    if let Some(bme280) = &mut i2c1_devices.bme280 {
        match bme280.measure(delay) {
            Ok(m) => {
                let temperature = Temperature::from_celsius(m.temperature);
                let pressure = Pressure::from_pascals(m.pressure);
                let altitude = cansat_core::calculate_altitude(pressure);
                data.temperature = Some(temperature);
                data.pressure = Some(pressure);
                data.altitude = Some(altitude);
            }
            Err(e) => {
                defmt::error!(
                    "Could not read bme280 measurements: {}",
                    defmt::Debug2Format(&e)
                );
            }
        }
    }
    if let Some(mut nmea) = gps.lock(|gps| gps.last_nmea()) {
        let clrf_len = 2;
        nmea.truncate(nmea.len().saturating_sub(clrf_len));
        let nmea_gga = NmeaGga::try_new(&nmea);
        match nmea_gga {
            Ok(gga) => {
                ctx.shared.is_fixed.lock(|f: &mut bool| *f = gga.get_fix());
                data.nmea = Some(gga);
            }
            Err(e) => {
                defmt::error!(
                    "Could not read NMEA GGA command: {}",
                    defmt::Debug2Format(&e)
                );
            }
        }
    }
    if let Some(mpu) = &mut i2c1_devices.mpu {
        data.rollpitch = mpu.get_acc_angles().ok().map(|v| (v.x, v.y));
        data.gyro = mpu.get_gyro().ok().map(|v| (v.x, v.y, v.z));
        data.acceleration = mpu.get_acc().ok().map(|v| (v.x, v.y, v.z));
    }
    data
}
pub async fn send_meas(ctx: app::send_meas::Context<'_>) {
    let lora = ctx.local.lora;
    let mut csv_record = ctx.shared.csv_record;
    loop {
        csv_record.lock(|csv| {
            if let Some(lora) = lora {
                if !csv.is_empty() {
                    send_lora_package(lora, &csv[..csv.len() - 1]).unwrap();
                }
            }
        });
        Systick::delay(1.secs()).await;
    }
}
fn send_lora_package(lora: &mut crate::Lora, csv: &[u8]) -> Result<(), Error> {
    let mut command: heapless::Vec<u8, 256> = heapless::Vec::new();
    command.extend_from_slice(b"AT+TEST=TXLRSTR, \"").unwrap();
    command.extend_from_slice(csv).unwrap();
    command.extend_from_slice(b"\"\r\n").unwrap();
    let mut response: [u8; 255] = [0; 255];
    defmt::info!("{=[u8]:a}", command);
    lora.send(&command)?;
    for _ in 1..=2 {
        let nread = lora.receive(&mut response)?;
        let response = cansat_lora::parse_response(&response[..nread]).map_err(LoraError::Parse)?;
        if let ResponseContent::Error(ec) = response.content {
            return Err(Error::Response(ec));
        }
    }
    Ok(())
}
pub fn gps_irq(ctx: app::gps_irq::Context) {
    let mut gps = ctx.shared.gps;
    if let Err(e) = gps.lock(|gps| gps.read_serial()) {
        defmt::error!("Failed to read gps' serial: {}", e);
    };
}
pub async fn blink(ctx: app::blink::Context<'_>) {
    let led = ctx.local.led;
    loop {
        led.toggle();
        defmt::debug!("Blink");
        Systick::delay(1.secs()).await;
    }
}
pub async fn buzz(mut ctx: app::buzz::Context<'_>) {
    let buzzer = ctx.local.buzzer;
    let mut is_fixed = false;
    loop {
        buzzer.toggle();
        ctx.shared.is_fixed.lock(|f| {
            is_fixed = *f;
        });
        if is_fixed {
            defmt::debug!("Buzz with GPS fix");
            Systick::delay(1.secs()).await;
        } else {
            defmt::debug!("Buzz without GPS fix");
            Systick::delay(3.secs()).await;
        }
    }
}