1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use ayaka_primitive::RawValue;
use fallback::FallbackSpec;
use serde::{Deserialize, Serialize};
use std::{
    borrow::Cow,
    collections::{HashMap, VecDeque},
};

/// The unit of one line in an action.
///
/// If a frontend supports animation,
/// the characters in [`ActionSubText::Chars`] should be printed one by one,
/// while the characters in [`ActionSubText::Block`] should be printed together.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum ActionSubText {
    /// Characters printed one by one.
    /// Usually they are meaningful texts.
    Chars(String),
    /// Characters printed together.
    /// Usually they are HTML tags or other control characters.
    Block(String),
}

impl ActionSubText {
    /// Creates [`ActionSubText::Chars`].
    pub fn chars(s: impl Into<String>) -> Self {
        Self::Chars(s.into())
    }

    /// Creates [`ActionSubText::Block`].
    pub fn block(s: impl Into<String>) -> Self {
        Self::Block(s.into())
    }

    /// Gets a reference of [`str`].
    pub fn as_str(&self) -> &str {
        match self {
            Self::Chars(s) | Self::Block(s) => s,
        }
    }

    /// Gets the inner [`String`].
    pub fn into_string(self) -> String {
        match self {
            Self::Chars(s) | Self::Block(s) => s,
        }
    }
}

/// A map from variable name to [`RawValue`].
pub type VarMap = HashMap<String, RawValue>;

/// The serializable context.
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct RawContext {
    /// Current base paragraph tag.
    pub cur_base_para: String,
    /// Current paragraph tag.
    pub cur_para: String,
    /// Current text index.
    pub cur_act: usize,
    /// Current local variables.
    pub locals: VarMap,
}

/// The `text` is a [`VecDeque<ActionSubText>`].
/// The [`ActionSubText`] could be pushed and poped at front or back.
///
/// Generally, you should avoid using `push_back` directly.
/// To reduce allocations in serialization, you should use
/// `push_back_chars` and `push_back_block`.
///
/// ```
/// # use ayaka_bindings_types::*;
/// let mut text = ActionText::default();
/// text.push_back_chars("Hello ");
/// assert_eq!(text.text[0], ActionSubText::chars("Hello "));
/// text.push_back_chars("world!");
/// assert_eq!(text.text[0], ActionSubText::chars("Hello world!"));
/// ```
#[derive(Debug, Default, Clone, Serialize, Deserialize, FallbackSpec)]
pub struct ActionText {
    /// The full texts.
    pub text: VecDeque<ActionSubText>,
    /// The key of current character.
    pub ch_key: Option<String>,
    /// The current character.
    pub character: Option<String>,
    /// The temp variables.
    pub vars: VarMap,
}

impl ActionText {
    /// Push the string as [`ActionSubText::Chars`] to the back.
    /// If the back element is also [`ActionSubText::Chars`], the string is appended.
    pub fn push_back_chars<'a>(&mut self, s: impl Into<Cow<'a, str>>) {
        let s = s.into();
        if let Some(ActionSubText::Chars(text)) = self.text.back_mut() {
            text.push_str(&s);
        } else {
            self.text.push_back(ActionSubText::chars(s));
        }
    }

    /// Push the string as [`ActionSubText::Block`] to the back.
    /// If the back element is also [`ActionSubText::Block`], the string is appended.
    pub fn push_back_block<'a>(&mut self, s: impl Into<Cow<'a, str>>) {
        let s = s.into();
        if let Some(ActionSubText::Block(text)) = self.text.back_mut() {
            text.push_str(&s);
        } else {
            self.text.push_back(ActionSubText::block(s));
        }
    }
}

impl std::fmt::Display for ActionText {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for text in &self.text {
            write!(f, "{}", text.as_str())?;
        }
        Ok(())
    }
}

impl PartialEq for ActionText {
    fn eq(&self, other: &Self) -> bool {
        self.to_string() == other.to_string()
            && self.ch_key == other.ch_key
            && self.character == other.character
            && self.vars == other.vars
    }
}

/// The full action information in one line of config.
/// It provides the full texts and other properties exacted from [`ayaka_primitive::Text`].
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum Action {
    /// An empty action usually means an `exec` or custom action.
    #[default]
    Empty,
    /// A text action, display some texts.
    Text(ActionText),
    /// A switch action, display switches and let player to choose.
    Switches(Vec<Switch>),
    /// A custom action.
    Custom(VarMap),
}

/// One switch in the switches of an [`Action`].
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, FallbackSpec)]
pub struct Switch {
    /// The switch text.
    pub text: String,
    /// Whether the switch is enabled.
    pub enabled: bool,
}