use std::{
    ffi::{c_char, CStr},
    sync::Arc,
};

use anyhow::Result;
use rfactor_sm_reader::{rF2VehicleTelemetry, VehicleScoringInfoV01};
use ui::prelude::*;
use utilities::prelude::Color;

use crate::overlay::{
    rfactor_data::{DataReceiver, GamePhase},
    UiOverlay,
};
use crate::write_log;

pub struct LeaderBoard {
    gui_handler: Arc<GuiHandler>,
    gui: Arc<GuiBuilder>,
    main_grid: Arc<Grid>,

    entries: Vec<LeaderBoardEntry>,

    entry_backgrounds: [Color; 2],
    player_background: Color,
}

impl LeaderBoard {
    const GRID: &str = include_str!("leaderboard_grid.xml");
    const ENTRY: &str = include_str!("leaderboard_entry.xml");

    pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
        let gui = GuiBuilder::from_str(gui_handler, Self::GRID)?;

        let main_grid = gui.element("main_grid")?;

        Ok(Self {
            gui_handler: gui_handler.clone(),
            gui,
            main_grid,

            entries: Vec::new(),

            entry_backgrounds: [Color::try_from("#838383")?, Color::try_from("#545454")?],
            player_background: Color::try_from("#b4bf26")?,
        })
    }

    fn c_char_to_string(c: [c_char; 32usize]) -> String {
        unsafe { CStr::from_ptr(&c as *const c_char) }
            .to_str()
            .unwrap()
            .to_string()
    }

    fn update_leaderboard<F>(
        &mut self,
        vehicle_scorings: &[VehicleScoringInfoV01],
        f: F,
    ) -> Result<()>
    where
        F: Fn(&mut LeaderBoardEntry, &VehicleScoringInfoV01) -> Result<()>,
    {
        for vehicle_scoring in vehicle_scorings {
            let driver_name = Self::c_char_to_string(vehicle_scoring.mDriverName);

            // check driver list
            match self
                .entries
                .iter_mut()
                .find(|entry| vehicle_scoring.mID == entry.id())
            {
                Some(entry) => {
                    if entry.name() != driver_name {
                        entry.change_name(driver_name)?;
                    }

                    entry.update_place(vehicle_scoring.mPlace)?;

                    f(entry, vehicle_scoring)?;
                }
                None => {
                    let entry = LeaderBoardEntry::new(
                        &self.gui_handler,
                        vehicle_scoring.mID,
                        driver_name,
                        vehicle_scoring.mPlace,
                        vehicle_scoring.mTimeBehindLeader,
                        vehicle_scoring.mTimeBehindNext,
                        vehicle_scoring.mBestLapTime,
                    )?;

                    self.entries.push(entry);
                }
            }
        }

        write_log!("create entries");

        // check if entry count in grid is the same as the gathered entries
        let force_update = if !self.entries.is_empty()
            && self
                .main_grid
                .child_at(0, self.entries.len() - 1)?
                .is_none()
        {
            for i in 0..self.entries.len() {
                self.main_grid.detach(0, i)?;
            }

            true
        } else {
            false
        };

        // check if any entry needs resorting
        if force_update || self.entries.iter().any(|entry| entry.needs_resorting()) {
            write_log!("leader board update required");

            self.entries
                .sort_by(|lhs, rhs| lhs.place().cmp(&rhs.place()));

            for (i, entry) in self.entries.iter_mut().enumerate() {
                entry.resorting_finished();
                entry.change_background_color(self.entry_backgrounds[i % 2])?;

                self.main_grid.attach(entry.snippet(), 0, i, 1, 1)?;
            }
        }

        Ok(())
    }

    fn race_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
        self.update_leaderboard(vehicle_scorings, |entry, scoring| {
            entry.update_time_behind_leader(scoring.mTimeBehindLeader)
        })
    }

    fn quali_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
        self.update_leaderboard(vehicle_scorings, |entry, scoring| {
            entry.update_best_lap(scoring.mBestLapTime)
        })
    }
}

impl UiOverlay for LeaderBoard {}

impl DataReceiver for LeaderBoard {
    fn game_phase_change(&mut self, phase: GamePhase) -> Result<()> {
        match phase {
            GamePhase::Practice | GamePhase::Qualifying | GamePhase::Race => self.gui.enable(),
            _ => self.gui.disable(),
        }
    }

