{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds  #-}
{-# LANGUAGE FlexibleInstances  #-}
{-# LANGUAGE GADTs      #-}
{-# LANGUAGE PolyKinds  #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
-- | Commonly used 'Rec' instantiations.
module Data.Vinyl.Derived where

import Data.Proxy
import Data.Vinyl.ARec
import Data.Vinyl.Core
import Data.Vinyl.Functor
import Data.Vinyl.Lens
import Data.Vinyl.TypeLevel (Fst, Snd, RIndex)
import GHC.OverloadedLabels
import GHC.TypeLits

-- | Alias for Field spec
type a ::: b = '(a, b)

-- | A record of named fields.
type FieldRec = Rec ElField

-- | An 'ARec' of named fields to provide constant-time field access.
type AFieldRec ts = ARec ElField ts

-- | Heterogeneous list whose elements are evaluated during list
-- construction.
type HList = Rec Identity

-- | Heterogeneous list whose elements are left as-is during list
-- construction (cf. 'HList').
type LazyHList = Rec Thunk

-- | Get the data payload of an 'ElField'.
getField :: ElField '(s,t) -> t
getField (Field x) = x

-- | Get the label name of an 'ElField'.
getLabel :: forall s t. ElField '(s,t) -> String
getLabel (Field _) = symbolVal (Proxy::Proxy s)

-- | 'ElField' is isomorphic to a functor something like @Compose
-- ElField ('(,) s)@.
fieldMap :: (a -> b) -> ElField '(s,a) -> ElField '(s,b)
fieldMap f (Field x) = Field (f x)
{-# INLINE fieldMap #-}

-- | Something in the spirit of 'traverse' for 'ElField' whose kind
-- fights the standard library.
traverseField :: (KnownSymbol s, Functor f)
              => (a -> b) -> f (ElField '(s,a)) -> ElField '(s, f b)
traverseField f t = Field (fmap (f . getField)  t)

-- | Lens for an 'ElField''s data payload.
rfield :: Functor f => (a -> f b) -> ElField '(s,a) -> f (ElField '(s,b))
rfield f (Field x) = fmap Field (f x)
{-# INLINE rfield #-}

infix 8 =:

-- | Operator for creating an 'ElField'. With the @-XOverloadedLabels@
-- extension, this permits usage such as, @#foo =: 23@ to produce a
-- value of type @ElField ("foo" ::: Int)@.
(=:) :: KnownSymbol l => Label (l :: Symbol) -> (v :: *) -> ElField (l ::: v)
_ =: v = Field v

-- | Get a named field from a record.
rgetf
  :: forall l f v record us.
     (HasField record l us us v v, RecElemFCtx record f)
  => Label l -> record f us -> f (l ::: v)
rgetf _ = rget @(l ::: v)

-- | Get the value associated with a named field from a record.
rvalf
  :: (HasField record l us us v v, RecElemFCtx record ElField)
  => Label l -> record ElField us -> v
rvalf x = getField . rgetf x

-- | Set a named field. @rputf' #foo 23@ sets the field named @#foo@ to
-- @23@.
rputf' :: forall l v v' record us us'.
          (HasField record l us us' v v', KnownSymbol l, RecElemFCtx record ElField)
       => Label l -> v' -> record ElField us -> record ElField us'
rputf' _ = rput' @(l:::v) . (Field :: v' -> ElField '(l,v'))

-- | Set a named field without changing its type. @rputf #foo 23@ sets
-- the field named @#foo@ to @23@.
rputf :: forall l v record us.
          (HasField record l us us v v, KnownSymbol l, RecElemFCtx record ElField)
       => Label l -> v -> record ElField us -> record ElField us
rputf _ = rput @(l:::v) . Field

-- | A lens into a 'Rec' identified by a 'Label'.
rlensfL' :: forall l v v' record g f us us'.
             (Functor g, HasField record l us us' v v', RecElemFCtx record f)
          => Label l
          -> (f (l ::: v) -> g (f (l ::: v')))
          -> record f us
          -> g (record f us')
rlensfL' _ f = rlens' @(l ::: v) f

-- | A type-preserving lens into a 'Rec' identified by a 'Label'.
rlensfL :: forall l v record g f us.
           (Functor g, HasField record l us us v v, RecElemFCtx record f)
        => Label l
        -> (f (l ::: v) -> g (f (l ::: v)))
        -> record f us
        -> g (record f us)
rlensfL _ f = rlens' @(l ::: v) f

-- | A lens into the payload value of a 'Rec' field identified by a
-- 'Label'.
rlensf' :: forall l v v' record g us us'.
           (Functor g, HasField record l us us' v v', RecElemFCtx record ElField)
        => Label l -> (v -> g v') -> record ElField us -> g (record ElField us')
rlensf' _ f = rlens' @(l ::: v) (rfield f)

-- | A type-preserving lens into the payload value of a 'Rec' field
-- identified by a 'Label'.
rlensf :: forall l v record g us.
          (Functor g, HasField record l us us v v, RecElemFCtx record ElField)
        => Label l -> (v -> g v) -> record ElField us -> g (record ElField us)
rlensf _ f = rlens @(l ::: v) (rfield f)

-- | Shorthand for a 'FieldRec' with a single field.
(=:=) :: KnownSymbol s => Label (s :: Symbol) -> a -> FieldRec '[ '(s,a) ]
(=:=) _ x = Field x :& RNil

-- | A proxy for field types.
data SField (field :: k) = SField

instance Eq (SField a) where _ == _ = True
instance Ord (SField a) where compare _ _ = EQ
instance KnownSymbol s => Show (SField '(s,t)) where
  show _ = "SField "++symbolVal (Proxy::Proxy s)

type family FieldType l fs where
  FieldType l '[] = TypeError ('Text "Cannot find label "
                               ':<>: 'ShowType l
                               ':<>: 'Text " in fields")
  FieldType l ((l ::: v) ': fs) = v
  FieldType l ((l' ::: v') ': fs) = FieldType l fs

-- | Constraint that a label is associated with a particular type in a
-- record.
type HasField record l fs fs' v v' =
  (RecElem record (l ::: v) (l ::: v') fs fs' (RIndex (l ::: v) fs), FieldType l fs ~ v, FieldType l fs' ~ v')

-- | Proxy for label type
data Label (a :: Symbol) = Label
  deriving (Eq, Show)

instance s ~ s' => IsLabel s (Label s') where
#if __GLASGOW_HASKELL__ < 802
  fromLabel _ = Label
#else
  fromLabel = Label
#endif

-- | Defines a constraint that lets us extract the label from an
-- 'ElField'. Used in 'rmapf' and 'rpuref'.
class (KnownSymbol (Fst a), a ~ '(Fst a, Snd a)) => KnownField a where
instance KnownSymbol l => KnownField (l ::: v) where

-- | Shorthand for working with records of fields as in 'rmapf' and
-- 'rpuref'.
type AllFields fs = (RPureConstrained KnownField fs, RecApplicative fs, RApply fs)

-- | Map a function between functors across a 'Rec' taking advantage
-- of knowledge that each element is an 'ElField'.
rmapf :: AllFields fs
      => (forall a. KnownField a => f a -> g a)
      -> Rec f fs -> Rec g fs
rmapf f = (rpureConstrained @KnownField (Lift f) <<*>>)

-- | Remove the first component (e.g. the label) from a type-level
-- list of pairs.
type family Unlabeled ts where
  Unlabeled '[] = '[]
  Unlabeled ('(s,x) ': xs) = x ': Unlabeled xs

-- | Facilities for removing and replacing the type-level label, or
-- column name, part of a record.
class StripFieldNames ts where
  stripNames :: Rec ElField ts -> Rec Identity (Unlabeled ts)
  stripNames' :: Functor f => Rec (f :. ElField) ts -> Rec f (Unlabeled ts)
  withNames :: Rec Identity (Unlabeled ts) -> Rec ElField ts
  withNames' :: Functor f => Rec f (Unlabeled ts) -> Rec (f :. ElField) ts

instance StripFieldNames '[] where
  stripNames RNil = RNil
  stripNames' RNil = RNil
  withNames RNil = RNil
  withNames' RNil = RNil

instance (KnownSymbol s, StripFieldNames ts) => StripFieldNames ('(s,t) ': ts) where
  stripNames (Field x :& xs) = pure x :& stripNames xs
  stripNames' (Compose x :& xs) = fmap getField x :& stripNames' xs
  withNames (Identity x :& xs) = Field x :& withNames xs
  withNames' (x :& xs) = Compose (fmap Field x) :& withNames' xs

-- | Construct a 'Rec' with 'ElField' elements.
rpuref :: AllFields fs => (forall a. KnownField a => f a) -> Rec f fs
rpuref f = rpureConstrained @KnownField f

-- | Operator synonym for 'rmapf'.
(<<$$>>)
  :: AllFields fs
  => (forall a. KnownField a => f a -> g a) -> Rec f fs -> Rec g fs
(<<$$>>) = rmapf

-- | Produce a 'Rec' of the labels of a 'Rec' of 'ElField's.
rlabels :: AllFields fs => Rec (Const String) fs
rlabels = rpuref getLabel'
  where getLabel' :: forall l v. KnownSymbol l
                  => Const String (l ::: v)
        getLabel' = Const (symbolVal (Proxy::Proxy l))

-- * Specializations for working with an 'ARec' of named fields.