use crate::derived::{
    runtime_company_credit_rating, runtime_company_support_adjusted_share_price_scalar,
    runtime_decode_saved_f64_bits, runtime_round_f64_to_i64,
};
use crate::event::metrics::{
    RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_COMPANY_STAT_FAMILY_SPECIAL_232A,
    RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE, RUNTIME_COMPANY_STAT_SLOT_COUNT,
    RUNTIME_COMPANY_STAT_SLOT_CREDIT_RATING, RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
    RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN, RuntimeCompanyStatSelector,
};
use crate::state::RuntimeState;

pub fn runtime_company_stat_value(
    state: &RuntimeState,
    company_id: u32,
    selector: RuntimeCompanyStatSelector,
) -> Option<i64> {
    runtime_company_stat_value_f64(state, company_id, selector).and_then(runtime_round_f64_to_i64)
}

pub fn runtime_company_stat_value_f64(
    state: &RuntimeState,
    company_id: u32,
    selector: RuntimeCompanyStatSelector,
) -> Option<f64> {
    if selector.slot_id >= RUNTIME_COMPANY_STAT_SLOT_COUNT {
        return runtime_company_derived_stat_value(
            state,
            company_id,
            selector.family_id,
            selector.slot_id,
        );
    }
    match selector.family_id {
        RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER => {
            runtime_company_control_transfer_stat_value_f64(state, company_id, selector.slot_id)
        }
        RUNTIME_COMPANY_STAT_FAMILY_SPECIAL_232A => {
            runtime_company_special_stat_family_232a_value_f64(state, company_id, selector.slot_id)
        }
        family_id => {
            runtime_company_year_stat_value_f64(state, company_id, family_id, selector.slot_id)
        }
    }
}

pub(super) fn runtime_company_current_stat_value_f64(
    state: &RuntimeState,
    company_id: u32,
    slot_id: u32,
) -> Option<f64> {
    let market_state = state.service_state.company_market_state.get(&company_id)?;
    let index = slot_id.checked_mul(RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)? as usize;
    runtime_decode_saved_f64_bits(*market_state.year_stat_family_qword_bits.get(index)?)
}

pub(crate) fn runtime_company_direct_float_field_value_f64(
    state: &RuntimeState,
    company_id: u32,
    field_offset: u32,
) -> Option<f64> {
    let market_state = state.service_state.company_market_state.get(&company_id)?;
    let raw_u32 = *market_state
        .direct_control_transfer_float_fields_raw_u32
        .get(&field_offset)?;
    let value = f32::from_bits(raw_u32) as f64;
    if !value.is_finite() {
        return None;
    }
    Some(value)
}

pub(super) fn runtime_company_direct_i32_field_value_f64(
    state: &RuntimeState,
    company_id: u32,
    field_offset: u32,
) -> Option<f64> {
    let market_state = state.service_state.company_market_state.get(&company_id)?;
    Some(i64::from(
        *market_state
            .direct_control_transfer_int_fields_raw_u32
            .get(&field_offset)? as i32,
    ) as f64)
}

pub fn runtime_company_book_value_per_share(state: &RuntimeState, company_id: u32) -> Option<i64> {
    let company = state
        .companies
        .iter()
        .find(|company| company.company_id == company_id)?;
    if company.book_value_per_share != 0 {
        return Some(company.book_value_per_share);
    }
    runtime_company_direct_float_field_value_f64(state, company_id, 0x32f)
        .and_then(runtime_round_f64_to_i64)
}

pub fn runtime_decode_saved_f32_value(raw_u32: u32) -> Option<f64> {
    let value = f32::from_bits(raw_u32) as f64;
    if !value.is_finite() {
        return None;
    }
    Some(value)
}

