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
use std::str::from_utf8;

#[derive(Debug, Clone)]
pub enum LuaType {
    Nil,
    Boolean(bool),
    Integer(i64),
    Number(f64),
    String(String),
    Table(std::collections::HashMap<String, LuaType>),
}

fn print_lua_type(value: LuaType, f: &mut std::fmt::Formatter, depth: usize) -> std::fmt::Result {
    match value {
        LuaType::Nil => write!(f, "nil"),
        LuaType::Boolean(b) => write!(f, "Boolean({})", b),
        LuaType::Integer(n) => write!(f, "Integer({})", n),
        LuaType::Number(n) => write!(f, "Number({})", n),
        LuaType::String(s) => write!(f, "String(\"{}\")", s),
        LuaType::Table(map) => {
            write!(f, "{{")?;
            for (key, value) in map.iter() {
                write!(f, "\n{}{} = ", " ".repeat(depth * 4), key)?;
                print_lua_type(value.clone(), f, depth + 1)?;
            }
            write!(f, "\n{}}}", " ".repeat((depth - 1) * 4))
        }
    }
}

impl std::fmt::Display for LuaType {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        print_lua_type(self.clone(), f, 1)
    }
}

pub struct LuaConfig {
    pub path: String,
    pub data: std::collections::HashMap<String, LuaType>,
}

impl LuaConfig {
    pub fn from_string(file: String, default: &[u8]) -> Self {
        let default: String = from_utf8(default).unwrap().to_string();
        let mut lua_config = LuaConfig {
            path: "File from String".to_string(),
            data: std::collections::HashMap::new(),
        };
        lua_config.init(file, default);
        lua_config
    }

    pub fn from_file(path: String, default: &[u8]) -> Self {
        let file = std::fs::read_to_string(path).unwrap();
        let default: String = from_utf8(default).unwrap().to_string();
        let mut lua_config = LuaConfig {
            path: "File from String".to_string(),
            data: std::collections::HashMap::new(),
        };
        lua_config.init(file, default);
        lua_config
    }

    pub fn init(&mut self, config: String, default: String) {
        let lua = rlua::Lua::new();

        let default_values = LuaConfig::get_hashmap_by_function(&lua, &default, "Default");
        let config_values = LuaConfig::get_hashmap_by_function(&lua, &config, "Config");

        // for (key, _value) in config_values.iter() {
        //     if !default_values.contains_key(key) {
        //         println!(
        //             "Config value \"{}\" is not in the default values, it will be skipped :(",
        //             key
        //        );
        //     }
        // }

        let mut resulting_config_values: std::collections::HashMap<String, rlua::Value> =
            std::collections::HashMap::new();
        for (key, value) in default_values.iter() {
            let config_value = config_values.get(key);
            match config_value {
                Some(conf_value) => {
                    resulting_config_values.insert(key.to_string(), conf_value.clone());
                }
                None => {
                    resulting_config_values.insert(key.to_string(), value.clone());
                }
            }
        }

        self.data = self.convert_map(resulting_config_values);
    }

    pub fn get(&self, key: &str) -> Option<&LuaType> {
        self.data.get(key)
    }

    fn get_hashmap_by_function<'lua>(
        lua: &'lua rlua::Lua,
        code: &str,
        function_name: &str,
    ) -> std::collections::HashMap<String, rlua::Value<'lua>> {
        let ctx = lua.load(code);
        ctx.exec().unwrap();
        let globals = lua.globals();
        let func = globals.get::<_, rlua::Function>(function_name).unwrap();
        let table = func.call::<_, rlua::Table>(()).unwrap();

        if table.is_empty() {
            eprintln!("Default function return table is empty");
            std::process::exit(1);
        }

        let mut values = std::collections::HashMap::new();
        for pair in table.pairs::<String, rlua::Value>() {
            let (key, value) = pair.unwrap();
            values.insert(key, value);
        }

        values
    }

    fn value_to_lua_type(&self, value: &rlua::Value) -> LuaType {
        match value {
            rlua::Value::Nil => LuaType::Nil,
            rlua::Value::Boolean(b) => LuaType::Boolean(*b),
            rlua::Value::Integer(n) => LuaType::Integer(*n),
            rlua::Value::Number(n) => LuaType::Number(*n),
            rlua::Value::String(s) => LuaType::String(s.to_str().unwrap_or_default().to_owned()),
            rlua::Value::Table(table) => {
                let mut map = std::collections::HashMap::new();
                for pair in table.clone().pairs::<String, rlua::Value>() {
                    if let Ok((key, value)) = pair {
                        map.insert(key, self.value_to_lua_type(&value));
                    }
                }
                LuaType::Table(map)
            }
            _ => unimplemented!("Conversion for this Lua type is not implemented yet"), // Handle other types as needed
        }
    }

    fn convert_map(
        &self,
        lua_map: std::collections::HashMap<String, rlua::Value>,
    ) -> std::collections::HashMap<String, LuaType> {
        let mut result = std::collections::HashMap::new();
        for (key, value) in lua_map {
            result.insert(key, self.value_to_lua_type(&value));
        }
        result
    }
}

impl std::fmt::Display for LuaConfig {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        for (key, value) in self.data.iter() {
            write!(f, "{} = {}\n", key, value)?;
        }
        Ok(())
    }
}