module Composite.Opaleye.RecordTable where

import BasicPrelude
import Composite.Base (NamedField(fieldName))
import Control.Lens (Wrapped(type Unwrapped, _Wrapped'), from, view)
import Data.Profunctor (dimap)
import Data.Profunctor.Product ((***!))
import qualified Data.Profunctor.Product as PP
import Data.Proxy (Proxy(Proxy))
import Data.Text (unpack)
import Data.Vinyl.Core (Rec((:&), RNil))
import Data.Vinyl.Functor (Identity(Identity))
import Opaleye (Column, TableProperties, required, optional)

-- |Helper typeclass which picks which of 'required' or 'optional' to use for a pair of write column type and read column type.
--
-- @DefaultRecTableField (Maybe (Column a)) (Column a)@ uses 'optional'.
-- @DefaultRecTableField        (Column a)  (Column a)@ uses 'required'.
class DefaultRecTableField write read where
  defaultRecTableField :: String -> TableProperties write read

instance DefaultRecTableField (Maybe (Column a)) (Column a) where
  defaultRecTableField = optional

instance DefaultRecTableField (Column a) (Column a) where
  defaultRecTableField = required

-- |Type class for producing a default 'TableProperties' schema for some expected record types. 'required' and 'optional' are chosen automatically and the
-- column is named after the record fields, using 'NamedField' to reflect the field names.
--
-- For example, given:
--
-- >  type WriteRec = Record '["id" :-> Maybe (Column PGInt8), "name" :-> Column PGText]
-- >  type ReadRec  = Record '["id" :->        Column PGInt8 , "name" :-> Column PGText]
--
-- This:
--
-- >  defaultRecTable :: TableProperties WriteRec ReadRec
--
-- Is equivalent to:
--
-- > pReq (optional "id" &: required "name" &: Nil)
--
--
-- Alternately, use 'Composite.Opaleye.ProductProfunctors.pReq' and the usual Opaleye 'required' and 'optional'.
class DefaultRecTable write read where
  defaultRecTable :: TableProperties (Rec Identity write) (Rec Identity read)

instance DefaultRecTable '[] '[] where
  defaultRecTable = dimap (const ()) (const RNil) PP.empty

instance
    forall r reads w writes.
    ( NamedField w, NamedField r
    , DefaultRecTableField (Unwrapped w) (Unwrapped r)
    , DefaultRecTable writes reads
    ) => DefaultRecTable (w ': writes) (r ': reads) where
  defaultRecTable =
    dimap (\ (Identity (view _Wrapped' -> w) :& writeRs) -> (w, writeRs))
          (\ (r, readRs) -> (Identity (view (from _Wrapped') r) :& readRs))
          (step  ***! recur)
    where
      step :: TableProperties (Unwrapped w) (Unwrapped r)
      step = defaultRecTableField . unpack $ fieldName (Proxy :: Proxy r)
      recur :: TableProperties (Rec Identity writes) (Rec Identity reads)
      recur = defaultRecTable