use anyhow::Result;
use rfactor_sm_reader::*;

use std::{cell::RefCell, rc::Rc, time::Instant};

use crate::write_log;

use super::UiOverlay;

pub trait DataReceiver {
    fn game_phase_change(&mut self, phase: GamePhase) -> Result<()>;

    fn update_for_phase(&self, phase: GamePhase) -> bool;

    fn scoring_update(
        &mut self,
        phase: GamePhase,
        vehicle_scoring: &[VehicleScoringInfoV01],
    ) -> Result<()>;

    fn telemetry_update(
        &mut self,
        player_id: Option<i32>,
        telemetries: &[rF2VehicleTelemetry],
    ) -> Result<()>;
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum GamePhase {
    TestDay,
    Practice,
    Qualifying,
    Warmup,
    Race,
    None,
}

impl TryFrom<i32> for GamePhase {
    type Error = anyhow::Error;

    fn try_from(value: i32) -> Result<Self> {
        Ok(match value {
            0 => Self::TestDay,
            1..=4 => Self::Practice,
            5..=8 => Self::Qualifying,
            9 => Self::Warmup,
            10..=13 => Self::Race,

            _ => return Err(anyhow::anyhow!("Failed to parse GamePhase from: {}", value)),
        })
    }
}

pub struct RFactorData {
    // rf2 memory mapped data
    telemetry_reader: TelemetryReader,
    scoring_reader: ScoringReader,

    start_time: Instant,
    player_id: Option<i32>,
    previous_game_phase: GamePhase,

    receivers: Vec<Rc<RefCell<dyn UiOverlay>>>,
}

impl RFactorData {
    pub fn new() -> Result<Self> {
        write_log!(" =================== create RFactorData ===================");

        let start_time = Instant::now();

        Ok(Self {
            telemetry_reader: TelemetryReader::new(start_time.elapsed().as_secs_f32())?,
            scoring_reader: ScoringReader::new(start_time.elapsed().as_secs_f32())?,

            start_time,
            player_id: None,
            previous_game_phase: GamePhase::None,

            receivers: Vec::new(),
        })
    }

    pub fn add_receiver(&mut self, receiver: Rc<RefCell<dyn UiOverlay>>) {
        self.receivers.push(receiver);
    }

    fn now(&self) -> f32 {
        self.start_time.elapsed().as_secs_f32()
    }

    pub fn update(&mut self) -> Result<()> {
        write_log!(" =================== update RFactorData ===================");

        // get scoring info
        if let Some((scoring_info, vehicle_scorings)) =
            self.scoring_reader.vehicle_scoring(self.now())
        {
            write_log!(format!(
                "new scoring info: vehicles: {}",
                scoring_info.mNumVehicles
            ));

            // check for player id
            if scoring_info.mNumVehicles == 0 {
                self.player_id = None;
            } else if self.player_id.is_none() {
                for vehicle_scoring in vehicle_scorings.iter() {
                    if vehicle_scoring.mIsPlayer != 0 {
                        write_log!(format!("player found: {}", vehicle_scoring.mID));
                        self.player_id = Some(vehicle_scoring.mID);
                        break;
                    }
                }
            }

            {
                let phase = GamePhase::try_from(scoring_info.mSession)?;

                if self.previous_game_phase != phase {
                    self.previous_game_phase = phase;

                    for receiver in self.receivers.iter() {
                        receiver
                            .borrow_mut()
                            .game_phase_change(self.previous_game_phase)?;
                    }
                }

                write_log!(format!("GamePhase: {:?}", self.previous_game_phase));
            }

            for receiver in self.receivers.iter() {
                let mut rec_mut = receiver.borrow_mut();

                if rec_mut.update_for_phase(self.previous_game_phase) {
                    rec_mut.scoring_update(self.previous_game_phase, &vehicle_scorings)?;
                }
            }
        }

        // check telemetry data
        write_log!("before telemetry update");
        if let Some(telemetries) = self.telemetry_reader.query_telemetry(self.now()) {
            write_log!("new telemetry update");

            for receiver in self.receivers.iter() {
                let mut rec_mut = receiver.borrow_mut();

                if rec_mut.update_for_phase(self.previous_game_phase) {
                    rec_mut.telemetry_update(self.player_id, &telemetries)?;
                }
            }
        }

        Ok(())
    }
}