{-# LANGUAGE ScopedTypeVariables, TypeFamilies, MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators, UndecidableInstances, FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts, RankNTypes, AllowAmbiguousTypes, GADTs #-}
module Database.Selda.Selectors where
import Database.Selda.Table
import Database.Selda.Types
import Database.Selda.Column
import Data.Dynamic
import Data.List (foldl')
import Unsafe.Coerce

-- | Get the value at the given index from the given inductive tuple.
(!)  :: forall s t a. ToDyn (Cols () t) => Cols s t -> Selector t a -> Col s a
tup ! (Selector n) = unsafeCoerce (unsafeToList (toU tup) !! n)
  where toU = unsafeCoerce :: Cols s t -> Cols () t

upd :: forall s t. (ToDyn (Cols () t))
     => Cols s t -> Assignment s t -> Cols s t
upd tup (Selector n := x) =
    fromU . unsafeFromList $ replace (unsafeToList $ toU tup) (unsafeCoerce x)
  where
    toU = unsafeCoerce :: Cols s t -> Cols () t
    fromU = unsafeCoerce :: Cols () t -> Cols s t
    replace xs x' =
      case splitAt n xs of
        (left, _:right) -> left ++ x' : right
        _               -> error "impossible"

-- | A selector-value assignment pair.
data Assignment s t where
  (:=) :: Selector t a -> Col s a -> Assignment s t
infixl 2 :=

-- | For each selector-value pair in the given list, on the given tuple,
--   update the field pointed out by the selector with the corresponding value.
with :: forall s t. (ToDyn (Cols () t))
     => Cols s t -> [Assignment s t] -> Cols s t
with = foldl' upd

-- | A column selector. Column selectors can be used together with the '!' and
--   'with' functions to get and set values on inductive tuples, or to indicate
--   foreign keys.
data Selector t a = Selector Int

-- | The inductive tuple of selectors for a table of type @a@.
type family Selectors t a where
  Selectors t (a :*: b) = (Selector t a :*: Selectors t b)
  Selectors t a         = Selector t a

-- | Generate selector functions for the given table.
--   Selectors can be used to access the fields of a query result tuple, avoiding
--   the need to pattern match on the entire tuple.
--
-- > tbl :: Table (Int :*: Text)
-- > tbl = table "foo" $ required "bar" :*: required "baz"
-- > (tblBar :*: tblBaz) = selectors tbl
-- >
-- > q :: Query s Text
-- > q = tblBaz <$> select tbl
selectors :: forall a. HasSelectors a a => Table a -> Selectors a a
selectors _ = mkSel (Proxy :: Proxy a) 0 (Proxy :: Proxy a)

-- | Any table type that can have selectors generated.
class HasSelectors t a where
  mkSel :: Proxy t -> Int -> Proxy a -> Selectors t a

instance (Typeable a, HasSelectors t b) => HasSelectors t (a :*: b) where
  mkSel p n _ = (Selector n :*: mkSel p (n+1) (Proxy :: Proxy b))

instance {-# OVERLAPPABLE #-} (Selectors t a ~ Selector t a) =>
         HasSelectors t a where
  mkSel _ n _ = Selector n

-- | A pair of the table with the given name and columns, and all its selectors.
--   For example:
--
-- > tbl :: Table (Int :*: Text)
-- > (tbl, tblBar :*: tblBaz)
-- >   =  tableWithSelectors "foo"
-- >   $  required "bar"
-- >   :*: required "baz"
-- >
-- > q :: Query s Text
-- > q = tblBaz <$> select tbl
tableWithSelectors :: forall a. (TableSpec a, HasSelectors a a)
                   => TableName
                   -> ColSpecs a
                   -> (Table a, Selectors a a)
tableWithSelectors name cs = (t, s)
  where
    t = table name cs
    s = selectors t