use hifitime::{Duration, TimeScale, Unit};
use tabled::{settings::Style, Table, Tabled};

use crate::naif::daf::NAIFSummaryRecord;

use super::{BPC, SPK};

#[derive(Tabled)]
pub struct BpcRow {
    #[tabled(rename = "Name")]
    pub name: String,
    #[tabled(rename = "Start epoch")]
    pub start_epoch: String,
    #[tabled(rename = "End epoch")]
    pub end_epoch: String,
    #[tabled(rename = "Duration")]
    pub duration: Duration,
    #[tabled(rename = "Interpolation kind")]
    pub interpolation_kind: String,
    #[tabled(rename = "Frame")]
    pub frame: String,
    #[tabled(rename = "Inertial frame")]
    pub inertial_frame: String,
}

#[derive(Tabled)]
pub struct SpkRow {
    #[tabled(rename = "Name")]
    pub name: String,
    #[tabled(rename = "Target")]
    pub target: String,
    #[tabled(rename = "Center")]
    pub center: String,
    #[tabled(rename = "Start epoch")]
    pub start_epoch: String,
    #[tabled(rename = "End epoch")]
    pub end_epoch: String,
    #[tabled(rename = "Duration")]
    pub duration: Duration,
    #[tabled(rename = "Interpolation kind")]
    pub interpolation_kind: String,
}

pub trait NAIFPrettyPrint {
    fn describe(&self) -> String {
        self.describe_in(TimeScale::ET, None)
    }

    fn describe_in(&self, time_scale: TimeScale, round: Option<bool>) -> String;
}

impl NAIFPrettyPrint for BPC {
    /// Returns a string of a table representing this BPC where the epochs are printed in the provided time scale
    /// Set `round` to Some(false) to _not_ round the durations. By default, the durations will be rounded to the nearest second.
    fn describe_in(&self, time_scale: TimeScale, round: Option<bool>) -> String {
        // Build the rows of the table
        let mut rows = Vec::new();

        let round_value = if round.unwrap_or(true) {
            Unit::Second * 1.0_f64
        } else {
            Unit::Second * 0
        };

        // NOTE: Using the explicit loop and index here to we can fetch the name record correctly.
        let mut idx = None;
        loop {
            for (sno, summary) in self.data_summaries(idx).unwrap().iter().enumerate() {
                let name_rcrd = self.name_record(idx).unwrap();
                let name = name_rcrd.nth_name(sno, self.file_record().unwrap().summary_size());
                if summary.is_empty() {
                    continue;
                }
                rows.push(BpcRow {
                    name: name.to_string(),
                    start_epoch: summary
                        .start_epoch()
                        .to_gregorian_str(time_scale)
                        .to_string(),
                    end_epoch: summary.end_epoch().to_gregorian_str(time_scale).to_string(),
                    duration: (summary.end_epoch() - summary.start_epoch()).round(round_value),
                    interpolation_kind: summary.data_type().unwrap().to_string(),
                    frame: format!("{}", summary.frame_id),
                    inertial_frame: format!("{}", summary.inertial_frame_id),
                });
            }
            if let Ok(summary) = self.daf_summary(idx) {
                if summary.is_final_record() {
                    break;
                } else {
                    idx = Some(summary.next_record());
                }
            }
        }

        let mut tbl = Table::new(rows);
        tbl.with(Style::modern());
        format!("{tbl}")
    }
}

impl NAIFPrettyPrint for SPK {
    /// Returns a string of a table representing this SPK where the epochs are printed in the provided time scale
    /// Set `round` to Some(false) to _not_ round the duration. By default, the durations will be rounded to the nearest second.
    fn describe_in(&self, time_scale: TimeScale, round: Option<bool>) -> String {
        // Build the rows of the table
        let mut rows = Vec::new();

        let round_value = if round.unwrap_or(true) {
            Unit::Second * 1.0_f64
        } else {
            Unit::Second * 0
        };

        // NOTE: Using the explicit loop and index here to we can fetch the name record correctly.
        let mut idx = None;
        loop {
            for (sno, summary) in self.data_summaries(idx).unwrap().iter().enumerate() {
                let name_rcrd = self.name_record(idx).unwrap();
                let name = name_rcrd.nth_name(sno, self.file_record().unwrap().summary_size());
                if summary.is_empty() {
                    continue;
                }

                rows.push(SpkRow {
                    name: name.to_string(),
                    center: summary.center_frame_uid().to_string(),
                    start_epoch: summary
                        .start_epoch()
                        .to_gregorian_str(time_scale)
                        .to_string(),
                    end_epoch: summary.end_epoch().to_gregorian_str(time_scale).to_string(),
                    duration: (summary.end_epoch() - summary.start_epoch()).round(round_value),
                    interpolation_kind: summary.data_type().unwrap().to_string(),
                    target: summary.target_frame_uid().to_string(),
                });
            }
            if let Ok(summary) = self.daf_summary(idx) {
                if summary.is_final_record() {
                    break;
                } else {
                    idx = Some(summary.next_record());
                }
            }
        }
        let mut tbl = Table::new(rows);
        tbl.with(Style::sharp());
        format!("{tbl}")
    }
}
