use std::ops::Not;

use crate::push::state::{Gene, PushState};

use super::common::code_pop;

/// Checks to see if a single gene is a block.
fn _is_block(vals: Vec<Gene>) -> Option<bool> {
    Some(match vals[0] {
        Gene::Block(_) => true,
        _ => false,
    })
}
make_instruction_clone!(code, boolean, _is_block, Gene, 1);

/// Checks to see if a single gene is not a block.
fn _is_singular(vals: Vec<Gene>) -> Option<bool> {
    Some(_is_block(vals)?.not())
}
make_instruction_clone!(code, boolean, _is_singular, Gene, 1);

/// Returns the length of a block, else 1 if not a block
fn _length(vals: Vec<Gene>) -> Option<i128> {
    Some(match &vals[0] {
        Gene::Block(x) => x.len() as i128,
        _ => 1,
    })
}
make_instruction_clone!(code, int, _length, Gene, 1);

/// Returns the first item in a block if doable, else None
fn _first(vals: Vec<Gene>) -> Option<Gene> {
    match &vals[0] {
        Gene::Block(x) => {
            if x.len() > 1 {
                Some(x[0].clone())
            } else {
                None
            }
        }
        _ => None,
    }
}
make_instruction_clone!(code, code, _first, Gene, 1);

/// Returns the first item in a block if applicable, else None
fn _last(vals: Vec<Gene>) -> Option<Gene> {
    match &vals[0] {
        Gene::Block(x) => {
            if x.len() > 1 {
                Some(x.last()?.clone())
            } else {
                None
            }
        }
        _ => None,
    }
}
make_instruction_clone!(code, code, _last, Gene, 1);

/// Returns all but the first code item in a block if applicable, else None
fn _rest(vals: Vec<Gene>) -> Option<Gene> {
    match &vals[0] {
        Gene::Block(x) => {
            if x.len() > 1 {
                Some(Gene::Block(Box::new(x[1..].to_vec())))
            } else {
                None
            }
        }
        _ => None,
    }
}
make_instruction_clone!(code, code, _rest, Gene, 1);

/// Returns all but the first code item in a block if applicable, else None
fn _but_last(vals: Vec<Gene>) -> Option<Gene> {
    match &vals[0] {
        Gene::Block(x) => {
            let x_len = x.len();
            if x_len > 1 {
                Some(Gene::Block(Box::new(x[..x_len - 1].to_vec())))
            } else {
                None
            }
        }
        _ => None,
    }
}
make_instruction_clone!(code, code, _but_last, Gene, 1);

/// Returns all of the vals wrapped in a code block
fn _wrap_block(vals: Vec<Gene>) -> Option<Gene> {
    Some(Gene::Block(Box::new(vals)))
}
make_instruction_clone!(code, code, _wrap_block, Gene, 1);

/// Combines two genes into one. Accounts for blocks.
/// If the second gene is a block and the first one isn't,
/// appends the first gene to the second gene.
fn _combine(vals: Vec<Gene>) -> Option<Gene> {
    match (&vals[0], &vals[1]) {
        (Gene::Block(x), Gene::Block(y)) => {
            let x_clone = x.clone();
            let mut y_clone = y.clone();
            y_clone.extend(x_clone.into_iter());
            Some(Gene::Block(y_clone))
        }
        (Gene::Block(x), y) => {
            let mut x_clone = x.clone();
            x_clone.push(y.clone());
            Some(Gene::Block(x_clone))
        }
        (x, Gene::Block(y)) => {
            let mut y_clone = y.clone();
            y_clone.push(x.clone());
            Some(Gene::Block(y_clone))
        }
        (x, y) => Some(Gene::Block(Box::new(vec![x.clone(), y.clone()]))),
    }
}
make_instruction_clone!(code, code, _combine, Gene, 2);

