mod leaderboard_entry;

use leaderboard_entry::*;

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>,
    leaderboard: Arc<GuiBuilder>,
    deltaboard: Arc<GuiBuilder>,

    leaderboard_grid: Arc<Grid>,
    deltaboard_grid: Arc<Grid>,

    leaderboard_entries: Vec<LeaderBoardEntry>,
    deltaboard_entries: [LeaderBoardEntry; 5],

    leaderboard_redraw: bool,
    last_player_id: i32,

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

impl LeaderBoard {
    const LEADERBOARD: &str = include_str!("leaderboard_grid.xml");
    const DELTABOARD: &str = include_str!("deltaboard_grid.xml");

    pub fn new(gui_handler: &Arc<GuiHandler>) -> Result<Self> {
        let leaderboard = GuiBuilder::from_str(gui_handler, Self::LEADERBOARD)?;
        let deltaboard = GuiBuilder::from_str(gui_handler, Self::DELTABOARD)?;

        let leaderboard_grid = leaderboard.element("main_grid")?;
        let deltaboard_grid = deltaboard.element("main_grid")?;

        Ok(Self {
            gui_handler: gui_handler.clone(),
            leaderboard,
            deltaboard,
            leaderboard_grid,
            deltaboard_grid,

            leaderboard_entries: Vec::new(),
            deltaboard_entries: [
                LeaderBoardEntry::empty(gui_handler)?,
                LeaderBoardEntry::empty(gui_handler)?,
                LeaderBoardEntry::empty(gui_handler)?,
                LeaderBoardEntry::empty(gui_handler)?,
                LeaderBoardEntry::empty(gui_handler)?,
            ],

            leaderboard_redraw: false,
            last_player_id: -1,

            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
                .leaderboard_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,
                        {
                            let laps_behind = vehicle_scoring.mLapsBehindLeader;

                            if laps_behind != 0 {
                                BehindLeader::Laps(laps_behind)
                            } else {
                                BehindLeader::Time(vehicle_scoring.mTimeBehindLeader)
                            }
                        },
                        vehicle_scoring.mTimeBehindNext,
                        vehicle_scoring.mBestLapTime,
                    )?;

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

        // TODO: when people disconnect there needs to be a remove function
        // vehicle_scorings.len() != self.leaderboard_entries.len()

        write_log!("create entries");

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

            true
        } else {
            false
        };

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

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

            for (i, entry) in self.leaderboard_entries.iter_mut().enumerate() {
                entry.resorting_finished();

                // don't break here, just skip adding to grid
                // because resorting_finished should be called for every entry
                if i < self.leaderboard_grid.dimensions().1 {
                    entry.change_background_color(self.entry_backgrounds[i % 2])?;

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

            self.leaderboard_redraw = true;
        }

        // update delta board
        if self.last_player_id != -1 {
            write_log!("update delta board");

            if let Some((index, _player_entry)) = self
                .leaderboard_entries
                .iter()
                .enumerate()
                .find(|(_index, entry)| entry.id() == self.last_player_id)
            {
                let mut start_index = if index >= 2 { index - 2 } else { 0 };

                let max = self.leaderboard_entries.len().min(5);

                write_log!(format!(
                    "Delta Board: start {} - count {}",
                    start_index, max
                ));

                // clear old entries
                for i in 0..5 {
                    if let Some(child) = self.deltaboard_grid.detach(0, i)? {
                        if let Some(visiblity) = child.visibility() {
                            visiblity.set_visibility(false)?;
                        }
                    }
                }

                // add new entries
                for i in 0..max {
                    if let Some(leaderboard_entry) = self.leaderboard_entries.get(start_index) {
                        if let Some(entry) = self.deltaboard_entries.get_mut(i) {
                            entry.change_id(leaderboard_entry.id());
                            entry.update_place(leaderboard_entry.place())?;

                            if entry.id() == self.last_player_id {
                                entry.change_background_color(self.player_background)?;
                            } else {
                                entry.change_background_color(self.entry_backgrounds[i % 2])?;
                            }

                            if entry.name() != leaderboard_entry.name() {
                                entry.change_name(leaderboard_entry.name().to_string())?;
                            }

                            if let Some(vehicle_scoring) = vehicle_scorings
                                .iter()
                                .find(|scoring| scoring.mID == entry.id())
                            {
                                entry.update_time_behind_next(vehicle_scoring.mTimeBehindNext)?;
                            }

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

                        start_index += 1;
                    }
                }
            }
        }

        Ok(())
    }

    fn race_leaderboard(&mut self, vehicle_scorings: &[VehicleScoringInfoV01]) -> Result<()> {
        self.update_leaderboard(vehicle_scorings, |entry, scoring| {
            let laps_behind = scoring.mLapsBehindLeader;

            if laps_behind != 0 {
                entry.update_time_behind_leader(BehindLeader::Laps(laps_behind))
            } else {
                entry.update_time_behind_leader(BehindLeader::Time(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 => {
                for entry in self.leaderboard_entries.iter_mut() {
                    entry.update_best_lap(-1.0)?;
                }

                self.leaderboard.enable()?;
                self.deltaboard.enable()?;
            }

            GamePhase::Race => {
                for entry in self.leaderboard_entries.iter_mut() {
                    entry.update_time_behind_leader(BehindLeader::Time(0.0))?;
                }

                self.leaderboard.enable()?;
                self.deltaboard.enable()?;
            }
            _ => {
                self.last_player_id = -1;
                self.leaderboard.disable()?;
                self.deltaboard.disable()?;
            }
        }

        Ok(())
    }

    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 self.leaderboard_redraw {
            self.leaderboard_redraw = false;

            if let Some(player_id) = player_id {
                if let Some(entry) = self
                    .leaderboard_entries
                    .iter()
                    .find(|entry| entry.id() == player_id)
                {
                    write_log!(format!(
                        "Update player entry background color: {:?}",
                        self.player_background
                    ));
                    entry.change_background_color(self.player_background)?;
                }
            }
        }

        if let Some(player_id) = player_id {
            self.last_player_id = player_id;
        }

        Ok(())
    }
}