use super::{Map, QualifiedName, State, Story};
use crate::{
error::{Error, Result},
traits::FromStr,
traits::{FromMessagePack, FromYaml, LoadYaml, SaveMessagePack},
Load, LoadMessagePack, Save, SaveYaml, Section, StateMod, Value, GLOBAL,
};
use serde::{Deserialize, Serialize};
use std::{fmt, path::Path};
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Position {
#[serde(default)]
pub namespace: String,
#[serde(default)]
pub passage: String,
#[serde(default)]
pub line: usize,
}
impl Default for Position {
fn default() -> Self {
Self {
namespace: GLOBAL.to_string(),
passage: String::new(),
line: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
pub struct Bookmark {
#[serde(default)]
pub state: Map<String, State>,
#[serde(default)]
position: Position,
#[serde(default)]
pub stack: Vec<Position>,
#[serde(default)]
pub snapshots: Map<String, Vec<Position>>,
}
impl<'a> Bookmark {
pub fn new(state: Map<String, State>) -> Self {
Self {
state,
..Self::default()
}
}
#[inline]
pub fn namespace(&self) -> &str {
&self.position.namespace
}
#[inline]
pub fn passage(&self) -> &str {
&self.position.passage
}
#[inline]
pub fn line(&self) -> usize {
self.position.line
}
#[inline]
pub fn next_line(&mut self) {
self.position.line += 1
}
#[inline]
pub fn skip_lines(&mut self, lines: usize) {
self.position.line += lines
}
#[inline]
pub fn set_line(&mut self, line: usize) {
self.position.line = line
}
#[inline]
pub fn position(&self) -> &Position {
&self.position
}
#[inline]
pub fn set_passage(&mut self, passage: String) {
self.position.passage = passage;
}
#[inline]
pub fn set_namespace(&mut self, namespace: String) {
self.position.namespace = namespace;
}
#[inline]
pub fn set_position(&mut self, position: Position) {
self.position = position;
}
pub fn update_position(&mut self, namespace: String, passage: String) {
self.position.namespace = namespace;
self.position.passage = passage;
}
pub fn value(&'a self, var: &str) -> Result<&'a Value> {
let qname = QualifiedName::from(&self.position.namespace, var);
for namespace in qname.resolve() {
if let Some(section) = self.state.get(namespace) {
if let Some(val) = section.get(qname.name) {
return Ok(val);
}
} else {
return Err(error!("No state for namespace '{}'", namespace));
}
}
Err(error!(
"Var '{}' could not be found in namespace '{}' nor any of its parents.",
qname.name, qname.namespace
))
}
pub fn set_value(&'a mut self, statemod: StateMod, value: Value) -> Result<()> {
let qname = QualifiedName::from(&self.position.namespace, statemod.var);
for namespace in qname.resolve() {
if let Some(section) = self.state.get_mut(namespace) {
if let Some(value_mut) = section.get_mut(qname.name) {
return statemod.apply(value_mut, value);
}
} else {
return Err(error!("No state for namespace '{}'", namespace));
}
}
Err(error!(
"Var '{}' could not be found in namespace '{}' nor any of its parents.",
qname.name, qname.namespace
))
}
pub fn set_state(&mut self, state: &State) -> Result<()> {
for (key, value) in state {
let mut value = value.clone();
value.eval_as_expr(self)?;
let replaced: String;
let mut statemod_expr = key;
if key.starts_with("$passage") {
replaced = format!(
"${}{}",
&self.position.passage,
&statemod_expr["$passage".len()..]
);
statemod_expr = &replaced;
}
self.set_value(StateMod::from_str(statemod_expr)?, value)?;
}
Ok(())
}
fn default_val(state: &mut State, var: &str, val: &Value) {
if state.get(var).is_none() {
state.insert(var.to_string(), val.clone());
}
}
fn default_passage_expansion(
var: &str,
val: &Value,
section: &Section,
section_state: &mut State,
) {
for passage in section.passages.keys() {
let replaced = format!("{}{}", passage, &var["$passage".len()..]);
Self::default_val(section_state, &replaced, val);
}
}
fn init_section_state(&mut self, namespace: &str, story: &Story, section: &Section) {
let section_state = match self.state.get_mut(namespace) {
None => {
self.state.insert(namespace.to_string(), State::default());
self.state.get_mut(namespace).unwrap()
}
Some(state) => state,
};
for (var, val) in section.state() {
if var.starts_with("$passage") {
Self::default_passage_expansion(var, val, section, section_state);
} else {
Self::default_val(section_state, var, val);
}
}
Self::init_parent_expansions(namespace, story, section, section_state)
}
fn init_parent_expansions(
namespace: &str,
story: &Story,
section: &Section,
section_state: &mut State,
) {
let qname = QualifiedName::from(namespace, "");
let mut parents_iter = qname.resolve();
parents_iter.next(); for parent_namespace in parents_iter {
if let Some(parent_section) = story.sections.get(parent_namespace) {
for (var, val) in parent_section.state() {
if var.starts_with("$passage") {
Self::default_passage_expansion(var, val, section, section_state);
}
}
}
}
}
pub fn init_state(&mut self, story: &Story) {
for (namespace, section) in &story.sections {
self.init_section_state(namespace, story, section);
}
}
pub fn save_snapshot(&mut self, name: &str) {
let mut stack = self.stack.clone();
stack.push(self.position.clone());
self.snapshots.insert(name.to_string(), stack);
}
pub fn load_snapshot(&mut self, name: &str) -> Result<()> {
if let Some(stack) = self.snapshots.remove(name) {
self.stack = stack;
if let Some(position) = self.stack.pop() {
self.position = position;
Ok(())
} else {
Err(error!("Snapshot named '{}' was empty", name))
}
} else {
Err(error!("No snapshot named '{}'", name))
}
}
pub fn qualified_character_name(&self, story: &Story, character: &str) -> Result<String> {
let qname = QualifiedName::from(self.namespace(), character);
let (resolved_namespace, _section, _chardata) = story.character(&qname)?;
Ok(qname.to_string(resolved_namespace))
}
pub fn load_or_default<P: AsRef<Path> + fmt::Debug>(
path: P,
story: &Story,
default_passage: String,
) -> Result<Self> {
if path.as_ref().exists() {
let mut bookmark = Self::load(path)?;
bookmark.init_state(story);
Ok(bookmark)
} else {
let mut bookmark = Self::default();
bookmark.init_state(story);
bookmark.set_passage(default_passage);
bookmark.save(path)?;
Ok(bookmark)
}
}
}
impl FromYaml for Bookmark {}
impl FromMessagePack for Bookmark {}
impl SaveYaml for Bookmark {}
impl SaveMessagePack for Bookmark {}
impl Save for Bookmark {}
impl LoadYaml for Bookmark {}
impl LoadMessagePack for Bookmark {}
impl Load for Bookmark {}