extern crate crossterm;
extern crate serde;
extern crate serde_json;

use crossterm::style::{
    Colorize,
    Styler
};

use serde::{Serialize, Deserialize};

use std::io::{
    self,
    Write
};

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

mod arch;
mod bitran;
mod evol;
mod help;
mod par;
mod pop;
mod reset;
mod run;
mod save_load;
mod set;
mod stat;

#[derive(Serialize, Deserialize)]
struct Counters {
    epoch: usize
}

#[derive(Serialize, Deserialize)]
struct Settings {
    log: bool,
    test_all: bool,
    print_epochs_all: bool
}

/// Container of evolution process components.
pub struct Evolver {
    bitran: Box<dyn Bitransform>,
    population: Vec<Boolnet>,
    parameters: Parameters,
    counters: Counters,
    settings: Settings
}

// Defaults
const DEF_LOGGING: bool = false;
const DEF_TEST_ALL: bool = false;
const DEF_PRINT_EPOCHS_ALL: bool = false;

const DEF_POP: usize = 250;

const DEF_HEIGHT: usize = 1;
const DEF_SIZE: usize = 48;
const DEF_RANDS: usize = 2;
const DEF_LAYERS: usize = 1;
const DEF_CYCLES: usize = 1;

const DEF_BATCHES: usize = 8;
const DEF_REPLACE_RATIO: f64 = 0.7;
const DEF_PAR_RATIO: f64 = 0.8;
const DEF_PARENTS: usize = 2;
const DEF_PAR_SW_PROB: f64 = 0.9;
const DEF_MUTAT_PROB: f64 = 0.1;

impl Counters {
    fn new () -> Self {
        let epoch: usize = 0;
        Self{epoch}
    }
}

impl Settings {
    fn new () -> Self {
        let log = DEF_LOGGING;
        let test_all = DEF_TEST_ALL;
        let print_epochs_all = DEF_PRINT_EPOCHS_ALL;
        Self{log, test_all, print_epochs_all}
    }
}

impl Evolver {
    /// `register` must contain at least the entry about default Bitransform, `InvXor`.
    pub fn new_default(register: &BitransformRegister) -> Result<Self, String> {
        let bitran = register.new_from_desc(
            & BitransformDesc::new("InvXor", & vec![])
        )?;

        let mut population = Vec::<Boolnet>::new();
        for _ in 0..DEF_POP {
            population.push(Boolnet::new_rand(DEF_HEIGHT, DEF_SIZE, DEF_RANDS, DEF_LAYERS, DEF_CYCLES));
        }

        let parameters = Parameters{
            batches: DEF_BATCHES,
            replace_ratio: DEF_REPLACE_RATIO,
            par_ratio: DEF_PAR_RATIO,
            parents: DEF_PARENTS,
            par_sw_prob: DEF_PAR_SW_PROB,
            mutat_prob: DEF_MUTAT_PROB
        };

        let counters = Counters::new();

        let settings = Settings::new();

        Ok(Self{bitran, population, parameters, counters, settings})
    }