pub(crate) fn runtime_company_control_transfer_stat_value_f64(
    state: &RuntimeState,
    company_id: u32,
    slot_id: u32,
) -> Option<f64> {
    let company = state
        .companies
        .iter()
        .find(|company| company.company_id == company_id)?;
    match slot_id {
        0x00..=0x12 if slot_id != RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH => {
            runtime_company_current_stat_value_f64(state, company_id, slot_id)
        }
        RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH => {
            if company.current_cash != 0 {
                Some(company.current_cash as f64)
            } else {
                runtime_company_current_stat_value_f64(state, company_id, slot_id)
            }
        }
        0x14 => runtime_company_control_transfer_stat_value_f64(state, company_id, 0x31)
            .zip(state.service_state.company_market_state.get(&company_id))
            .map(|(value, market_state)| {
                if market_state.outstanding_shares == 0 {
                    0.0
                } else {
                    value / market_state.outstanding_shares as f64
                }
            }),
        0x15 => runtime_company_stat_value_f64(
            state,
            company_id,
            RuntimeCompanyStatSelector {
                family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
                slot_id: 0x2c,
            },
        )
        .zip(state.service_state.company_market_state.get(&company_id))
        .map(|(value, market_state)| {
            if market_state.outstanding_shares == 0 {
                0.0
            } else {
                value / market_state.outstanding_shares as f64
            }
        }),
        0x16 => runtime_company_stat_value_f64(
            state,
            company_id,
            RuntimeCompanyStatSelector {
                family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
                slot_id: 0x2b,
            },
        )
        .zip(state.service_state.company_market_state.get(&company_id))
        .map(|(value, market_state)| {
            if market_state.outstanding_shares == 0 {
                0.0
            } else {
                value / market_state.outstanding_shares as f64
            }
        }),
        0x13 => runtime_company_support_adjusted_share_price_scalar(state, company_id),
        0x17 => runtime_company_direct_float_field_value_f64(state, company_id, 0x4b),
        RUNTIME_COMPANY_STAT_SLOT_CREDIT_RATING => {
            runtime_company_credit_rating(state, company_id).map(|value| value as f64)
        }
        0x1a => runtime_company_direct_float_field_value_f64(state, company_id, 0x53),
        0x1b => runtime_company_direct_float_field_value_f64(state, company_id, 0x323),
        0x1c => runtime_company_direct_float_field_value_f64(state, company_id, 0x327),
        RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE => {
            runtime_company_book_value_per_share(state, company_id).map(|value| value as f64)
        }
        0x1e => runtime_company_direct_float_field_value_f64(state, company_id, 0x333),
        0x1f => runtime_company_direct_float_field_value_f64(state, company_id, 0x33b),
        0x20 => runtime_company_direct_float_field_value_f64(state, company_id, 0x33f),
        0x21 => runtime_company_direct_float_field_value_f64(state, company_id, 0x327).and_then(
            |denominator| {
                let numerator =
                    runtime_company_direct_float_field_value_f64(state, company_id, 0x32b)?;
                Some(if denominator <= 0.0 {
                    numerator
                } else {
                    numerator / denominator
                })
            },
        ),
        0x22 => runtime_company_direct_float_field_value_f64(state, company_id, 0x333).and_then(
            |denominator| {
                let numerator =
                    runtime_company_direct_float_field_value_f64(state, company_id, 0x337)?;
                Some(if denominator <= 0.0 {
                    numerator
                } else {
                    numerator / denominator
                })
            },
        ),
        0x23 => runtime_company_direct_float_field_value_f64(state, company_id, 0x33f).and_then(
            |denominator| {
                let numerator =
                    runtime_company_direct_float_field_value_f64(state, company_id, 0x343)?;
                Some(if denominator <= 0.0 {
                    numerator
                } else {
                    numerator / denominator
                })
            },
        ),
        0x26 => runtime_company_direct_i32_field_value_f64(state, company_id, 0x34b),
        0x27 => runtime_company_direct_i32_field_value_f64(state, company_id, 0x14f),
        0x28 => runtime_company_direct_i32_field_value_f64(state, company_id, 0x0d0b),
        0x29 => runtime_company_direct_i32_field_value_f64(state, company_id, 0x0d0f),
        0x2a => runtime_company_direct_i32_field_value_f64(state, company_id, 0x0d13),
        _ => None,
    }
}

pub(super) fn runtime_company_special_stat_family_232a_value_f64(
    state: &RuntimeState,
    company_id: u32,
    slot_id: u32,
) -> Option<f64> {
    let market_state = state.service_state.company_market_state.get(&company_id)?;
    let value = runtime_decode_saved_f64_bits(
        *market_state
            .special_stat_family_232a_qword_bits
            .get(slot_id as usize)?,
    )?;
    if (0x13..=0x1b).contains(&slot_id) {
        Some(value + runtime_company_control_transfer_stat_value_f64(state, company_id, slot_id)?)
    } else {
        Some(value)
    }
}