/// Pushes `code_pop` and the top item of the code stack to the exec stack.
/// Top code item gets executed before being removed from code stack.
fn code_do_then_pop(state: &mut PushState) {
    if state.code.is_empty() {
        return;
    }
    let c = state.code[state.code.len() - 1].clone();
    state.exec.push(Gene::StateFunc(code_pop));
    state.exec.push(c);
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{instructions::numeric::int_add, push::state::EMPTY_STATE};
    use rust_decimal::dec;

    #[test]
    fn is_block_test() {
        let mut test_state = EMPTY_STATE;

        test_state.code = vec![Gene::Block(Box::new(vec![]))];
        code_is_block(&mut test_state);
        assert_eq!(vec![true], test_state.boolean);
        test_state.boolean.clear();

        test_state.code = vec![(Gene::GeneInt(1))];
        code_is_block(&mut test_state);
        assert_eq!(vec![false], test_state.boolean);
    }

    #[test]
    fn is_singular_test() {
        let mut test_state = EMPTY_STATE;

        test_state.code = vec![Gene::Block(Box::new(vec![]))];
        code_is_singular(&mut test_state);
        assert_eq!(vec![false], test_state.boolean);
        test_state.boolean.clear();

        test_state.code = vec![(Gene::GeneInt(1))];
        code_is_singular(&mut test_state);
        assert_eq!(vec![true], test_state.boolean);
    }

    #[test]
    fn length_test() {
        let mut test_state = EMPTY_STATE;

        test_state.code = vec![Gene::Block(Box::new(vec![
            Gene::GeneInt(1),
            Gene::GeneFloat(dec!(3.8)),
        ]))];
        code_length(&mut test_state);
        assert_eq!(vec![2], test_state.int);
        test_state.int.clear();

        test_state.code = vec![Gene::Block(Box::new(vec![]))];
        code_length(&mut test_state);
        assert_eq!(vec![0], test_state.int);
        test_state.int.clear();

        test_state.code = vec![Gene::GeneInt(3)];
        code_length(&mut test_state);
        assert_eq!(vec![1], test_state.int);
    }

    #[test]
    fn first_test() {
        let mut test_state = EMPTY_STATE;

        test_state.code = vec![Gene::Block(Box::new(vec![
            Gene::GeneInt(1),
            Gene::GeneFloat(dec!(3.8)),
        ]))];
        code_first(&mut test_state);
        assert_eq!(vec![Gene::GeneInt(1)], test_state.code);

        test_state.code = vec![];
        code_first(&mut test_state);
        let empty_vec: Vec<Gene> = vec![];
        assert_eq!(empty_vec, test_state.code);
        drop(empty_vec);

        test_state.code = vec![Gene::GeneInt(1)];
        code_first(&mut test_state);
        assert_eq!(vec![Gene::GeneInt(1)], test_state.code);
    }

    #[test]
    fn last_test() {
        let mut test_state = EMPTY_STATE;

        test_state.code = vec![Gene::Block(Box::new(vec![
            Gene::GeneInt(1),
            Gene::GeneFloat(dec!(3.8)),
        ]))];
        code_last(&mut test_state);
        assert_eq!(vec![Gene::GeneFloat(dec!(3.8))], test_state.code);

        test_state.code = vec![];
        code_last(&mut test_state);
        let empty_vec: Vec<Gene> = vec![];
        assert_eq!(empty_vec, test_state.code);
        drop(empty_vec);

        test_state.code = vec![Gene::GeneInt(1)];
        code_last(&mut test_state);
        assert_eq!(vec![Gene::GeneInt(1)], test_state.code);
    }

    #[test]
    fn rest_test() {
        let mut test_state = EMPTY_STATE;

        test_state.code = vec![Gene::Block(Box::new(vec![
            Gene::GeneInt(1),
            Gene::GeneFloat(dec!(3.8)),
            Gene::GeneBoolean(true),
        ]))];
        code_rest(&mut test_state);
        assert_eq!(
            vec![Gene::Block(Box::new(vec![
                Gene::GeneFloat(dec!(3.8)),
                Gene::GeneBoolean(true)
            ]))],
            test_state.code
        );

        test_state.code = vec![];
        code_rest(&mut test_state);
        let empty_vec: Vec<Gene> = vec![];
        assert_eq!(empty_vec, test_state.code);
        drop(empty_vec);

        test_state.code = vec![Gene::GeneInt(1)];
        code_rest(&mut test_state);
        assert_eq!(vec![Gene::GeneInt(1)], test_state.code);
    }

    #[test]
    fn but_last_test() {
        let mut test_state = EMPTY_STATE;

        test_state.code = vec![Gene::Block(Box::new(vec![
            Gene::GeneInt(1),
            Gene::GeneFloat(dec!(3.8)),
            Gene::GeneBoolean(true),
        ]))];
        code_but_last(&mut test_state);
        assert_eq!(
            vec![Gene::Block(Box::new(vec![
                Gene::GeneInt(1),
                Gene::GeneFloat(dec!(3.8)),
            ]))],
            test_state.code
        );

        test_state.code = vec![];
        code_but_last(&mut test_state);
        let empty_vec: Vec<Gene> = vec![];
        assert_eq!(empty_vec, test_state.code);
        drop(empty_vec);

        test_state.code = vec![Gene::GeneInt(1)];
        code_but_last(&mut test_state);
        assert_eq!(vec![Gene::GeneInt(1)], test_state.code);
    }

    #[test]
    fn wrap_block_test() {
        let mut test_state = EMPTY_STATE;

        test_state.code = vec![Gene::GeneInt(1)];
        code_wrap_block(&mut test_state);
        assert_eq!(
            vec![Gene::Block(Box::new(vec![Gene::GeneInt(1)]))],
            test_state.code
        );
    }

    #[test]
    fn combine_test() {
        let mut test_state = EMPTY_STATE;

        test_state
            .code
            .push(Gene::Block(Box::new(vec![Gene::GeneInt(1)])));
        test_state.code.push(Gene::Block(Box::new(vec![
            Gene::GeneFloat(dec!(3.8)),
            Gene::GeneBoolean(true),
        ])));
        code_combine(&mut test_state);
        assert_eq!(
            vec![Gene::Block(Box::new(vec![
                Gene::GeneInt(1),
                Gene::GeneFloat(dec!(3.8)),
                Gene::GeneBoolean(true),
            ]))],
            test_state.code
        );
        test_state.code.clear();

        test_state
            .code
            .push(Gene::Block(Box::new(vec![Gene::GeneInt(1)])));
        test_state.code.push(Gene::GeneFloat(dec!(4.0)));
        code_combine(&mut test_state);
        assert_eq!(
            vec![Gene::Block(Box::new(vec![
                Gene::GeneInt(1),
                Gene::GeneFloat(dec!(4.0)),
            ]))],
            test_state.code
        );
        test_state.code.clear();

        test_state.code.push(Gene::GeneFloat(dec!(4.0)));
        test_state
            .code
            .push(Gene::Block(Box::new(vec![Gene::GeneInt(1)])));
        code_combine(&mut test_state);
        assert_eq!(
            vec![Gene::Block(Box::new(vec![
                Gene::GeneInt(1),
                Gene::GeneFloat(dec!(4.0)),
            ]))],
            test_state.code
        );
        test_state.code.clear();

        test_state.code.push(Gene::GeneFloat(dec!(4.0)));
        test_state.code.push(Gene::GeneChar('z'));
        code_combine(&mut test_state);
        assert_eq!(
            vec![Gene::Block(Box::new(vec![
                Gene::GeneChar('z'),
                Gene::GeneFloat(dec!(4.0)),
            ]))],
            test_state.code
        );
    }

    #[test]
    fn _code_do_then_pop_test() {
        let mut test_state = EMPTY_STATE;

        test_state.code.push(Gene::StateFunc(int_add));
        code_do_then_pop(&mut test_state);
        assert_eq!(vec![Gene::StateFunc(int_add)], test_state.code);
        assert_eq!(
            vec![Gene::StateFunc(code_pop), Gene::StateFunc(int_add)],
            test_state.exec
        );
    }
}