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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
use serde::{Deserialize, Serialize};
use std::{
    cell::RefCell,
    fmt::Display,
    ops::RangeInclusive,
    rc::{Rc, Weak},
};

use crate::{
    effects::{effect::EntityName, stats::Statistics},
    error::SAPTestError,
    pets::pet::Pet,
    shop::store::ShopState,
    FoodName,
};

use super::actions::Action;

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
/// Possible equality conditions to check.
pub enum EqualityCondition {
    /// Is same pet.
    IsSelf,
    /// Is this tier.
    Tier(usize),
    /// Has same name.
    Name(EntityName),
    /// Is this level.
    Level(usize),
    /// Has this [`Action`].
    Action(Box<Action>),
    /// Triggered by this [`Status`].
    Trigger(Status),
    /// Is frozen. Only available for shops.
    Frozen,
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
/// Conditions a `Team` is in.
pub enum TeamCondition {
    /// Previous team fight was win.
    PreviousWon,
    /// Previous team fight was draw.
    PreviousDraw,
    /// Previous team fight was loss.
    PreviousLoss,
    /// Has this many open slots.
    OpenSpaceEqual(usize),
    /// Has this many pets on team.
    NumberPetsEqual(usize),
    /// Has this many or more pets on team.
    NumberPetsGreaterEqual(usize),
}

impl TryFrom<&TeamCondition> for Status {
    type Error = SAPTestError;

    fn try_from(value: &TeamCondition) -> Result<Self, Self::Error> {
        match value {
            TeamCondition::PreviousWon => Ok(Status::WinBattle),
            TeamCondition::PreviousDraw => Ok(Status::DrawBattle),
            TeamCondition::PreviousLoss => Ok(Status::LoseBattle),
            _ => Err(SAPTestError::InvalidTeamAction {
                subject: "Convert TeamCondition Failure".to_string(),
                reason: "Team Condition must match a possible battle status (ex. Win or Lose)"
                    .to_string(),
            }),
        }
    }
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
/// Conditions a `Shop` is in.
pub enum ShopCondition {
    /// Shop is in this state.
    InState(ShopState),
    /// Gold is equal to this amount.
    GoldEqual(usize),
    /// Gold is greater than or equal to this amount.
    GoldGreaterEqual(usize),
}

/// Conditions to select [`Pet`]s or [`ShopItem`](crate::shop::store::ShopItem) by.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub enum ItemCondition {
    /// Is the healthiest (highest health) pet.
    Healthiest,
    /// Is the illest (lowest health) pet.
    Illest,
    /// Is the strongest (highest attack) pet.
    Strongest,
    /// Is the weakest (lowest attack) pet.
    Weakest,
    /// Is highest tier pet.
    HighestTier,
    /// Is lowest tier pet.
    LowestTier,
    /// Multiple conditions.
    Multiple(Vec<ItemCondition>),
    /// Multiple conditions. All must be met to be included.
    MultipleAll(Vec<ItemCondition>),
    /// Has the quality.
    Equal(EqualityCondition),
    /// Doesn't have this quality.
    NotEqual(EqualityCondition),
    /// All alive pets.
    None,
}

/// Positions to select pets by.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
pub enum Position {
    /// Some number of [`Pet`]s based on a given [`ItemCondition`].
    /// * 3rd argument will shuffle any found pets.
    N(ItemCondition, usize, bool),
    /// Any [`Pet`] that matches a given [`ItemCondition`].
    Any(ItemCondition),
    /// All [`Pet`]s that match a given [`ItemCondition`].
    All(ItemCondition),
    /// Position of self.
    OnSelf,
    /// Pet affected in [`Outcome`] trigger.
    TriggerAffected,
    /// Pet causing in [`Outcome`] trigger.
    TriggerAfflicting,
    /// First pet on [`Team`](crate::teams::team::Team).
    First,
    /// Last pet on [`Team`](crate::teams::team::Team).
    Last,
    /// Opposite team's pet at the current pet index.
    Opposite,
    /// Pets ahead of current pet.
    Ahead,
    /// A specified range on a [`Team`](crate::teams::team::Team).
    Range(RangeInclusive<isize>),
    /// A [`Pet`] relative to current [`Pet`].
    /// * Note: Empty slots are taken into consideration.
    Relative(isize),
    /// Nearest pet(s) ahead or behind from current [`Pet`].
    /// * Negative values check pets behind.
    /// * Positive values check pets ahead.
    Nearest(isize),
    /// Multiple [`Position`]s.
    Multiple(Vec<Position>),
    /// All [`Pet`]'s adjacent to current index.
    Adjacent,
    #[default]
    /// No position.
    None,
}

/// Target team for an effect.
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
pub enum Target {
    /// Friend team.
    Friend,
    /// Enemy team.
    Enemy,
    /// Shop.
    Shop,
    /// Either `Friend` or `Enemy` team.
    /// * Ex. [Badger](crate::pets::names::PetName::Badger)
    Either,
    #[default]
    /// No target.
    None,
}

/// The outcome of any [`Pet`] action. Serve as [`Effect`](crate::Effect) triggers in battle.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Outcome {
    /// Status of a [`Pet`].
    pub status: Status,
    #[serde(skip)]
    /// The affected pet.
    pub(crate) affected_pet: Option<Weak<RefCell<Pet>>>,
    /// The affected team.
    pub affected_team: Target,
    #[serde(skip)]
    /// The pet causing the status_update.
    pub(crate) afflicting_pet: Option<Weak<RefCell<Pet>>>,
    /// The team causing the status update.
    pub afflicting_team: Target,
    /// General position on `affected_team`.
    pub position: Position,
    /// Difference in [`Statistics`] after status update from initial state.
    pub(crate) stat_diff: Option<Statistics>,
}

