mem-query 0.0.1

Relational algebra interface for Rust collections
//! Definitions and tools for working with table headers

use crate::prelude::*;
use crate::record::{ ExternalRecord, Record };

use tylisp::{
    ops::{Is, list::{Find, Missing, Here, There, SubsetP}}, 
             marker_traits::{Pass,Fail,List}};

#[cfg(feature="const")]
use tylisp::{
    ops::set::{Set,},
};

use core::any::{Any, TypeId};

/// Marker trait for `HList`s that can be record/relation headers
///
/// In addition to specifying the column data in the abstract, it is
/// also used as the baseline `Record` implementation.
///
/// It must be a proper list of `Col` types that contains no duplicates.
/// Because it is dependent on `tylisp::List`, a sealed trait, it cannot
/// be implemented by downstream developers.
///
/// It was marked `unsafe` to implement because `col_opt()` must agree
/// with `HasCol`.  (Note: replaced by panicking unreachable call)

pub trait Header: Sized
                + Clone
                + List
                + 'static
                + for<'a> AsListRefs<'a> {
    /// Get a column reference if it exists
    fn col_opt<C:Col>(&self)->Option<&C>;

    /// Check for presence of a particular column
    fn has_col<C:Col>() -> bool;

    /// Are there any columns in common?
    fn is_disjoint<H:Header>() -> bool;

    #[inline(always)]
    /// Get a column reference; the type bound statically ensures it exists
    fn col_ref<C:Col>(&self)->&C
    where Self: HasCol<C> {
        match self.col_opt() {
            Some(c) => c,
            None => unreachable!()
        }
    }

    fn clone_from_rec<R>(rec:&R)->Self where 
        sexpr!{SubsetP, @Self, @R::Cols}: Pass,
        R: Record
    {
        Self::clone_from_rec_unchecked(rec)
    }
    fn clone_from_rec_unchecked(_:&impl Record)->Self;
}

impl Header for HNil {
    #[inline(always)]
    fn col_opt<C:Col>(&self)->Option<&C> { None }

    #[inline(always)]
    fn has_col<C:Col>()->bool { false }

    #[inline(always)]
    fn is_disjoint<H:Header>()->bool { true }
    fn clone_from_rec_unchecked(_:&impl Record)->Self { Self }
}    

#[cfg(not(feature = "const"))]
impl<H:Col, T:Header> Header for HCons<H,T>
where sexpr!{Is, Missing, {Find, @H, @T}}: Pass,
      Self: List
{
    #[inline(always)]
    fn col_opt<C:Col>(&self) -> Option<&C> {
        if TypeId::of::<C>() == TypeId::of::<H>() {
            match (&self.head as &dyn Any).downcast_ref() {
                Some(x) => Some(x),
                None => unreachable!()
            }
        } else {
            self.tail.col_opt()
        }
    }

    #[inline(always)]
    fn has_col<C:Col>()->bool {
        (TypeId::of::<C>() == TypeId::of::<H>()) || T::has_col::<C>()
    }

    #[inline(always)]
    fn is_disjoint<Other:Header>() -> bool {
        T::is_disjoint::<Other>() && !Other::has_col::<H>()
    }

    fn clone_from_rec_unchecked(r:&impl Record)->Self {
         HCons { head: r.col_opt::<H>().unwrap().clone(),
                 tail: T::clone_from_rec_unchecked(r)
        }
    }
}

#[cfg(feature = "const")]
impl<H:Col, T:Header> Header for HCons<H,T>
where Self: List + Set {
    #[inline(always)]
    fn col_opt<C:Col>(&self) -> Option<&C> {
        if TypeId::of::<C>() == TypeId::of::<H>() {
            match (&self.head as &dyn Any).downcast_ref() {
                Some(x) => Some(x),
                None => unreachable!()
            }
        } else {
            self.tail.col_opt()
        }
    }

    fn has_col<C:Col>()->bool {
        Self::contains::<C>()
    }
}

/// Finds the location of a column within a Header, and returns
/// its index.
pub trait HasCol<C:Col> {
    type Index;
}

impl<C:Col, H:Header> HasCol<C> for H
where sexpr!{Is, Missing, {Find, @C, @H}}: Fail,
sexpr!{Find, @C, @H}: tylisp::engine::Eval,
Self: Take<eval!{Find,@C,@H}, Taken=C>
{
    type Index = eval!{Find, @C, @H};
}

/// Extracts the item at a given `Index` from an HList
pub trait Take<Index> {
    /// The type of the item at that index
    type Taken;

    /// A list of all the other elements
    type Remainder;
    fn take(self)->(Self::Taken, Self::Remainder);
}

impl<H,T> Take<Here> for HCons<H,T> {
    type Taken = H;
    type Remainder = T;
    #[inline(always)]
    fn take(self)->(H,T) { (self.head, self.tail) }
}

impl<H,T,More> Take<There<More>> for HCons<H,T>
where T:Take<More> {
    type Taken = T::Taken;
    type Remainder = HCons<H,T::Remainder>;
    #[inline(always)]
    fn take(self)->(Self::Taken, Self::Remainder) {
        let (x, tail) = self.tail.take();
        (x, sexpr_val!{self.head; tail})
    }
}

