Documentation
mod arbitrary;
mod build;

pub use self::arbitrary::{arb_auto_ty, arb_polar_ty};
pub use self::build::Constructed;

use std::cmp::Ordering;
use std::rc::Rc;
use std::vec;

use im::OrdMap;
use itertools::EitherOrBoth;

use crate::auto::StateSet;
use crate::Polarity;

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Constructor {
    Bool,
    Fun(StateSet, StateSet),
    Record(OrdMap<Rc<str>, StateSet>),
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Component {
    Bool,
    Fun,
    Record,
}

impl crate::Constructor for Constructor {
    type Label = Label;
    type Component = Component;

    fn component(&self) -> Self::Component {
        match self {
            Constructor::Bool => Component::Bool,
            Constructor::Fun(..) => Component::Fun,
            Constructor::Record(..) => Component::Record,
        }
    }

    fn join(&mut self, other: &Self, pol: Polarity) {
        match (self, other) {
            (Constructor::Bool, Constructor::Bool) => (),
            (Constructor::Fun(ld, lr), Constructor::Fun(rd, rr)) => {
                ld.union(rd);
                lr.union(rr);
            }
            (Constructor::Record(ref mut lhs), Constructor::Record(ref rhs)) => match pol {
                Polarity::Pos => {
                    *lhs = lhs.clone().intersection_with(rhs.clone(), |mut l, r| {
                        l.union(&r);
                        l
                    })
                }
                Polarity::Neg => {
                    *lhs = lhs.clone().union_with(rhs.clone(), |mut l, r| {
                        l.union(&r);
                        l
                    })
                }
            },
            _ => unreachable!(),
        }
    }

    fn visit_params_intersection<F, E>(&self, other: &Self, mut visit: F) -> Result<(), E>
    where
        F: FnMut(Self::Label, &StateSet, &StateSet) -> Result<(), E>,
    {
        itertools::merge_join_by(self.params(), other.params(), |l, r| Ord::cmp(&l.0, &r.0))
            .try_for_each(|eob| match eob {
                EitherOrBoth::Both(l, r) => visit(l.0, &l.1, &r.1),
                _ => Ok(()),
            })
    }

    fn map<F>(self, mut mapper: F) -> Self
    where
        F: FnMut(Self::Label, StateSet) -> StateSet,
    {
        match self {
            Constructor::Bool => Constructor::Bool,
            Constructor::Fun(d, r) => {
                Constructor::Fun(mapper(Label::Domain, d), mapper(Label::Range, r))
            }
            Constructor::Record(fields) => Constructor::Record(
                fields
                    .into_iter()
                    .map(|(label, set)| (label.clone(), mapper(Label::Label(label), set)))
                    .collect(),
            ),
        }
    }
}

impl Constructor {
    fn params(&self) -> Vec<(Label, StateSet)> {
        match self {
            Constructor::Bool => vec![],
            Constructor::Fun(d, r) => vec![(Label::Domain, d.clone()), (Label::Range, r.clone())],
            Constructor::Record(fields) => fields
                .clone()
                .into_iter()
                .map(|(label, set)| (Label::Label(label), set))
                .collect(),
        }
    }
}

impl PartialOrd for Constructor {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (self, other) {
            (Constructor::Bool, Constructor::Bool) => Some(Ordering::Equal),
            (Constructor::Fun(..), Constructor::Fun(..)) => Some(Ordering::Equal),
            (Constructor::Record(ref lhs), Constructor::Record(ref rhs)) => {
                iter_set::cmp(lhs.keys(), rhs.keys()).map(Ordering::reverse)
            }
            _ => None,
        }
    }
}

#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub enum Label {
    Domain,
    Range,
    Label(Rc<str>),
}

impl crate::Label for Label {
    fn polarity(&self) -> Polarity {
        match self {
            Label::Domain => Polarity::Neg,
            Label::Range | Label::Label(_) => Polarity::Pos,
        }
    }
}