use crate::{Value, SJFLError};
use crate::file::SJFLFile;
use std::collections::HashMap;
use std::fs;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
use std::path::PathBuf;

pub struct Session {
    keywords: Vec<Vec<u8>>,
    str_table: HashMap<Vec<u8>, u32>,
    str_table_rev: HashMap<u32, Vec<u8>>,
    pub(crate) vars: HashMap<u32, Value>,

    dir: Option<String>,
    pub(crate) files: Vec<SJFLFile>
}

impl Session {

    pub fn new() -> Self {
        let mut result = Session {
            keywords: vec![
                b"true".to_vec(), b"false".to_vec(), b"null".to_vec(),
                b"True".to_vec(), b"False".to_vec(), b"None".to_vec(),
            ],
            str_table: HashMap::new(),
            str_table_rev: HashMap::new(),
            vars: HashMap::new(),
            dir: None,
            files: vec![]
        };

        for keyword in result.keywords.clone().iter() {
            result.intern_string(keyword);
        }

        result
    }

    pub fn new_dir(dir: &str) -> Result<Self, SJFLError> {
        let mut result = Self::new();

        result.init_dir(dir)?;

        Ok(result)
    }

    pub(crate) fn is_keyword(&self, i: u32) -> bool {
        i < self.keywords.len() as u32
    }

    pub(crate) fn is_valid_index(&self, i: u32) -> bool {
        self.str_table_rev.contains_key(&i)
    }

    pub(crate) fn get_string_from_index(&self, i: u32) -> Option<Vec<u8>> {
        self.str_table_rev.get(&i).map(|v| v.to_vec())
    }

    /// it loads the SJFL files in `dir`, and returns Ok(true)
    /// if it's already initialized, it does nothing and returns Ok(false)
    pub fn init_dir(&mut self, dir: &str) -> Result<bool, SJFLError> {

        if self.is_dir_init() {
            return Ok(false);
        }

        match fs::read_dir(dir) {
            Ok(entries) => {
                let mut files = vec![];

                for entry in entries {

                    if let Ok(entry) = entry {

                        if let Some(file) = entry.path().to_str() {

                            if let Ok(path) = PathBuf::from_str(file) {

                                if let Some(s) = path.extension() {

                                    if path.is_file() && s.to_str().is_some() {
                                        let ext = s.to_str().unwrap();
                                        let ext = ext.to_ascii_lowercase();

                                        let is_binary = if ext == "sjfl" || ext == "json" {
                                            false
                                        }

                                        else if ext == "bsjfl" {
                                            true
                                        }

                                        else {
                                            continue;
                                        };

                                        let is_json = ext == "json";

                                        let timestamp = match fs::metadata(&path) {
                                            Err(_) => {
                                                println!("It seems like your system does not support file metadata");
                                                continue;
                                            }
                                            Ok(metadata) if metadata.modified().is_err() || metadata.created().is_err() => {
                                                println!("It seems like your system does not support file metadata");
                                                continue;
                                            }
                                            Ok(metadata) => {
                                                let mut hasher = std::collections::hash_map::DefaultHasher::new();

                                                let modified = metadata.modified().unwrap();
                                                modified.hash(&mut hasher);

                                                let created = metadata.created().unwrap();
                                                created.hash(&mut hasher);

                                                hasher.finish()
                                            }
                                        };

                                        files.push(SJFLFile {
                                            path: path.to_str().unwrap().to_string(),
                                            is_binary,
                                            is_json,
                                            timestamp
                                        });
                                    }

                                }

                            }

                        }

                    }

                }

                self.files = files;
            },
            Err(e) => {
                return Err(SJFLError::FileError(format!("{e:?}")));
            }
        }

        Ok(true)
    }

    /// checks whether it has information of current dir
    pub fn is_dir_init(&self) -> bool {
        self.dir.is_some()
    }

    pub fn get_value_from_var_name(&self, var: &str) -> Option<Value> {
        match self.str_table.get(var.as_bytes()) {
            Some(v) => match self.vars.get(v) {
                Some(v) => Some(v.clone()),
                _ => None
            }
            _ => None
        }
    }

    pub(crate) fn get_value_from_var_index(&self, var: u32) -> Option<Value> {
        match self.vars.get(&var) {
            Some(v) => Some(v.clone()),
            _ => None
        }
    }

    pub(crate) fn intern_string(&mut self, buffer: &[u8]) -> u32 {

        match self.str_table.get(buffer) {
            Some(n) => *n,
            _ => {
                let index = self.str_table.len() as u32;
                self.str_table.insert(buffer.to_vec(), index);
                self.str_table_rev.insert(index, buffer.to_vec());

                index
            }
        }

    }

}