use std::fs;

use crate::{
    base::{
        LoadFromJson,
        SaveToJson
    },
    bitransform::{
        BitransformDesc,
        BitransformRegister
    },
    boolnet::Boolnet,
    evolearn::Parameters
};

use super::{
    Counters,
    Evolver,
    Settings
};

const BITRAN_FILENAME: &str = "bitran.json";
const POP_DIR: &str = "population";
const BOOLNET_FILE_EXT: &str = "bnt";
const PARAMS_FILENAME: &str = "parameters.json";
const COUNTERS_FILENAME: &str = "counters.json";
const SETTINGS_FILENAME: &str = "settings.json";

impl Evolver {
    /// Saves evolver to dir.
    /// `dirpath` cannot start with "`/`" due to safety reasons.
    pub fn save(&self, dirpath: &str) -> Result<(), String> {
        if dirpath.starts_with("/") {
            return Err(String::from("Save path cannot start with \"/\" due to safety reasons"));
        }

        let pop_dirpath = format!("{}/{}", dirpath, POP_DIR);
        fs::create_dir_all(&pop_dirpath).map_err(|e| e.to_string())?;
        fs::remove_dir_all(&pop_dirpath).map_err(|e| e.to_string())?;
        fs::create_dir_all(&pop_dirpath).map_err(|e| e.to_string())?;
        // Now it should exist and be empty... also, now "dirpath" dir should exist

        self.bitran.desc().save_to_json(& format!("{}/{}", dirpath, BITRAN_FILENAME))?;

        for i in 0..self.population.len() {
            let filepath = format!("{}/{}.{}", pop_dirpath, i, BOOLNET_FILE_EXT);
            self.population[i].save(&filepath)?;
        }

        self.parameters.save_to_json(& format!("{}/{}", dirpath, PARAMS_FILENAME))?;
        self.counters.save_to_json(& format!("{}/{}", dirpath, COUNTERS_FILENAME))?;
        self.settings.save_to_json(& format!("{}/{}", dirpath, SETTINGS_FILENAME))?;

        Ok(())
    }

    /// Loads evolver from dir.
    pub fn load(register: &BitransformRegister, dirpath: &str) -> Result<Self, String> {
        let bitran_desc = BitransformDesc::load_from_json(& format!("{}/{}", dirpath, BITRAN_FILENAME))?;
        let bitran = register.new_from_desc(&bitran_desc)?;

        let mut population = Vec::<Boolnet>::new();
        let pop_dirpath = format!("{}/{}", dirpath, POP_DIR);
        for i in 0.. {
            let filepath = format!("{}/{}.{}", pop_dirpath, i, BOOLNET_FILE_EXT);
            match Boolnet::load(&filepath) {
                Ok(bn) => population.push(bn),
                Err(..) => break
            };
        }
        if population.len() == 0 {
            return Err(String::from("No boolnets were found, cannot load empty population"));
        }

        let parameters = Parameters::load_from_json(& format!("{}/{}", dirpath, PARAMS_FILENAME))?;

        let counters = Counters::load_from_json(& format!("{}/{}", dirpath, COUNTERS_FILENAME))?;

        let settings = Settings::load_from_json(& format!("{}/{}", dirpath, SETTINGS_FILENAME))?;

        // Don't evolve, because all boolnets are unscored (score = -inf) and thus "sorted" already
        Ok(Self{bitran, population, parameters, counters, settings})
    }

}
