#[cfg(test)]
mod tests;

use std::fs::read_to_string;

use lexer::tokenize;
use token::{Token, TokenType};
use std::slice::Iter;

mod lexer;
mod token;

#[derive(Debug, PartialEq)]
pub enum GsblError {
    FailedToParseFile(String),
    FailedToParseArray(String),
    MalformedCode(String),
}

#[derive(Debug, PartialEq, Clone)]
enum GsblValueType {
    String,
    Int,
    Float,
    Bool,
    Table,
    Array,
}

#[derive(Debug, PartialEq, Clone)]
enum GsblValue {
    String(String),
    Int(i32),
    Float(f32),
    Bool(bool),
    Table(GsblTable),
    Array(Vec<GsblValue>)
}

#[derive(Debug, PartialEq, Clone)]
struct Data {
    name: String,
    value: GsblValue,
    value_type: GsblValueType
}

#[derive(Debug, PartialEq, Clone)]
pub struct GsblTable { // this is used as a variable type, so it's named GSBL and not Table
    data: Vec<Data>
}

impl GsblTable {
    fn expect<'a>(tokens: &'a mut Iter<Token>, token_type: TokenType) -> Result<&'a Token, GsblError> {
        let token = tokens.next().unwrap();
        if token.token_type != token_type {
            return Err(GsblError::MalformedCode(format!("Expected token {:?}, but found {:?}", token_type, token.token_type)));
        }
        Ok(token)
    }

    fn parse_table<'a>(
        tokens: &'a mut Iter<Token>,
        mut token: &'a Token
    ) -> Result<GsblTable, GsblError> {
        let mut table_data = Vec::new();

        while token.token_type != TokenType::CloseBrace
              && token.token_type != TokenType::EndOfFile {
            if token.token_type == TokenType::Identifier {
                table_data.push(Self::parse_variable(tokens, &token)?);
            }
            token = tokens.next().unwrap();
        }

        if token.token_type == TokenType::EndOfFile {
            return Err(GsblError::MalformedCode("Unterminated table".to_string()));
        }

        Ok(GsblTable {data: table_data})
    }

    fn parse_array<'a>(
        tokens: &'a mut Iter<Token>,
        mut token: &'a Token
    ) -> Result<Vec<GsblValue>, GsblError> {
        let mut array_data = Vec::new();

        let mut array_type = TokenType::Unknown;

        while token.token_type != TokenType::CloseBracket
            && token.token_type != TokenType::EndOfFile {
            if array_type == TokenType::Unknown {
                array_type = token.token_type.clone();
            }
            if array_type != token.token_type && token.token_type != TokenType::Comma {
                return Err(GsblError::MalformedCode("Multiple data types inside array".to_string()));
            }
            array_data.push(match token.token_type {
                TokenType::String => {
                    GsblValue::String(token.value.clone())
                },
                TokenType::Int => {
                    GsblValue::Int(token.value.parse::<i32>().unwrap())
                },
                TokenType::Float => {
                    GsblValue::Float(token.value.parse::<f32>().unwrap())
                },
                TokenType::Bool => {
                    GsblValue::Bool(token.value == "true")
                },
                TokenType::OpenBracket => {
                    GsblValue::Array(Self::parse_array(tokens, token)?) // recursion, yippee!!
                },
                TokenType::Comma => {
                    token = tokens.next().unwrap();
                    continue;
                }
                _ => { return Err(GsblError::FailedToParseArray(format!("Unknown token inside array declaration: {:?}", token.token_type))); }
            });
            token = tokens.next().unwrap();
        }

        if token.token_type == TokenType::EndOfFile {
            return Err(GsblError::MalformedCode("Unterminated array".to_string()));
        }

        Ok(array_data)
    }

    fn parse_variable<'a>(
        tokens: &'a mut Iter<Token>,
        mut token: &'a Token
    ) -> Result<Data, GsblError> {
        let var_name = token.value.clone();

        Self::expect(tokens, TokenType::Equals)?;
        token = tokens.next().unwrap();

        let value_type: GsblValueType;

        let data = Data {
            name: var_name,
            value: match token.token_type {
                TokenType::String => {
                    value_type = GsblValueType::String;
                    GsblValue::String(token.value.clone())
                },
                TokenType::Int => {
                    value_type = GsblValueType::Int;
                    GsblValue::Int(token.value.parse::<i32>().unwrap())
                },
                TokenType::Float => {
                    value_type = GsblValueType::Float;
                    GsblValue::Float(token.value.parse::<f32>().unwrap())
                },
                TokenType::Bool => {
                    value_type = GsblValueType::Bool;
                    GsblValue::Bool(token.value == "true")
                },
                TokenType::OpenBrace => {
                    token = tokens.next().unwrap();
                    value_type = GsblValueType::Table;
                    GsblValue::Table(Self::parse_table(tokens, token)?)
                },
                TokenType::OpenBracket => {
                    token = tokens.next().unwrap();
                    value_type = GsblValueType::Array;
                    GsblValue::Array(Self::parse_array(tokens, token)?)
                }
                _ => { return Ok(Data { value: GsblValue::Int(0), value_type: GsblValueType::Int, name: "coolplaceholder".to_string()}); }
            },
                    value_type
        };

        Ok(data)
    }

    pub fn parse_file(file_path: &str) -> Result<Self, GsblError> {
        let tokens = tokenize(&read_to_string(file_path).unwrap());

        if !tokens.is_ok() {
            return Err(tokens.unwrap_err());
        }

        let tokens = tokens.unwrap();
        let mut tokens = tokens.iter();

        let mut table: GsblTable = GsblTable { data: Vec::new() };

        while let Some(token) = tokens.next() {
            if token.token_type == TokenType::EndOfFile {
                break;
            }

            if token.token_type == TokenType::MultilineComment || token.token_type == TokenType::Comment {
                continue;
            }
            
            if token.token_type == TokenType::OpenBrace {
                table = Self::parse_table(&mut tokens, token)?;
                break;
            }
        }
        Ok(table)
    }

    
    fn get_value<T>(&self, var_name: &str, value_type: GsblValueType, map_fn: impl Fn(&GsblValue) -> Option<T>) -> Option<T> {
        for e in &self.data {
            if e.name == var_name && e.value_type == value_type {
                return map_fn(&e.value);
            }
        }
        None
    }

    fn get_array<T>(&self, var_name: &str, map_fn: impl Fn(&GsblValue) -> Option<T>) -> Option<Vec<T>> {
        for e in &self.data {
            if e.name == var_name && e.value_type == GsblValueType::Array {
                if let GsblValue::Array(a) = &e.value {
                    return Some(
                        a.iter()
                            .filter_map(|value| map_fn(value))
                            .collect()
                    );
                }
            }
        }
        None
    }

    pub fn get_table(&self, var_name: &str) -> Option<GsblTable> {
        self.get_value(var_name, GsblValueType::Table, |v| {
            if let GsblValue::Table(t) = v {
                Some((*t).clone())
            } else {
                None
            }
        })
    }

    pub fn get_string(&self, var_name: &str) -> Option<String> {
        self.get_value(var_name, GsblValueType::String, |v| {
            if let GsblValue::String(s) = v {
                Some((*s).clone())
            } else {
                None
            }
        })
    }

    pub fn get_string_array(&self, var_name: &str) -> Option<Vec<String>> {
        self.get_array(var_name, |v| {
            if let GsblValue::String(s) = v {
                Some((*s).clone())
            } else {
                None
            }
        })
    }

    pub fn get_int(&self, var_name: &str) -> Option<i32> {
        self.get_value(var_name, GsblValueType::Int, |v| {
            if let GsblValue::Int(i) = v {
                Some(*i)
            } else {
                None
            }
        })
    }

    pub fn get_int_array(&self, var_name: &str) -> Option<Vec<i32>> {
        self.get_array(var_name, |v| {
            if let GsblValue::Int(i) = v {
                Some(*i)
            } else {
                None
            }
        })
    }

    pub fn get_float(&self, var_name: &str) -> Option<f32> {
        self.get_value(var_name, GsblValueType::Float, |v| {
            if let GsblValue::Float(f) = v {
                Some(*f)
            } else {
                None
            }
        })
    }

    pub fn get_float_array(&self, var_name: &str) -> Option<Vec<f32>> {
        self.get_array(var_name, |v| {
            if let GsblValue::Float(f) = v {
                Some(*f)
            } else {
                None
            }
        })
    }

    pub fn get_bool(&self, var_name: &str) -> Option<bool> {
        self.get_value(var_name, GsblValueType::Bool, |v| {
            if let GsblValue::Bool(b) = v {
                Some(*b)
            } else {
                None
            }
        })
    }

    pub fn get_bool_array(&self, var_name: &str) -> Option<Vec<bool>> {
        self.get_array(var_name, |v| {
            if let GsblValue::Bool(b) = v {
                Some(*b)
            } else {
                None
            }
        })
    }
}
