ed-journals 0.5.0

Provides models for representing and parsing elite dangerous journal files
Documentation
use std::collections::HashMap;

use serde::Serialize;
use crate::exploration::calculate_estimated_worth;

use crate::logs::{LogEvent, LogEventContent};
use crate::logs::commander_event::CommanderEvent;
use crate::logs::scan_organic_event::ScanOrganicEventScanType;
use crate::modules::civilization::LocationInfo;
use current_organic::CurrentOrganic;
use crate::logs::rank_event::RankEvent;
use crate::logs::reputation_event::ReputationEvent;
use crate::logs::statistics_event::StatisticsEvent;
use crate::logs::scan_event::ScanEvent;
use crate::state::models::carrier_state::CarrierState;
use crate::state::models::feed_result::FeedResult;
use crate::state::models::materials_state::MaterialsState;
use crate::state::models::mission_state::MissionState;
use crate::state::SystemState;
use crate::try_feed;

pub mod current_organic;

#[derive(Serialize)]
pub struct CommanderState {
    pub fid: String,
    pub name: String,
    pub systems: HashMap<u64, SystemState>,
    pub current_system: Option<u64>,
    pub current_organic: Option<CurrentOrganic>,
    pub current_organic_process: Option<ScanOrganicEventScanType>,
    pub current_exploration_data: Vec<ScanEvent>,
    pub material_state: MaterialsState,
    pub mission_state: MissionState,
    pub carrier_state: Option<CarrierState>,
    pub rank: Option<RankEvent>,
    pub reputation: Option<ReputationEvent>,
    pub statistics: Option<StatisticsEvent>,
}

