Documentation
use std::collections::VecDeque;
use std::rc::Rc;

use lazy_static::lazy_static;
use proptest::collection::btree_map;
use proptest::prelude::*;
use proptest::prop_oneof;
use proptest::proptest;
use proptest::strategy::{LazyJust, NewTree, ValueTree};
use proptest::string::string_regex;
use proptest::test_runner::TestRunner;
use rand_distr::Exp1;

use super::{Constructed, Constructor};
use crate::auto::{Automaton, StateId};
use crate::polar::Ty;
use crate::Polarity;

pub fn arb_auto_ty(pol: Polarity) -> BoxedStrategy<(Automaton<Constructor>, StateId)> {
    arb_polar_ty(pol)
        .prop_map(move |ty| {
            let mut auto = Automaton::new();
            let mut builder = auto.builder();
            let id = builder.build_polar(pol, &ty);
            drop(builder);
            (auto, id)
        })
        .boxed()
}

pub fn arb_polar_ty(pol: Polarity) -> BoxedStrategy<Ty<Constructed, char>> {
    prop_oneof![
        LazyJust::new(|| Ty::Zero),
        prop::char::range('a', 'e').prop_map(Ty::UnboundVar),
        BoundVar.prop_map(Ty::BoundVar),
    ]
    .prop_recursive(32, 1000, 8, |inner| {
        prop_oneof![
            3 => arb_polar_cons(inner.clone()).prop_map(Ty::Constructed),
            1 => (inner.clone(), inner.clone()).prop_map(|(l, r)| Ty::Add(Box::new(l), Box::new(r))),
            1 => inner.prop_map(Box::new).prop_map(Ty::Recursive),
        ]
    })
    .prop_filter("invalid polar type", move |ty| check(pol, ty, &mut VecDeque::new(), 0))
    .boxed()
}

fn arb_polar_cons(ty: BoxedStrategy<Ty<Constructed, char>>) -> BoxedStrategy<Constructed> {
    lazy_static! {
        static ref IDENT: SBoxedStrategy<Rc<str>> =
            string_regex("[a-z]").unwrap().prop_map(Into::into).sboxed();
    }

    prop_oneof![
        LazyJust::new(|| Constructed::Bool),
        (ty.clone(), ty.clone()).prop_map(|(d, r)| Constructed::Fun(Box::new(d), Box::new(r))),
        btree_map(IDENT.clone(), ty.prop_map(Box::new), 0..8).prop_map(Constructed::Record)
    ]
    .boxed()
}

fn check(
    pol: Polarity,
    ty: &Ty<Constructed, char>,
    recs: &mut VecDeque<Polarity>,
    unguarded: usize,
) -> bool {
    match ty {
        Ty::BoundVar(idx) => {
            if *idx < unguarded || *idx >= recs.len() {
                false
            } else {
                recs[*idx] == pol
            }
        }
        Ty::Constructed(Constructed::Fun(d, r)) => {
            check(-pol, d, recs, 0) && check(pol, r, recs, 0)
        }
        Ty::Constructed(Constructed::Record(fields)) => {
            fields.iter().all(|(_, t)| check(pol, t, recs, 0))
        }
        Ty::Add(l, r) => check(pol, l, recs, unguarded) && check(pol, r, recs, unguarded),
        Ty::Recursive(t) => {
            recs.push_front(pol);
            let b = check(pol, t, recs, unguarded + 1);
            recs.pop_front();
            b
        }
        _ => true,
    }
}

#[derive(Debug)]
struct BoundVar;
struct BoundVarTree(usize);

impl Strategy for BoundVar {
    type Tree = BoundVarTree;
    type Value = usize;

    fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
        let val: f64 = runner.rng().sample(Exp1);
        Ok(BoundVarTree(val as usize))
    }
}

impl ValueTree for BoundVarTree {
    type Value = usize;

    fn current(&self) -> Self::Value {
        self.0
    }

    fn simplify(&mut self) -> bool {
        false
    }

    fn complicate(&mut self) -> bool {
        false
    }
}

proptest! {
    #[test]
    fn polar_pos(_ in arb_polar_ty(Polarity::Pos)) {}

    #[test]
    fn polar_neg(_ in arb_polar_ty(Polarity::Neg)) {}

    #[test]
    fn auto_pos(_ in arb_auto_ty(Polarity::Pos)) {}

    #[test]
    fn auto_neg(_ in arb_auto_ty(Polarity::Neg)) {}
}