{-# LANGUAGE AllowAmbiguousTypes,
             ConstraintKinds,
             CPP,
             DataKinds,
             FlexibleContexts,
             FlexibleInstances,
             GADTs,
             KindSignatures,
             MultiParamTypeClasses,
             PatternSynonyms,
             PolyKinds,
             RankNTypes,
             ScopedTypeVariables,
             TypeApplications,
             TypeFamilies,
             TypeOperators,
             ViewPatterns #-}
module Frames.RecF (V.rappend, V.rtraverse, rdel, CanDelete,
                    -- frameCons, frameConsA, frameSnoc,
                    -- pattern (:&), pattern Nil,
                    AllCols,
                    UnColumn, -- AsVinyl(..)
                    StripFieldNames(..),
                    mapMono,
                    -- mapMethod,
                    -- runcurry, runcurry', runcurryA, runcurryA',
                    -- ShowRec,
                    -- showRec,
                    ColFun, ColumnHeaders,
                    columnHeaders) where
-- import Data.List (intercalate)
import Data.Proxy
import qualified Data.Vinyl as V
import Data.Vinyl (Rec(..))
import Data.Vinyl.Derived (StripFieldNames(..), Unlabeled)
import Data.Vinyl.Functor ((:.))
import Data.Vinyl.TypeLevel
import Frames.Col
import Frames.TypeLevel
import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)

-- -- | Add a column to the head of a row.
-- frameCons :: (Functor f, KnownSymbol s)
--           => f a -> Rec f rs -> Rec f (s :-> a ': rs)
-- frameCons = (V.:&) . fmap Col
-- {-# INLINE frameCons #-}

-- -- | Add a pure column to the head of a row.
-- frameConsA :: (Applicative f, KnownSymbol s)
--            => a -> Rec f rs -> Rec f (s :-> a ': rs)
-- frameConsA = (V.:&) . fmap Col . pure
-- {-# INLINE frameConsA #-}

-- -- | Separate the first element of a row from the rest of the row.
-- frameUncons :: Functor f => Rec f (s :-> r ': rs) -> (f r, Rec f rs)
-- frameUncons (x V.:& xs) = (fmap getCol x, xs)
-- {-# INLINE frameUncons #-}

-- -- | Add a column to the tail of a row. Note that the supplied value
-- -- should be a 'Col' to work with the @Frames@ tooling.
-- frameSnoc :: Rec f rs -> f r -> Rec f (rs ++ '[r])
-- frameSnoc r x = V.rappend r (x V.:& RNil)
-- {-# INLINE frameSnoc #-}

-- pattern Nil :: Rec f '[]
-- pattern Nil <- RNil where
--   Nil = RNil

-- pattern (:&) :: (Functor f, KnownSymbol s) => f r -> Rec f rs -> Rec f (s :-> r ': rs)
-- pattern x :& xs <- (frameUncons -> (x, xs)) where
--   x :& xs = frameCons x xs

class ColumnHeaders (cs::[(Symbol,*)]) where
  -- | Return the column names for a record.
  columnHeaders :: proxy (Rec f cs) -> [String]

instance ColumnHeaders '[] where
  columnHeaders _ = []

instance forall cs s c. (ColumnHeaders cs, KnownSymbol s)
    => ColumnHeaders (s :-> c ': cs) where
  columnHeaders _ = symbolVal (Proxy::Proxy s) : columnHeaders (Proxy::Proxy (Rec f cs))

-- | A type function to convert a 'Rec f' to a 'Rec (g :. f)'. @ColFun
-- f (Record rs) = Rec (f :. ElField) rs@.
type family ColFun f x where
  ColFun f (Rec g rs) = Rec (f :. g) rs

-- | Strip the column information from each element of a list of
-- types.
type family UnColumn (ts :: [(Symbol,*)]) where
  UnColumn '[] = '[]
  UnColumn ((s :-> t) ': ts) = t ': UnColumn ts

-- | Enforce a constraint on the payload type of each column.
type AllCols c ts = AllConstrained c (UnColumn ts)

-- | Map a function across a 'Rec' with monomorphic (i.e. all the
-- same) indices.
mapMonoV :: (AllAre a ts, Functor f)
         => (a -> b)
         -> Rec f ts
         -> Rec f (ReplaceAll b ts)
mapMonoV _ RNil = RNil
mapMonoV f (x :& xs) = fmap f x :& mapMonoV f xs

-- | Map a function across a homogeneous 'Rec' of named values. This
-- is a thin wrapper over 'mapMonoV'.
mapMono :: (AllAre a (Unlabeled ts),
            StripFieldNames ts,
            StripFieldNames (ReplaceAllSnd b ts),
            ReplaceAll b (Unlabeled ts) ~ Unlabeled (ReplaceAllSnd b ts))
        => (a -> b)
        -> Rec V.ElField ts
        -> Rec V.ElField (ReplaceAllSnd b ts)
mapMono f = withNames . mapMonoV f . stripNames

-- | A constraint that a field can be deleted from a record.
type CanDelete r rs = (V.RElem r rs (RIndex r rs), RDelete r rs V.⊆ rs)

-- | Delete a field from a record
rdel :: CanDelete r rs => Rec f rs -> Rec f (RDelete r rs)
rdel = V.rcast