use crate::abi::{ChangeOp, DataChange, DataSelect, Header, Str};
use crate::content::Guid;
use crate::content::recipes::RecipeKind;
use crate::content::workshops::WorkshopId;
use crate::{Load, Off};
use super::{Entry, Field, Value, ValueError, field_and_value};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Recipes {
Kind(RecipeKind),
Workshop(WorkshopId),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RecipeTraits {
pub kind: RecipeKind,
pub workshop: u32,
}
impl Recipes {
#[must_use]
pub fn picks(self, recipe: RecipeTraits) -> bool {
match self {
Self::Kind(kind) => recipe.kind == kind,
Self::Workshop(workshop) => recipe.workshop == workshop.id(),
}
}
const fn to_abi(self) -> (DataSelect, [u8; 16], u32) {
match self {
Self::Kind(kind) => (DataSelect::RECIPE_KIND, [0; 16], kind.code().0),
Self::Workshop(workshop) => (DataSelect::WORKSHOP, workshop.guid().0, 0),
}
}
fn from_abi(raw: &DataChange) -> Option<Self> {
match raw.select {
DataSelect::RECIPE_KIND => {
let kind = RecipeKind::from(crate::abi::RecipeKind(raw.categories));
(kind.code().0 == raw.categories).then_some(Self::Kind(kind))
}
DataSelect::WORKSHOP => Some(Self::Workshop(WorkshopId::new(Guid(raw.entry)))),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RecipesChange {
pub recipes: Recipes,
pub field: Field,
pub value: Value,
}
impl RecipesChange {
#[must_use]
pub const fn new(recipes: Recipes, field: Field, value: Value) -> Self {
Self {
recipes,
field,
value,
}
}
pub fn check(&self) -> Result<(), ValueError> {
if self.field.entry() != Entry::Recipe || self.field == Field::Ingredient {
return Err(ValueError::NotRecipeSelectable { field: self.field });
}
self.field.check(self.value)
}
#[must_use]
pub const fn of(&self, recipe: Guid) -> super::Change {
super::Change::new(recipe, self.field, self.value)
}
pub fn from_abi(raw: &DataChange) -> Result<Option<Self>, ValueError> {
let Some(recipes) = Recipes::from_abi(raw) else {
return Ok(None);
};
let Some((field, value)) = field_and_value(raw) else {
return Ok(None);
};
let change = Self::new(recipes, field, value);
change.check()?;
Ok(Some(change))
}
fn to_abi(self, source: &str) -> DataChange {
let (op, value) = match self.value {
Value::Set(v) => (ChangeOp::SET, v),
Value::Times(v) => (ChangeOp::TIMES, v),
};
let (select, entry, categories) = self.recipes.to_abi();
DataChange {
header: Header::of::<DataChange>(),
entry,
field: self.field.to_abi(),
op,
value,
source: Str::new(source),
item: [0; 16],
select,
categories,
}
}
}
impl Load<'_> {
pub fn change_recipes(&mut self, change: &RecipesChange, source: &str) -> Result<(), Off> {
change.field.unstable_gate()?;
change
.check()
.map_err(|why| Off::new(format!("change of {:?}: {why}", change.recipes)))?;
let raw = change.to_abi(source);
let api = self.host().api();
let status = (api.change_data)(self.host().handle(), &raw const raw);
crate::load::ok(status, (), || {
format!("change {} of {:?}", change.field, change.recipes)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
const KILN: WorkshopId = WorkshopId::new(Guid([7; 16]));
#[test]
fn a_selection_survives_its_abi_record() {
for recipes in [Recipes::Kind(RecipeKind::Terrain), Recipes::Workshop(KILN)] {
let change = RecipesChange::new(recipes, Field::Output, Value::Set(3.0));
let raw = change.to_abi("x");
assert_eq!(RecipesChange::from_abi(&raw), Ok(Some(change)));
}
}
#[test]
fn an_unknown_kind_code_selects_nothing() {
let mut raw = RecipesChange::new(
Recipes::Kind(RecipeKind::Roof),
Field::Cost,
Value::Set(1.0),
)
.to_abi("");
raw.categories = 99;
assert_eq!(RecipesChange::from_abi(&raw), Ok(None));
}
#[test]
fn only_fields_of_a_whole_recipe_are_selected() {
for field in Field::ALL {
let value = if *field == Field::Station {
Value::Set(0.0)
} else {
Value::Times(1.0)
};
let change = RecipesChange::new(Recipes::Workshop(KILN), *field, value);
let whole = field.entry() == Entry::Recipe && *field != Field::Ingredient;
assert_eq!(change.check().is_ok(), whole, "{field}");
}
}
}