use crate::components::constants::lookup_team_or;
use crate::components::game::at_bat::AtBat;
use crate::components::game::player::{Player, PlayerStats};
use crate::components::game::win_probability::WinProbability;
use crate::components::linescore::LineScore;
use crate::components::standings::Team;
use indexmap::IndexMap;
use mlbt_api::live::LiveResponse;
use mlbt_api::plays::Play;
use mlbt_api::schedule::AbstractGameState;
use mlbt_api::win_probability::WinProbabilityResponse;
use std::collections::HashMap;
use std::sync::LazyLock;
pub type AtBatIndex = u8;
pub type PlayerId = u64;
pub type PlayerMap = HashMap<PlayerId, Player>;
static DEFAULT_AT_BAT: LazyLock<AtBat> = LazyLock::new(AtBat::default);
#[derive(Default)]
pub struct GameState {
pub game_id: u64,
pub home_team: Team,
pub away_team: Team,
pub linescore: LineScore,
pub current_at_bat: AtBatIndex,
pub at_bats: IndexMap<AtBatIndex, AtBat>,
pub on_deck: Option<PlayerId>,
pub in_hole: Option<PlayerId>,
pub win_probability: WinProbability,
pub players: PlayerMap,
pub home_abs_challenges: Option<u8>,
pub away_abs_challenges: Option<u8>,
pub abstract_game_state: Option<AbstractGameState>,
}
impl GameState {
pub fn update(&mut self, live_data: &LiveResponse, win_probability: &WinProbabilityResponse) {
if self.game_id != live_data.game_pk {
self.reset();
}
self.game_id = live_data.game_pk;
self.players = Self::create_players(live_data); self.set_teams(live_data);
self.set_on_deck(live_data);
self.set_abs_challenges(live_data);
self.abstract_game_state = live_data.game_data.status.abstract_game_state;
self.current_at_bat = Self::get_current_play_ab_index(live_data);
self.linescore = LineScore::from_live_data(live_data, &self.home_team, &self.away_team);
if let Some(plays) = &live_data.live_data.plays.all_plays {
plays.iter().for_each(|p| Self::update_single_play(self, p));
}
self.win_probability = WinProbability::from(win_probability);
}
fn set_teams(&mut self, live_data: &LiveResponse) {
let home = &live_data.game_data.teams.home;
let away = &live_data.game_data.teams.away;
self.home_team = lookup_team_or(&home.name, || Team::from_live(home));
self.away_team = lookup_team_or(&away.name, || Team::from_live(away));
}
fn set_abs_challenges(&mut self, live_data: &LiveResponse) {
let abs = live_data.game_data.abs_challenges.as_ref();
self.home_abs_challenges = abs.map(|abs| abs.home.remaining);
self.away_abs_challenges = abs.map(|abs| abs.away.remaining);
}
fn set_on_deck(&mut self, live_data: &LiveResponse) {
self.on_deck = live_data
.live_data
.linescore
.offense
.on_deck
.as_ref()
.map(|od| od.id);
self.in_hole = live_data
.live_data
.linescore
.offense
.in_hole
.as_ref()
.map(|ih| ih.id);
}
fn create_players(live_data: &LiveResponse) -> PlayerMap {
let mut map = HashMap::new();
for player in live_data.game_data.players.values() {
map.insert(player.id, Player::from(player));
}
if let Some(teams) = &live_data.live_data.boxscore.teams {
for player in teams.home.players.values() {
if let Some(p) = map.get_mut(&player.person.id) {
p.stats = PlayerStats::from(player);
}
}
for player in teams.away.players.values() {
if let Some(p) = map.get_mut(&player.person.id) {
p.stats = PlayerStats::from(player);
}
}
}
map
}
pub fn get_latest_at_bat(&self) -> &AtBat {
self.at_bats
.get(&self.current_at_bat)
.unwrap_or_else(|| &DEFAULT_AT_BAT)
}
pub fn get_at_bat_by_index(&self, index: u8) -> Option<&AtBat> {
self.at_bats.get(&index)
}
pub fn get_at_bat_by_index_or_current(&self, index: Option<u8>) -> (&AtBat, bool) {
let idx = index.unwrap_or(self.current_at_bat);
match self.get_at_bat_by_index(idx) {
Some(at_bat) => (at_bat, idx == self.current_at_bat),
None => (self.get_latest_at_bat(), true),
}
}
pub fn count_events(&self) -> usize {
self.at_bats.len()
}
fn get_current_play_ab_index(live_data: &LiveResponse) -> AtBatIndex {
live_data
.live_data
.plays
.current_play
.as_ref()
.map(|c| c.about.at_bat_index)
.unwrap_or(0)
}
pub fn update_single_play(&mut self, play: &Play) {
let at_bat = AtBat::from(play);
self.at_bats.insert(at_bat.index, at_bat);
}
pub fn reset(&mut self) {
*self = Self::default()
}
pub fn format_on_deck(&self) -> Option<String> {
self.on_deck
.and_then(|id| self.players.get(&id))
.map(|player| format!("on deck: {}", player.last_name))
}
pub fn format_in_hole(&self) -> Option<String> {
self.in_hole
.and_then(|id| self.players.get(&id))
.map(|player| format!("in hole: {}", player.last_name))
}
}