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
use diesel::{Insertable, Queryable};

use crate::schema::translations;
use crate::VocabStoreError;

#[derive(Debug, Default, Insertable, Queryable, PartialEq)]
pub struct Translation {
    pub local: String,
    pub foreign: String,
    pub guesses_local_total: i32,
    pub guesses_local_correct: i32,
    pub guesses_foreign_total: i32,
    pub guesses_foreign_correct: i32,
}

fn normalised_percent(numerator: i32, denominator: i32) -> f64 {
    if denominator > 0 {
        numerator as f64 / denominator as f64
    } else {
        0.0
    }
}

impl Translation {
    pub fn new(local: &str, foreign: &str) -> Translation {
        Translation {
            local: local.to_lowercase(),
            foreign: foreign.to_lowercase(),
            ..Default::default()
        }
    }

    pub fn get_total_percent(&self) -> f64 {
        normalised_percent(
            self.guesses_local_correct + self.guesses_foreign_correct,
            self.guesses_local_total + self.guesses_foreign_total,
        )
    }

    pub fn guess_local(&mut self, guess: &str) -> bool {
        self.guesses_local_total += 1;
        if self.local.to_lowercase() == guess.to_lowercase() {
            self.guesses_local_correct += 1;
            true
        } else {
            false
        }
    }

    pub fn guess_foreign(&mut self, guess: &str) -> bool {
        self.guesses_foreign_total += 1;
        if self.foreign.to_lowercase() == guess.to_lowercase() {
            self.guesses_foreign_correct += 1;
            true
        } else {
            false
        }
    }

    pub fn reconcile(self, other: Translation) -> Result<Translation, VocabStoreError> {
        // Don't reconcile different translations
        if self.local != other.local || self.foreign != other.foreign {
            return Err(VocabStoreError::ReconciliationError);
        }

        // Take whichever side has most guesses
        let more_local = self.guesses_local_total > other.guesses_local_total;
        let more_foreign = self.guesses_foreign_total > other.guesses_foreign_total;

        let (guesses_local_total, guesses_local_correct) = if more_local {
            (self.guesses_local_total, self.guesses_local_correct)
        } else {
            (other.guesses_local_total, other.guesses_local_correct)
        };
        let (guesses_foreign_total, guesses_foreign_correct) = if more_foreign {
            (self.guesses_foreign_total, self.guesses_foreign_correct)
        } else {
            (other.guesses_foreign_total, other.guesses_foreign_correct)
        };

        Ok(Translation {
            local: self.local,
            foreign: self.foreign,
            guesses_local_total,
            guesses_local_correct,
            guesses_foreign_total,
            guesses_foreign_correct,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::Translation;
    use crate::VocabStoreError;

    #[test]
    fn test_guess_local() {
        let mut translation = Translation::new("yes", "はい");
        assert_eq!(translation.guess_local("yEs"), true);
        assert_eq!(translation.guesses_local_total, 1);
        assert_eq!(translation.guesses_local_correct, 1);
        assert_eq!(translation.guesses_foreign_total, 0);
        assert_eq!(translation.guesses_foreign_correct, 0);
        assert_eq!(translation.get_total_percent(), 1.0);

        assert_eq!(translation.guess_local("no"), false);
        assert_eq!(translation.guesses_local_total, 2);
        assert_eq!(translation.guesses_local_correct, 1);
        assert_eq!(translation.guesses_foreign_total, 0);
        assert_eq!(translation.guesses_foreign_correct, 0);
        assert_eq!(translation.get_total_percent(), 0.5);
    }

    #[test]
    fn test_guess_foreign() {
        let mut translation = Translation::new("yes", "はい");
        assert_eq!(translation.guess_foreign("はい"), true);
        assert_eq!(translation.guesses_local_total, 0);
        assert_eq!(translation.guesses_local_correct, 0);
        assert_eq!(translation.guesses_foreign_total, 1);
        assert_eq!(translation.guesses_foreign_correct, 1);
        assert_eq!(translation.get_total_percent(), 1.0);

        assert_eq!(translation.guess_foreign("いいえ"), false);
        assert_eq!(translation.guesses_local_total, 0);
        assert_eq!(translation.guesses_local_correct, 0);
        assert_eq!(translation.guesses_foreign_total, 2);
        assert_eq!(translation.guesses_foreign_correct, 1);
        assert_eq!(translation.get_total_percent(), 0.5);
    }

    #[test]
    fn test_reconcile() {
        let mut old_translation = Translation::new("yes", "はい");
        old_translation.guesses_local_correct = 4;
        old_translation.guesses_local_total = 5;
        old_translation.guesses_foreign_correct = 3;
        old_translation.guesses_foreign_total = 5;
        let mut new_translation = Translation::new("yes", "はい");
        new_translation.guesses_local_correct = 4;
        new_translation.guesses_local_total = 4;
        new_translation.guesses_foreign_correct = 6;
        new_translation.guesses_foreign_total = 6;
        let reconciled_translation = old_translation.reconcile(new_translation).unwrap();

        assert_eq!(reconciled_translation.local, "yes");
        assert_eq!(reconciled_translation.foreign, "はい");
        assert_eq!(reconciled_translation.guesses_local_correct, 4);
        assert_eq!(reconciled_translation.guesses_local_total, 5);
        assert_eq!(reconciled_translation.guesses_foreign_correct, 6);
        assert_eq!(reconciled_translation.guesses_foreign_total, 6);

        let mut old_translation = Translation::new("no", "いいえ");
        let mut new_translation = Translation::new("japan", "日本");
        match old_translation.reconcile(new_translation) {
            Err(VocabStoreError::ReconciliationError) => {}
            _ => assert!(false, "VocabStore did not return ReconciliationError error"),
        }
    }
}