pub trait ProjectAnyRefFrom<Other>: for<'a> ProjectRefFrom<'a, Other> {}
impl<T,Other:Header> ProjectAnyRefFrom<Other> for T
where T: for<'a> ProjectRefFrom<'a, Other> {}

/// A header that can be created by extracting values from `Other`
pub trait ProjectFrom<Other>: Header + ProjectAnyRefFrom<Other> {
    /// The remaining columns in `Other` after extracting the projection
    type Remainder: Header;

    /// Construct `Self` by extracting values from `other`.
    fn project_from(other:Other)->(Self, Self::Remainder);
}

/// The reference version of `ProjectFrom`
///
/// Instead of consuming `other`, it produces a list of column references
pub trait ProjectRefFrom<'a, Other>: Header {
    type AsRef: Record<Cols=Self>+'a;
    fn ref_from(rec: impl ExternalRecord<'a, Cols=Other>)->Self::AsRef;
}

impl<Other: Header> ProjectFrom<Other> for HNil {
    type Remainder = Other;
    #[inline(always)]
    fn project_from(other:Other)->(HNil, Other) { (HNil, other) }
}

impl<'a, Other: Header> ProjectRefFrom<'a, Other> for HNil {
    type AsRef = HNil;
    #[inline(always)]
    fn ref_from(_: impl ExternalRecord<'a, Cols=Other>)->Self::AsRef { HNil }
}

impl<'a, Other: Header+HasCol<H>, H:Col, T:ProjectRefFrom<'a, Other>>
ProjectRefFrom<'a, Other> for HCons<H,T> where Self: Header {
    type AsRef = sexpr!{&'a H; T::AsRef};
    #[inline(always)]
    fn ref_from(rec: impl ExternalRecord<'a, Cols=Other>)->Self::AsRef {
        sexpr_val!{ rec.ext_col_ref(); T::ref_from(rec) }
    } 
}

impl<Other: Header, T:ProjectFrom<Other>, H:Col>
ProjectFrom<Other> for HCons<H,T>
where
    T::Remainder: HasCol<H>
                + Take<<T::Remainder as HasCol<H>>::Index, Taken=H>,
    Self: Header + for<'a> ProjectRefFrom<'a, Other>,
    <T::Remainder as Take<< T::Remainder as HasCol<H> > ::Index >>::Remainder: Header,
{
    type Remainder = <T::Remainder as Take<< T::Remainder as HasCol<H> > ::Index >>::Remainder;
    #[inline(always)]
    fn project_from(other:Other)->(Self, Self::Remainder) {
        let (tail, x) = T::project_from(other);
        let (head, remainder) = x.take();
        (HCons { head, tail }, remainder)
    }
}

pub trait AsListRefsStatic: for<'a> AsListRefs<'a> {}
impl<T> AsListRefsStatic for T where T:for<'a> AsListRefs<'a> {}

pub trait AsListRefs<'a> {
    type AsRefs: Copy + Clone;
    fn ref_from_ext_rec<R>(r:R)-><Self as AsListRefs<'a>>::AsRefs where
        Self: Sized,
        sexpr!{SubsetP, @Self, @R::Cols}: Pass,
        R: ExternalRecord<'a>
    { <Self as AsListRefs<'a>>::ref_from_ext_rec_unchecked(r) }
    fn ref_from_ext_rec_unchecked(_:impl ExternalRecord<'a>)->Self::AsRefs;
}

impl<'a> AsListRefs<'a> for HNil {
    type AsRefs = HNil;
    fn ref_from_ext_rec_unchecked(_:impl ExternalRecord<'a>)->Self::AsRefs { HNil }
}

impl<'a, H:Col, T:AsListRefs<'a>> AsListRefs<'a> for HCons<H,T> {
    type AsRefs = HCons<&'a H, T::AsRefs>;
    fn ref_from_ext_rec_unchecked(r:impl ExternalRecord<'a>)->Self::AsRefs {
        HCons {
            head: r.ext_col_opt::<H>().unwrap(),
            tail: T::ref_from_ext_rec_unchecked(r)
        }
    }
}

#[test]
fn test_headers() {
    col!{ A: usize }
    col!{ B: usize }
    col!{ C: usize }

    assert_trait!{ HNil: Header };

    assert_trait!{ sexpr!{A, B, C}: Header };
    assert_trait!{ sexpr!{A, B, C}: HasCol<A> };
    assert_trait!{ sexpr!{A, B, C}: HasCol<B> };

    assert_trait!{ sexpr!{B, C, A}: Header };
    assert_trait!{ sexpr!{A, C}: Header };

    assert_trait!{ sexpr!{A, C}: ProjectFrom<sexpr!{B, C, A}>};

    let (_ac, _b):(_,sexpr!{B}) = <sexpr!{A,C}>::project_from(sexpr_val!{A(2), B(3), C(4)});
}

#[test]
fn test_as_list_refs() {
    col!{ A: usize }
    col!{ B: usize }
    col!{ C: usize }

    let rec = sexpr_val!{A(1), B(2), C(3)};
    let _: sexpr!{&A, &B, &C} = <sexpr!{A,B,C}>::ref_from_ext_rec(&rec);
}