use std::{cell::RefCell, collections::BTreeMap};

use crate::choice_function::ChoiceFunction;

use super::{Chromosomes, Genetic, Genotype};

impl Genotype {
    pub fn generate<T: Genetic>(choice_function: ChoiceFunction) -> Self {
        let root = RootTranscriber::Generator(Generator {
            choice_function,
            progress: BTreeMap::new(),
        });
        let root_cell = RefCell::new(root);
        let transcriber = Transcriber {
            root: &root_cell,
            chromosome: String::from("base"),
        };
        let mut base = T::default();
        base.transcribe(transcriber);
        match root_cell.into_inner() {
            RootTranscriber::Generator(generator) => {
                let chromosomes = generator.progress;
                return Genotype { chromosomes };
            }
            _ => panic!("a generator should not be able to turn into anything else"),
        }
    }

    pub fn load<T: Genetic>(&self) -> T {
        let mut progress = BTreeMap::new();
        for (key, (lhs, rhs)) in self.chromosomes.iter() {
            progress.insert(
                key.clone(),
                // as a note: we could just store the character iterator!
                (0, Vec::from_iter(lhs.chars()), Vec::from_iter(rhs.chars())),
            );
        }
        let root = RootTranscriber::Loader(Loader { progress });
        let root_cell = RefCell::new(root);
        let transcriber = Transcriber {
            root: &root_cell,
            chromosome: String::from("base"),
        };
        let mut base = T::default();
        base.transcribe(transcriber);
        return base;
    }
}

enum RootTranscriber {
    Generator(Generator),
    Loader(Loader),
}

impl RootTranscriber {
    pub fn express<T: Clone>(
        &mut self,
        chromosome: &str,
        field: &mut T,
        possibilities: &[(char, T)],
    ) {
        match self {
            RootTranscriber::Generator(generator) => generator.express(chromosome, possibilities),
            RootTranscriber::Loader(loader) => {
                loader.express(chromosome, field, possibilities);
            }
        }
    }
}

pub struct Transcriber<'r> {
    root: &'r RefCell<RootTranscriber>,
    chromosome: String,
}

impl Transcriber<'_> {
    pub fn express<T: Clone>(&mut self, field: &mut T, possibilities: &[(char, T)]) {
        self.root
            .borrow_mut()
            .express(&self.chromosome, field, possibilities)
    }

    pub fn nest(&self, suffix: &str) -> Transcriber {
        return Transcriber {
            root: self.root,
            chromosome: format!("{}.{}", self.chromosome, suffix),
        };
    }
}
struct Generator {
    choice_function: ChoiceFunction,
    progress: BTreeMap<String, Chromosomes>,
}

impl Generator {
    pub fn express<T>(&mut self, chromosome: &str, possibilities: &[(char, T)]) {
        let lhs = self.choice_function.choose(possibilities);
        let rhs = self.choice_function.choose(possibilities);

        let progress = self
            .progress
            .entry(chromosome.to_string()) // SLOW!
            .or_insert((String::new(), String::new()));

        progress.0.push(lhs.0);
        progress.1.push(rhs.0);
    }
}

struct Loader {
    progress: BTreeMap<String, (usize, Vec<char>, Vec<char>)>,
}

impl Loader {
    pub fn express<T: Clone>(
        &mut self,
        chromosome: &str,
        field: &mut T,
        possibilities: &[(char, T)],
    ) {
        let (mut lhs, mut rhs) = ('?', '?');
        if let Some((ix, lhs_buf, rhs_buf)) = self.progress.get_mut(chromosome) {
            lhs = lhs_buf.get(*ix).cloned().unwrap_or('?');
            rhs = rhs_buf.get(*ix).cloned().unwrap_or('?');
            *ix += 1;
        }

        let mut expressed = lhs;
        if dominance(rhs) > dominance(lhs) {
            expressed = rhs;
        }
        for (allele, value) in possibilities.iter() {
            if *allele == expressed {
                *field = value.clone();
                return;
            }
        }
    }
}

fn dominance(allele: char) -> u8 {
    if allele.is_ascii_lowercase() {
        return 1;
    }
    if allele.is_ascii_uppercase() {
        return 2;
    }
    return 0;
}