    /// Control evolution from basic shell.
    /// No history or autocompletion of commands... yet.
    pub fn shell(&mut self, register: &BitransformRegister) -> Result<(), String> {
        let mut hint_shown = false;

        loop {
            // Assertion: population is sorted by score, descending
            println!("");

            // Print state
            println!("{} {:?}", "BITRANSFORM:".blue().bold(), self.bitran);
            println!("{} {}", "POPULATION:".blue().bold(), self.population.len());
            if self.population.len() > 0 {
                let bn = & self.population[0];
                println!("{} {} = {}, {} = {}, {} = {}, {} = {}, {} = {}",
                    "ARCHITECTURE:".blue().bold(),
                    "height".yellow(), bn.height(),
                    "size".yellow(), bn.size(),
                    "rands".yellow(), bn.rands(),
                    "layers".yellow(), bn.layers(),
                    "cycles".yellow(), bn.cycles()
                );
            } else {
                println!("{}", "No population, create or load one.".red());
            }
            let params = & self.parameters;
            println!("{} {} = {}, {} = {}, {} = {}, {} = {}, {} = {}, {} = {}",
                "PARAMETERS:".blue().bold(),
                "batches".yellow(), params.batches,
                "replace_ratio".yellow(), params.replace_ratio,
                "par_ratio".yellow(), params.par_ratio,
                "parents".yellow(), params.parents,
                "par_sw_prob".yellow(), params.par_sw_prob,
                "mutat_prob".yellow(), params.mutat_prob
            );
            let settings = & self.settings;
            println!("{} {} {}, {} {}, {} {}",
                "SETTINGS:".dark_blue().bold(),
                "log".dark_yellow(), match settings.log {
                    false => "off", true => "on"
                }.dark_cyan(),
                "test".dark_yellow(), match settings.test_all {
                    false => "new", true => "all"
                }.dark_cyan(),
                "print".dark_yellow(), match settings.print_epochs_all {
                    false => "improve", true => "all"
                }.dark_cyan()
            );

            // Hint
            if !hint_shown {
                println!("{}", "Type \"?\" to see the list of available commands...".dark_grey());
                hint_shown = true;
            }

            // Prompt
            print!("$ ");
            let _ = io::stdout().flush();

            // Read input and process it... main part
            let mut input = String::new();
            match io::stdin().read_line(&mut input) {
                Ok(..) => {
                    input.pop(); // remove trailing newline
                    let tokens: Vec<&str> = input.split_whitespace().collect();
                    if tokens.len() > 0 {
                        let command = tokens[0];
                        match command {
                            "exit" | "quit" | "end" | "bye" | "q" => {
                                break;
                            },

                            "help" | "?" => {
                                let comm: &str = if tokens.len() > 1 {
                                    tokens[1]
                                } else {
                                    ""
                                };
                                self.help(comm);
                            },

                            "stat" => {
                                match self.stat() {
                                    Err(s) => {
                                        println!("{}", format!("Error: {}", s).red().bold());
                                    },
                                    _ => {}
                                }
                            },

                            "evol" | "e" => {
                                let (parsed, epochs) = if tokens.len() > 1 {
                                    match tokens[1].parse::<usize>() {
                                        Ok(epochs) => (true, epochs),
                                        Err(e) => {
                                            println!("{}", format!("Parse error: arg 1: {}", e.to_string()).red().bold());
                                            (false, 0)
                                        }
                                    }
                                } else {
                                    (true, 0)
                                };
                                if parsed {
                                    match self.evol(epochs) {
                                        Err(s) => {
                                            println!("{}", format!("Error: {}", s).red().bold());
                                        },
                                        _ => {}
                                    }
                                }
                            },

                            "par" | "p" => {
                                let (parsed, param, valstr) = if tokens.len() > 2 {
                                    (true, tokens[1], tokens[2])
                                } else {
                                    println!("{}", "Parse error: not enough args".red().bold());
                                    (false, "", "")
                                };
                                if parsed {
                                    match self.par(param, valstr) {
                                        Err(s) => {
                                            println!("{}", format!("Error: {}", s).red().bold());
                                        },
                                        _ => {}
                                    }
                                }
                            },

                            "arch" | "a" => {
                                let (parsed, height, size, rands, layers, cycles) = if tokens.len() > 5 {
                                    match [tokens[1].parse::<usize>(), tokens[2].parse::<usize>(), tokens[3].parse::<usize>(), tokens[4].parse::<usize>(), tokens[5].parse::<usize>()] {
                                        [Ok(height), Ok(size), Ok(rands), Ok(layers), Ok(cycles)] => (true, height, size, rands, layers, cycles),
                                        r => { // at least 1 is Err
                                            (0..5).for_each(|i| if let Err(e) = & r[i] {
                                                println!("{}", format!("Parse error: arg {}: {}", 1 + i, e.to_string()).red().bold());
                                            });
                                            (false, 0, 0, 0, 0, 0)
                                        }
                                    }
                                } else {
                                    println!("{}", "Parse error: not enough args".red().bold());
                                    (false, 0, 0, 0, 0, 0)
                                };
                                if parsed {
                                    match self.arch(height, size, rands, layers, cycles) {
                                        Err(s) => {
                                            println!("{}", format!("Error: {}", s).red().bold());
                                        },
                                        _ => {}
                                    }
                                }
                            },

                            "pop" => {
                                let (parsed, size) = if tokens.len() > 1 {
                                    match tokens[1].parse::<usize>() {
                                        Ok(size) => (true, size),
                                        Err(e) => {
                                            println!("{}", format!("Parse error: arg 1: {}", e.to_string()).red().bold());
                                            (false, 0)
                                        }
                                    }
                                } else {
                                    println!("{}", "Parse error: not enough args".red().bold());
                                    (false, 0)
                                };
                                if parsed {
                                    match self.pop(size) {
                                        Err(s) => {
                                            println!("{}", format!("Error: {}", s).red().bold());
                                        },
                                        _ => {}
                                    }
                                }
                            },

                            "reset" => {
                                match self.reset() {
                                    Err(s) => {
                                        println!("{}", format!("Error: {}", s).red().bold());
                                    },
                                    _ => {}
                                }
                            },

                            "bitran" | "b" => {
                                let (parsed, id, prefs) = if tokens.len() > 1 {
                                    let mut parsed: bool = true;
                                    let mut prefs = Vec::<usize>::new();
                                    for i in 2..tokens.len() {
                                        match tokens[i].parse::<usize>() {
                                            Ok(pref) => {
                                                prefs.push(pref);
                                            },
                                            Err(e) => {
                                                println!("{}", format!("Parse error: arg {}: {}", i - 1, e.to_string()).red().bold());
                                                parsed = false;
                                                break;
                                            }
                                        }
                                    }
                                    (parsed, tokens[1], prefs)
                                } else {
                                    (true, "", vec![])
                                };
                                if parsed {
                                    match self.bitran(register, id, &prefs) {
                                        Err(s) => {
                                            println!("{}", format!("Error: {}", s).red().bold());
                                        },
                                        _ => {}
                                    }
                                }
                            },

                            "run" | "r" => {
                                let (parsed, bn_ind, inp_hex) = if tokens.len() > 2 {
                                    match tokens[1].parse::<usize>() {
                                        Ok(bn_ind) => (true, bn_ind, tokens[2]),
                                        Err(e) => {
                                            println!("Parse error: arg 1: {}", e.to_string());
                                            (false, 0, "")
                                        }
                                    }
                                } else {
                                    println!("{}", "Parse error: not enough args".red().bold());
                                    (false, 0, "")
                                };
                                if parsed {
                                    match self.run(bn_ind, inp_hex) {
                                        Ok(outp_hex) => {
                                            println!("{}", &outp_hex);
                                        },
                                        Err(s) => {
                                            println!("{}", format!("Error: {}", s).red().bold());
                                        },
                                    }
                                }
                            },

                            "save" => {
                                let (parsed, dirpath) = if tokens.len() > 1 {
                                    (true, tokens[1])
                                } else {
                                    println!("{}", "Parse error: not enough args".red().bold());
                                    (false, "")
                                };
                                if parsed {
                                    match self.save(dirpath) {
                                        Err(s) => {
                                            println!("{}", format!("Error: {}", s).red().bold());
                                        },
                                        _ => {}
                                    }
                                }
                            },

                            "load" => {
                                let (parsed, dirpath) = if tokens.len() > 1 {
                                    (true, tokens[1])
                                } else {
                                    println!("{}", "Parse error: not enough args".red().bold());
                                    (false, "")
                                };
                                if parsed {
                                    match Self::load(register, dirpath) {
                                        Ok(evolver) => {
                                            *self = evolver;
                                        },
                                        Err(s) => {
                                            println!("{}", format!("Error: {}", s).red().bold());
                                        }
                                    }
                                }
                            },

                            "set" | "s" => {
                                let (parsed, setting, valstr) = if tokens.len() > 2 {
                                    (true, tokens[1], tokens[2])
                                } else {
                                    println!("{}", "Parse error: not enough args".red().bold());
                                    (false, "", "")
                                };
                                if parsed {
                                    match self.set(setting, valstr) {
                                        Err(s) => {
                                            println!("{}", format!("Error: {}", s).red().bold());
                                        },
                                        _ => {}
                                    }
                                }
                            },

                            "about" => {
                                base::about();
                            },

                            _ => {
                                println!("{}", "Unknown command".red().bold());
                            }
                        }
                    } else {
                        println!("{}", "No command, doing nothing".dark_yellow().bold());
                    }
                },
                Err(e) => {
                    println!("{}", format!("Error reading input: {}", e.to_string()).red().bold());
                }
            }

        }

        Ok(())
    }

}