pub(super) fn runtime_company_year_stat_value_f64(
    state: &RuntimeState,
    company_id: u32,
    family_id: u32,
    slot_id: u32,
) -> Option<f64> {
    let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?);
    let year_delta = current_year_word.checked_sub(family_id)?;
    if year_delta == 0 || year_delta >= RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN {
        return None;
    }
    let market_state = state.service_state.company_market_state.get(&company_id)?;
    let index = slot_id
        .checked_mul(RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)?
        .checked_add(year_delta)? as usize;
    runtime_decode_saved_f64_bits(*market_state.year_stat_family_qword_bits.get(index)?)
}

pub fn runtime_company_derived_stat_value(
    state: &RuntimeState,
    company_id: u32,
    family_id: u32,
    slot_id: u32,
) -> Option<f64> {
    let stat = |slot_id| {
        runtime_company_stat_value_f64(
            state,
            company_id,
            RuntimeCompanyStatSelector { family_id, slot_id },
        )
    };
    let rounded_stat = |slot_id| stat(slot_id).and_then(runtime_round_f64_to_i64);
    match slot_id {
        0x2b => Some(stat(0x2d)? + stat(0x2c)?),
        0x2c => Some(stat(0x04)? + stat(0x03)? + stat(0x02)? + stat(0x01)?),
        0x2d => Some(stat(0x2f)? + stat(0x2e)?),
        0x2e => Some(
            stat(0x0c)?
                + stat(0x0b)?
                + stat(0x0a)?
                + stat(0x08)?
                + stat(0x07)?
                + stat(0x06)?
                + stat(0x05)?,
        ),
        0x2f => stat(0x09),
        0x30 => Some(stat(0x11)? + stat(0x10)? + stat(0x0f)? + stat(0x0e)? + stat(0x0d)?),
        0x31 => Some(stat(0x30)? + stat(0x12)?),
        0x32 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x24)?),
        0x33 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x16)?),
        0x34 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x17)?),
        0x35 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x18)?),
        0x36 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x19)?),
        0x37 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x1a)?),
        0x38 => runtime_divide_by_rounded_stat_i64(stat(0x2c)?, rounded_stat(0x1b)?),
        _ => None,
    }
}

pub(super) fn runtime_divide_by_rounded_stat_i64(numerator: f64, denominator: i64) -> Option<f64> {
    if denominator == 0 {
        return Some(0.0);
    }
    Some(numerator / denominator as f64)
}

pub fn runtime_company_trailing_full_year_stat_series(
    state: &RuntimeState,
    company_id: u32,
    slot_id: u32,
    full_year_count: u32,
) -> Option<(Vec<u32>, Vec<i64>)> {
    let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?);
    let mut year_words = Vec::with_capacity(full_year_count as usize);
    let mut values = Vec::with_capacity(full_year_count as usize);
    for year_offset in 1..=full_year_count {
        let family_id = current_year_word.checked_sub(year_offset)?;
        let value = runtime_company_stat_value_f64(
            state,
            company_id,
            RuntimeCompanyStatSelector { family_id, slot_id },
        )
        .and_then(runtime_round_f64_to_i64)?;
        year_words.push(family_id);
        values.push(value);
    }
    Some((year_words, values))
}

pub fn runtime_company_year_or_control_transfer_metric_value(
    state: &RuntimeState,
    company_id: u32,
    year_word: u32,
    slot_id: u32,
) -> Option<f64> {
    let current_year_word = u32::from(state.world_restore.packed_year_word_raw_u16?);
    if year_word == current_year_word {
        runtime_company_stat_value_f64(
            state,
            company_id,
            RuntimeCompanyStatSelector {
                family_id: RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER,
                slot_id,
            },
        )
    } else {
        runtime_company_stat_value_f64(
            state,
            company_id,
            RuntimeCompanyStatSelector {
                family_id: year_word,
                slot_id,
            },
        )
    }
}
