#[cfg(feature = "std")]
pub use stdmod::*;

pub use satrs_shared::res_code::ResultU16;

use serde::{Deserialize, Serialize};
use serde_hex::{SerHex, StrictCapPfx};
#[derive(Debug, Copy, Clone, Serialize)]
pub struct ResultU16Info {
    pub name: &'static str,
    pub result: &'static ResultU16,
    pub group_str: &'static str,
    pub info: &'static str,
}

impl ResultU16Info {
    pub const fn const_new(
        name: &'static str,
        result: &'static ResultU16,
        group_str: &'static str,
        info: &'static str,
    ) -> Self {
        Self {
            name,
            result,
            group_str,
            info,
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ResultU16InfoSerializable {
    #[serde(with = "SerHex::<StrictCapPfx>")]
    raw: u16,
    #[serde(with = "SerHex::<StrictCapPfx>")]
    group_id: u8,
    #[serde(with = "SerHex::<StrictCapPfx>")]
    unique_id: u8,
    name: &'static str,
    group_str: &'static str,
    info: &'static str,
}

impl From<ResultU16Info> for ResultU16InfoSerializable {
    fn from(v: ResultU16Info) -> Self {
        Self {
            raw: v.result.raw(),
            group_id: v.result.group_id(),
            unique_id: v.result.unique_id(),
            name: v.name,
            group_str: v.group_str,
            info: v.info,
        }
    }
}

#[cfg(feature = "std")]
pub mod stdmod {
    use super::*;
    use std::fs::File;
    use std::io;

    pub fn print_resultcodes_as_csv(
        writer_builder: csv::WriterBuilder,
        codes: &[ResultU16Info],
    ) -> Result<(), csv::Error> {
        let mut wtr = writer_builder.from_writer(io::stdout());
        for result in codes {
            wtr.serialize(ResultU16InfoSerializable::from(*result))?;
        }
        wtr.flush()?;
        Ok(())
    }

    /// This function exports a slice of result code information objects to a CSV file.
    pub fn write_resultcodes_to_csv(
        writer_builder: csv::WriterBuilder,
        results: &[ResultU16Info],
        file: File,
    ) -> Result<(), csv::Error> {
        let mut wtr = writer_builder.from_writer(file);
        for result_code in results {
            wtr.serialize(ResultU16InfoSerializable::from(*result_code))?;
        }
        wtr.flush()?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::File;
    use std::io::{BufRead, BufReader};
    use std::path::Path;
    // Special solution for this crate because the code generated by a macro will use
    // satrs_mib::res_code::*
    use crate as satrs_mib;
    use satrs_mib::resultcode;
    use satrs_shared::res_code::ResultU16;

    #[derive(Debug)]
    #[allow(dead_code)]
    pub enum GroupId {
        Tmtc = 0,
    }

    #[resultcode]
    pub const INVALID_PUS_SERVICE: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 0);
    #[resultcode]
    pub const INVALID_PUS_SUBSERVICE: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 1);

    #[resultcode(info = "Not enough data inside the TC application data field")]
    pub const NOT_ENOUGH_APP_DATA: ResultU16 = ResultU16::new(GroupId::Tmtc as u8, 2);

    pub const TMTC_RESULTS: &[ResultU16Info] = &[
        INVALID_PUS_SERVICE_EXT,
        INVALID_PUS_SUBSERVICE_EXT,
        NOT_ENOUGH_APP_DATA_EXT,
    ];

    const CSV_NAME: &str = "dummy.csv";

    #[test]
    fn test_printout() {
        let mut wtrb = csv::WriterBuilder::new();
        wtrb.delimiter(b';');
        print_resultcodes_as_csv(wtrb, TMTC_RESULTS).expect("Priting result codes failed");
    }

    #[test]
    fn test_csv_export() {
        let csvpath = Path::new(CSV_NAME);
        let mut wtrb = csv::WriterBuilder::new();
        let file = File::create(csvpath).expect("Creating CSV file failed");
        wtrb.delimiter(b';');
        write_resultcodes_to_csv(wtrb, TMTC_RESULTS, file).expect("CSV export failed");
        assert!(csvpath.exists());
        let file = File::open(csvpath).expect("Opening CSV file failed");
        let buf_reader = BufReader::new(file);
        for (idx, line) in buf_reader.lines().enumerate() {
            match idx {
                0 => {
                    assert!(line.is_ok());
                    let line = line.unwrap();
                    assert_eq!(line, "raw;group_id;unique_id;name;group_str;info");
                }
                1 => {
                    assert!(line.is_ok());
                    let line = line.unwrap();
                    assert_eq!(line, "0x0000;0x00;0x00;INVALID_PUS_SERVICE;;");
                }
                2 => {
                    assert!(line.is_ok());
                    let line = line.unwrap();
                    assert_eq!(line, "0x0001;0x00;0x01;INVALID_PUS_SUBSERVICE;;");
                }
                3 => {
                    assert!(line.is_ok());
                    let line = line.unwrap();
                    assert_eq!(line, "0x0002;0x00;0x02;NOT_ENOUGH_APP_DATA;;Not enough data inside the TC application data field");
                }
                _ => (),
            }
        }
        std::fs::remove_file(Path::new("dummy.csv")).expect("Removing dummy csv failed");
    }
}