impl CommanderState {
    pub fn feed_log_event(&mut self, log_event: &LogEvent) -> FeedResult {
        match &log_event.content {
            LogEventContent::Scan(event) => {
                self.current_exploration_data.push(event.clone());
            },
            LogEventContent::Died(event) => {
                self.current_exploration_data.clear();
            },
            LogEventContent::Rank(ranks) => {
                self.rank = Some(ranks.clone());
            }
            LogEventContent::Reputation(reputation) => {
                self.reputation = Some(reputation.clone());
            },
            LogEventContent::Statistics(statistics) => {
                self.statistics = Some(statistics.clone());
            }
            LogEventContent::MultiSellExplorationData(event) => {
                for system in &event.discovered {
                    self.current_exploration_data
                        .retain(|item| {
                            item.star_system != system.system_name
                        });
                }
            },
            LogEventContent::SellExplorationData(event) => {
                for system in &event.systems {
                    self.current_exploration_data
                        .retain(|item| {
                            &item.star_system != system
                        });
                }
            },
            LogEventContent::Location(location) => {
                self.current_system = Some(location.location_info.system_address);

                let system = self.upset_system(&location.location_info);
                system.visit(&log_event.timestamp);
            }
            LogEventContent::CarrierJump(carrier_jump) => {
                let system = self.upset_system(&carrier_jump.system_info);
                system.carrier_visit(&log_event.timestamp);
            }
            LogEventContent::FSDJump(fsd_jump) => {
                self.current_system = Some(fsd_jump.system_info.system_address);

                let system = self.upset_system(&fsd_jump.system_info);
                system.visit(&log_event.timestamp);
            }
            LogEventContent::ScanOrganic(scan_organic) => match &scan_organic.scan_type {
                ScanOrganicEventScanType::Sample => {
                    self.current_organic_process = Some(ScanOrganicEventScanType::Sample);
                    self.current_organic = Some(CurrentOrganic {
                        system_address: scan_organic.system_address,
                        body_id: scan_organic.body,
                        species: scan_organic.species.clone(),
                    });
                },
                ScanOrganicEventScanType::Analyse => {
                    self.current_organic_process = Some(ScanOrganicEventScanType::Analyse);
                },
                ScanOrganicEventScanType::Log => {
                    self.current_organic = None;
                },
            },

            LogEventContent::Materials(event) => {
                for material in &event.raw {
                    self.material_state.set_material_count(material.name.clone(), material.count);
                }

                for material in &event.encoded {
                    self.material_state.set_material_count(material.name.clone(), material.count);
                }

                for material in &event.manufactured {
                    self.material_state.set_material_count(material.name.clone(), material.count);
                }
            }
            LogEventContent::MaterialCollected(event) => {
                self.material_state.add_material_count(event.name.clone(), event.count);
            },
            LogEventContent::MaterialDiscarded(event) => {
                self.material_state.remove_material_count(event.name.clone(), event.count);
            },
            LogEventContent::MaterialTrade(event) => {
                self.material_state.remove_material_count(event.paid.material.clone(), event.paid.quantity);
                self.material_state.add_material_count(event.received.material.clone(), event.received.quantity);
            },

            LogEventContent::CarrierStats(stats) => {
                if self.carrier_state.is_none() {
                    let mut state: CarrierState = stats.clone().into();
                    state.feed_log_event(&log_event);

                    self.carrier_state = Some(state);
                }
            },

            LogEventContent::CarrierJump(_)
            | LogEventContent::CarrierBuy(_)
            | LogEventContent::CarrierJumpRequest(_)
            | LogEventContent::CarrierDecommission(_)
            | LogEventContent::CarrierCancelDecommission(_)
            | LogEventContent::CarrierBankTransfer(_)
            | LogEventContent::CarrierDepositFuel(_)
            | LogEventContent::CarrierCrewServices(_)
            | LogEventContent::CarrierFinance(_)
            | LogEventContent::CarrierShipPack(_)
            | LogEventContent::CarrierModulePack(_)
            | LogEventContent::CarrierTradeOrder(_)
            | LogEventContent::CarrierDockingPermission(_)
            | LogEventContent::CarrierNameChange(_)
            | LogEventContent::CarrierJumpCancelled(_) => {
                match &mut self.carrier_state {
                    Some(state) => { try_feed!(state.feed_log_event(&log_event)); },
                    None => return FeedResult::Later,
                }
            },

            _ => {}
        }

        let carrier_has_been_scrapped = self.carrier_state
            .as_ref()
            .is_some_and(|state| state.has_been_scrapped(&log_event.timestamp));

        if carrier_has_been_scrapped {
            self.carrier_state = None;
        }

        if let Some(address) = log_event.content.system_address() {
            let Some(system) = self.systems.get_mut(&address) else {
                return FeedResult::Later;
            };

            try_feed!(system.feed_log_event(log_event));
        }

        FeedResult::Accepted
    }

    pub fn upset_system(&mut self, location_info: &LocationInfo) -> &mut SystemState {
        self.systems
            .entry(location_info.system_address)
            .or_insert_with(|| location_info.into());

        self.systems
            .get_mut(&location_info.system_address)
            .expect("Should have been added")
    }

    pub fn current_system(&self) -> Option<&SystemState> {
        self.systems.get(&self.current_system?)
    }

    pub fn system_by_address(&self, address: u64) -> Option<&SystemState> {
        self.systems.get(&address)
    }

    pub fn current_exploration_worth(&self) -> u64 {
        self.current_exploration_data
            .iter()
            .map(|item| calculate_estimated_worth(item))
            .sum()
    }
}

impl From<&CommanderEvent> for CommanderState {
    fn from(value: &CommanderEvent) -> Self {
        CommanderState {
            fid: value.fid.to_string(),
            name: value.name.to_string(),
            systems: HashMap::new(),
            current_system: None,
            current_organic: None,
            current_organic_process: None,
            current_exploration_data: Vec::new(),
            material_state: MaterialsState::default(),
            mission_state: MissionState::default(),
            carrier_state: None,
            rank: None,
            reputation: None,
            statistics: None,
        }
    }
}