    fn update_for_phase(&self, phase: GamePhase) -> bool {
        match phase {
            GamePhase::Practice | GamePhase::Qualifying | GamePhase::Race => true,
            _ => false,
        }
    }

    fn scoring_update(
        &mut self,
        phase: GamePhase,
        vehicle_scorings: &[VehicleScoringInfoV01],
    ) -> Result<()> {
        write_log!("=================== leader board: scoring update ===================");

        match phase {
            GamePhase::Practice | GamePhase::Qualifying => {
                self.quali_leaderboard(vehicle_scorings)?
            }

            GamePhase::Race => self.race_leaderboard(vehicle_scorings)?,

            _ => (),
        }

        write_log!("leader board update finished");

        Ok(())
    }

    fn telemetry_update(
        &mut self,
        player_id: Option<i32>,
        _telemetries: &[rF2VehicleTelemetry],
    ) -> Result<()> {
        if let Some(player_id) = player_id {
            if let Some(entry) = self.entries.iter().find(|entry| entry.id() == player_id) {
                entry.change_background_color(self.player_background)?;
            }
        }

        Ok(())
    }
}

struct LeaderBoardEntry {
    id: i32,

    name: String,
    place: u8,
    time_behind_leader: f64,
    time_behind_next: f64,
    best_lap: f64,

    snippet: Arc<GuiSnippet>,

    grid: Arc<Grid>,
    name_label: Arc<Label>,
    place_label: Arc<Label>,
    time_label: Arc<Label>,

    place_updated: bool,
}

impl LeaderBoardEntry {
    pub fn new(
        gui_handler: &Arc<GuiHandler>,
        id: i32,
        name: String,
        place: u8,
        time_behind_leader: f64,
        time_behind_next: f64,
        best_lap: f64,
    ) -> Result<Self> {
        let snippet = GuiSnippet::from_str(gui_handler, LeaderBoard::ENTRY)?;

        let background = snippet.element("grid")?;
        let name_label: Arc<Label> = snippet.element("name")?;
        let place_label: Arc<Label> = snippet.element("place")?;
        let time_label: Arc<Label> = snippet.element("time")?;

        name_label.set_text(&name)?;
        place_label.set_text(place)?;
        time_label.set_text("---")?;

        Ok(Self {
            id,

            name,
            place,
            time_behind_leader,
            time_behind_next,
            best_lap,

            snippet,

            grid: background,
            name_label,
            place_label,
            time_label,

            place_updated: true,
        })
    }

    pub fn id(&self) -> i32 {
        self.id
    }

    pub fn place(&self) -> u8 {
        self.place
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn change_name(&mut self, name: String) -> Result<()> {
        self.name = name;

        self.name_label.set_text(&self.name)
    }

    pub fn snippet(&self) -> Arc<GuiSnippet> {
        self.snippet.clone()
    }

    pub fn change_background_color(&self, color: Color) -> Result<()> {
        self.grid.set_background(color)
    }

    pub fn update_place(&mut self, place: u8) -> Result<()> {
        if self.place != place {
            self.place_updated = true;
        }

        self.place = place;

        self.place_label.set_text(self.place)
    }

    pub fn update_time_behind_leader(&mut self, time: f64) -> Result<()> {
        if self.time_behind_leader != time {
            self.time_behind_leader = time;

            // check if we are leader
            if self.time_behind_leader == 0.0 {
                self.time_label.set_text("---")?;
            } else {
                self.time_label
                    .set_text(format!("+{:.3}", self.time_behind_leader))?;
            }
        }

        Ok(())
    }

    pub fn update_best_lap(&mut self, time: f64) -> Result<()> {
        if self.best_lap != time {
            self.best_lap = time;

            if self.best_lap < 0.0 {
                self.time_label.set_text("---")?;
            } else {
                let text = if self.best_lap > 60.0 {
                    let full_minutes = (self.best_lap / 60.0).floor();
                    let remainder = self.best_lap - (full_minutes * 60.0);

                    format!("{:.0}:{:.3}", full_minutes, remainder)
                } else {
                    format!("{:.3}", self.best_lap)
                };

                self.time_label.set_text(text)?;
            }
        }

        Ok(())
    }

    pub fn update_time_behind_next(&mut self, time: f64) -> Result<()> {
        self.time_behind_next = time;

        self.time_label
            .set_text(format!("+{:.3}", self.time_behind_next))
    }

    pub fn needs_resorting(&self) -> bool {
        self.place_updated
    }

    pub fn resorting_finished(&mut self) {
        self.place_updated = false;
    }
}