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
use crate::RootConfig;
use colored::*;
use colorsys::Rgb;
use serde_json::Value;

/// Defines a printable
///
/// # Properties
/// - `color` - The color that needs to be used for the unicode
/// - `count` - The number of contributions done by the user on that specific date
#[derive(Clone, Default, Debug, PartialEq, Eq)]
pub struct Printable {
    pub color: String,
    pub count: i64,
}

/// Defines all the options for the printable
///
/// This might be a little tricky, you might wanna read the detailed explained docs - https://takoyaki.vercel.app/docs/creating-print-options
pub struct PrintOptions<'a> {
    /// The root for the weeks array
    pub weeks_root: &'a str,
    /// The root for the day's contributions
    pub days_root: &'a str,
    /// Key for the contribution count
    pub contribution_count_key: &'a str,
    /// Key for the contribution color
    pub contribution_color_key: &'a str,
}

/// Handles printing the data to the terminal
pub struct PrintableGrid {
    /// The raw grid
    pub grid: Vec<Vec<Printable>>,
}

impl Default for PrintableGrid {
    fn default() -> Self {
        Self::new()
    }
}

impl PrintableGrid {
    /// Creates a new instance of the printable
    ///
    /// # Example:
    /// ```
    /// use takoyaki::PrintableGrid;
    ///
    /// let printable = Printable::new()
    /// ```
    pub fn new() -> Self {
        Self { grid: vec![] }
    }

    /// Iterates over the raw data and find the data at the specific destionation
    ///
    /// # Arguments
    /// - `data` - The raw data that needs to be looped through
    /// - `dest` - A string that includes all the key path to the destination separated by dots.
    ///
    /// # Example:
    /// ```
    /// use takoyaki::PrintableGrid;
    /// use serde_json::json;
    ///
    /// let data = json!({
    ///     "children": {
    ///         "are_human_dumb": true
    ///     }
    /// });
    ///
    /// assert_eq!(PrintableGrid::pluck(&data, "children.are_human_dumb"), json!(true))
    /// ```
    pub fn pluck(data: &Value, dest: &str) -> Value {
        // Clone the existing data
        let mut data_clone = data.clone();

        // Iterate through the path
        for i in dest.split('.') {
            // Mutate the data
            data_clone = data_clone.get(i).unwrap().clone();
        }

        // Return the path
        data_clone
    }

    /// Inserts a `item` at `x` and `y` positions in a 2d grid
    ///
    /// # Arguments
    /// - `x`: The position on the x asis
    /// - `y`: The position on the y asis
    /// - `item`: The item that needs to be inserted
    ///
    /// # Example
    /// ```
    /// use takoyaki::{PrintableGrid,Printable};
    ///
    /// let mut grid = PrintableGrid::new();
    ///
    /// let printable = Printable { color: "".to_string(), count: 0 };
    /// grid.insert_at(2, 2, printable.clone());
    ///
    /// assert_eq!(grid.grid[2][2], printable);
    /// ```
    pub fn insert_at(&mut self, x: usize, y: usize, item: Printable) {
        // Resize the grid on the x axis with vec![] as a default fill value
        if self.grid.len() <= x {
            self.grid.resize(x + 1, vec![])
        }

        // Resize the grid on the y axis with PrintableGrid::default() as a default fill value
        if self.grid[x].len() <= y {
            self.grid[x].resize(y, Printable::default())
        }

        // Insert at the specific location
        self.grid[x].insert(y, item);
    }

    /// Generates the grid according the the print option
    pub fn generate_grid(&mut self, opts: PrintOptions, data: Value) {
        // Traverse through the weeks
        let mut x = 0;

        for (y, week) in Self::pluck(&data, opts.weeks_root)
            .as_array()
            .unwrap()
            .iter()
            .enumerate()
        {
            // Iterate through the days
            for day in Self::pluck(week, opts.days_root).as_array().unwrap() {
                let color_temp = Self::pluck(day, opts.contribution_color_key);
                let color = color_temp.as_str().unwrap().to_string();
                let count = Self::pluck(day, opts.contribution_count_key)
                    .as_i64()
                    .unwrap();

                self.insert_at(x, y, Printable { color, count });

                x += 1;
            }

            x = 0;
        }
    }

    /// Pretty prints the grid
    pub fn pretty_print(&self) -> crate::Result<()> {
        let config = RootConfig::parse()?;

        for x in &self.grid {
            for y in x {
                // Create a fallback color
                let fallback_color = serde_json::Value::String(y.color.clone());

                // Get the color to use
                let raw_color = config
                    .colors
                    .get(format!("{}_contribution", y.count))
                    .or_else(|| config.colors.get("x_contribution"))
                    .unwrap_or(&fallback_color);

                // Convert it to rgb
                let color = Rgb::from_hex_str(raw_color.as_str().unwrap())?;

                if config.unicode.paint_bg {
                    // Print the unicode
                    print!(
                        "{}",
                        config.unicode.character.on_truecolor(
                            color.red() as u8,
                            color.green() as u8,
                            color.blue() as u8
                        )
                    )
                } else {
                    print!(
                        "{}",
                        config.unicode.character.truecolor(
                            color.red() as u8,
                            color.green() as u8,
                            color.blue() as u8
                        )
                    )
                }
            }

            println!()
        }

        Ok(())
    }
}