impl PartialEq for Outcome {
    fn eq(&self, other: &Self) -> bool {
        let same_affected_pet = if let (Some(pet), Some(other_pet)) =
            (self.affected_pet.as_ref(), other.affected_pet.as_ref())
        {
            pet.ptr_eq(other_pet)
        } else {
            self.affected_pet.is_none() && other.affected_pet.is_none()
        };
        same_affected_pet
            && self.status == other.status
            && self.position == other.position
            && self.affected_team == other.affected_team
            && self.afflicting_team == other.afflicting_team
    }
}

impl Default for Outcome {
    fn default() -> Self {
        Self {
            status: Status::None,
            affected_pet: Default::default(),
            affected_team: Target::None,
            afflicting_pet: Default::default(),
            afflicting_team: Target::None,
            position: Position::None,
            stat_diff: Default::default(),
        }
    }
}
impl Display for Outcome {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "[Status: {:?}, Position: {:?}, Affected: {:?}, From: {:?}]",
            self.status, self.position, self.affected_pet, self.afflicting_pet
        )
    }
}

impl Outcome {
    /// Attach the affected pet to this trigger.
    /// # Example.
    /// ```
    /// use std::{rc::Rc, cell::RefCell};
    /// use saptest::{Pet, PetName, effects::trigger::TRIGGER_SELF_FAINT};
    ///
    /// let ant = Rc::new(RefCell::new(Pet::try_from(PetName::Ant).unwrap()));
    /// let mut faint_trigger = TRIGGER_SELF_FAINT.clone();
    /// // Set affected pet to be ant.
    /// faint_trigger.set_affected(&ant);
    ///
    /// let affected_pet = faint_trigger.get_affected().unwrap();
    /// assert!(affected_pet.ptr_eq(&Rc::downgrade(&ant)));
    /// ```
    pub fn set_affected(&mut self, pet: &Rc<RefCell<Pet>>) -> &mut Self {
        self.affected_pet = Some(Rc::downgrade(pet));
        self
    }

    /// Attach the afflicting pet to this trigger.
    /// # Example.
    /// ```
    /// use std::{rc::Rc, cell::RefCell};
    /// use saptest::{Pet, PetName, effects::trigger::TRIGGER_SELF_FAINT};
    ///
    /// let ant = Rc::new(RefCell::new(Pet::try_from(PetName::Ant).unwrap()));
    /// let mosquito = Rc::new(RefCell::new(Pet::try_from(PetName::Mosquito).unwrap()));
    /// let mut faint_trigger = TRIGGER_SELF_FAINT.clone();
    /// // Set affected pet to be ant and afflicting pet to be mosquito.
    /// faint_trigger.set_affected(&ant).set_afflicting(&mosquito);
    ///
    /// let afflicting_pet = faint_trigger.get_afflicting().unwrap();
    /// assert!(afflicting_pet.ptr_eq(&Rc::downgrade(&mosquito)));
    /// ```
    pub fn set_afflicting(&mut self, pet: &Rc<RefCell<Pet>>) -> &mut Self {
        self.afflicting_pet = Some(Rc::downgrade(pet));
        self
    }

    /// Get the affected pet of a trigger.
    /// # Example
    /// ```
    /// use saptest::effects::trigger::TRIGGER_START_BATTLE;
    /// // No single affected pet as affects every pet.
    /// assert!(TRIGGER_START_BATTLE.get_affected().is_none())
    /// ```
    pub fn get_affected(&self) -> Option<Weak<RefCell<Pet>>> {
        self.affected_pet.as_ref().cloned()
    }

    /// Get the afflicting pet of a trigger.
    /// # Example
    /// ```
    /// use saptest::effects::trigger::TRIGGER_START_BATTLE;
    /// // No single afflicting pet as no pet causes the start of battle.
    /// assert!(TRIGGER_START_BATTLE.get_afflicting().is_none())
    /// ```
    pub fn get_afflicting(&self) -> Option<Weak<RefCell<Pet>>> {
        self.afflicting_pet.as_ref().cloned()
    }
}
/// Status of [`Entity`](super::effect::Entity).
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub enum Status {
    /// Start of Turn.
    StartTurn,
    /// End of Turn.
    EndTurn,
    /// Shop tier upgraded.
    ShopTierUpgrade,
    /// Start of Battle.
    StartOfBattle,
    /// After start of battle, prior to first battle.
    BeforeFirstBattle,
    /// Won the Battle.
    WinBattle,
    /// Loss the battle.
    LoseBattle,
    /// Drew
    DrawBattle,
    /// Before pet attacks.
    BeforeAttack,
    /// Pet is attacking.
    Attack,
    /// Any damage calculation
    AnyDmgCalc,
    /// Indirect dmg attack calculation.
    IndirectAttackDmgCalc,
    /// Direct dmg attack calculation.
    AttackDmgCalc,
    /// Pet levels up.
    Levelup,
    /// Food bought.
    BuyFood,
    /// Food eaten.
    AteFood,
    /// Specific food eaten.
    AteSpecificFood(FoodName),
    /// Pet bought.
    BuyPet,
    /// Pet sold.
    Sell,
    /// Shop rolled.
    Roll,
    /// Pet hurt.
    Hurt,
    /// Pet fainted.
    Faint,
    /// Pet knocked out during an attack.
    /// * After [`attack`](crate::pets::combat::PetCombat::attack) or [`indirect_attack`](crate::pets::combat::PetCombat::indirect_attack)
    KnockOut,
    /// Pet summoned.
    Summoned,
    /// Pet pushed.
    Pushed,
    /// No status change.
    None,
}