use anyhow::{anyhow, Result};
use crate::frame::*;
use crate::instance::*;
use crate::repository::*;
use crate::semantics::*;
pub fn roll(
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
class_name: &str,
parent_uid: &str,
injectors: Option<&Injectors>,
) -> Result<String> {
let class = resolve_actual_class_to_roll(builder, class_name)?;
let uid = builder.randomizer.uid();
create_entity_frame(tx, parent_uid, &uid, class)?;
let entity = tx.create(&uid)?;
entity["uid"] = serde_json::Value::from(uid.as_str());
entity["uuid"] = serde_json::Value::from(uid.as_str());
entity["parent_uid"] = serde_json::Value::from(parent_uid);
entity["class"] = serde_json::Value::from(class.name.as_str());
if let Some(prependers) = injectors {
for injector in prependers.prependers.as_slice() {
injector.inject(builder, tx, &uid, parent_uid)?;
}
}
for (_, attr) in class.attrs.as_slice() {
attr.cmd.apply(&mut Context::Rolling, builder, tx, &uid)?;
}
if let Some(appenders) = injectors {
for injector in appenders.appenders.as_slice() {
injector.inject(builder, tx, &uid, parent_uid)?;
}
}
tx.save(&uid)?;
Ok(uid)
}
pub fn unroll(
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
uid: &str,
injectors: Option<&Injectors>,
) -> Result<String> {
let entity = tx.load(uid)?;
let parent_spec = entity["$parent"].clone();
let class_name = entity["class"].as_str().unwrap().to_string();
let users = entity["$users"]
.as_array()
.cloned()
.unwrap_or_else(Vec::new);
withdraw(builder, tx, uid, &class_name)?;
let class = builder.sandbox.classes.get(&class_name).unwrap();
if let Some(injs) = injectors {
for injector in injs.appenders.as_slice() {
injector.eject(builder, tx, uid, "")?;
}
}
for (_, attr) in class.attrs.as_slice() {
attr.cmd.revert(&mut Context::Unrolling, builder, tx, uid)?;
}
if let Some(boots) = injectors {
for injector in boots.prependers.as_slice() {
injector.eject(builder, tx, uid, "")?;
}
}
let parent_uid = if !parent_spec.is_null() {
let parent_uid = parent_spec["uid"].as_str().unwrap();
let parent_attr = parent_spec["attr"].as_str().unwrap();
let parent = tx.load(parent_uid)?;
parent[parent_attr]
.as_array_mut()
.unwrap()
.retain(|v| v != uid);
tx.save(parent_uid)?;
parent_uid
} else {
"root"
};
remove_entity_frame(tx, uid)?;
tx.remove(uid)?;
for user_spec in users {
let user_uid = user_spec["uid"].as_str().unwrap();
let user_attr = user_spec["attr"].as_str().unwrap();
if let Ok(user) = tx.load(user_uid) {
let user_class = builder
.sandbox
.classes
.get(user["class"].as_str().unwrap())
.unwrap();
match user[user_attr] {
serde_json::Value::Object(_) => {
user_class.attrs[user_attr].cmd.revert(
&mut Context::Restoring,
builder,
tx,
user_uid,
)?;
}
serde_json::Value::Array(_) => {
user[user_attr].as_array_mut().unwrap().retain(|v| v != uid);
}
_ => {}
}
let mut ctx = Context::Appending(AppendPayload {
class_override: Some(&class_name),
appended_uid: None,
});
user_class.attrs[user_attr]
.cmd
.apply(&mut ctx, builder, tx, user_uid)?;
tx.save(user_uid)?;
}
}
Ok(parent_uid.to_string())
}
pub fn reroll(
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
uid: &str,
class_override: Option<&str>,
) -> Result<String> {
let (parent_uid, parent_attr) = {
let entity = tx.load(uid)?;
let parent_spec = &entity["$parent"];
(
parent_spec["uid"].as_str().unwrap().to_string(),
parent_spec["attr"].as_str().unwrap().to_string(),
)
};
let parent_entity = tx.load(&parent_uid)?;
let parent_class_name = parent_entity["class"].as_str().unwrap().to_string();
let parent_class = &builder.sandbox.classes[&parent_class_name];
let mut ctx = Context::Rerolling(RerollPayload {
class_override,
existing_uid: uid.to_string(),
new_uid: None,
});
parent_class.attrs[&parent_attr]
.cmd
.apply(&mut ctx, builder, tx, &parent_uid)?;
unroll(builder, tx, uid, None)?;
if let Context::Rerolling(payload) = ctx {
if let Some(new_uid) = payload.new_uid {
return Ok(new_uid);
}
}
Err(anyhow!("Reroll failed due to an unknown reason"))
}
pub fn append(
builder: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
parent_uid: &str,
attr_name: &str,
class_override: Option<&str>,
) -> Result<String> {
let parent = tx.load(parent_uid)?;
let parent_class = parent["class"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("class key not found or not a string"))?;
let class = builder
.sandbox
.classes
.get(parent_class)
.ok_or_else(|| anyhow::anyhow!("class not found in instance"))?;
let attr = class
.attrs
.get(attr_name)
.ok_or_else(|| anyhow::anyhow!("attribute not found in class"))?;
let mut ctx = Context::Appending(AppendPayload {
class_override,
appended_uid: None,
});
attr.cmd.apply(&mut ctx, builder, tx, parent_uid)?;
if let Context::Appending(payload) = ctx {
if let Some(added_uid) = payload.appended_uid {
tx.save(parent_uid)?;
Ok(added_uid)
} else {
Err(anyhow!(
"Appending entity to {} in {} failed",
attr_name,
parent_uid
))
}
} else {
Err(anyhow!(
"Appending entity to {} in {} failed",
attr_name,
parent_uid
))
}
}
fn resolve_actual_class_to_roll<'a>(
builder: &'a SandboxBuilder,
class_name: &str,
) -> Result<&'a Class> {
let mut class_to_resolve = builder
.sandbox
.classes
.get(class_name)
.ok_or(anyhow!("class {} not found", class_name))?;
while class_to_resolve.subclasses != SubclassesSpecifier::Empty() {
class_to_resolve = match &class_to_resolve.subclasses {
SubclassesSpecifier::Var(variable_symbol) => {
let variable_name = &variable_symbol[1..]; let class_list = builder.sandbox.globals[variable_name]
.as_array()
.ok_or(anyhow!("Unable to find {}", variable_symbol))?;
let rolled_class_name = builder.randomizer.choose(class_list).as_str().unwrap();
builder
.sandbox
.classes
.get(rolled_class_name.trim())
.ok_or(anyhow!("class {} not found", rolled_class_name))?
}
SubclassesSpecifier::List(class_list) => {
let rolled_class_name = builder.randomizer.choose(class_list);
builder
.sandbox
.classes
.get(rolled_class_name)
.ok_or(anyhow!("class {} not found", rolled_class_name))?
}
SubclassesSpecifier::Empty() => class_to_resolve,
};
}
Ok(class_to_resolve)
}