{-# LANGUAGE GADTs, TypeOperators #-}
module Data.HMemDb.CreateTable
    (CreateTable(makeTable, fixKeys), IsKeySpec, createTable) where
import Control.Concurrent.STM (STM, newTVar)
import qualified Data.Map as M (empty)
import Data.HMemDb.Bin (Bin, TableRefs)
import Data.HMemDb.ForeignKeys
    (ForeignKey, PreForeignKey(PreForeignKey), makeForeignKey)
import Data.HMemDb.KeyBackends (KeyBack(KeyBack), PreKeyBack(PreKeyBack))
import Data.HMemDb.MapTVar (readTVarMap)
import Data.HMemDb.RefContainer (RefContainer)
import Data.HMemDb.RefConverter (PreRefConv, RefConv(RefConv))
import Data.HMemDb.Specs
    (FullSpec(keySpec, tabSpec), Keys(Keys), KeySpec(KeySpec), makeRC, (:+:)((:+:)))
import Data.HMemDb.Tables
    (PreTable(tabIndices), Table(Table), emptyPreTable)
-- | This is a class of sets of 'Data.HMemDb.KeySpec's and 'Data.HMemDb.ForeignKey's
class CreateTable u where
    makeTable ::
        Bin r
        -> TableRefs r
        -> PreRefConv r a a
        -> u a KeySpec 
        -> STM (PreTable r a, u a (PreForeignKey r))
    fixKeys ::
        Bin r -> PreTable r a -> u a (PreForeignKey r) -> u a ForeignKey
instance CreateTable Keys where
    makeTable _ tr pr ~Keys =
        do pt <- emptyPreTable tr pr
           return (pt, Keys)
    fixKeys _ _ ~Keys = Keys
-- | This class is here for technical reasons; it has just one instance.
class IsKeySpec ks where
    makeTableKS ::
        CreateTable u => Bin r
        -> TableRefs r
        -> PreRefConv r a a
        -> (u :+: ks) a KeySpec
        -> STM (PreTable r a, (u :+: ks) a (PreForeignKey r))
instance (Ord i, RefContainer s) => IsKeySpec (KeySpec s i) where
    makeTableKS b tr pr (uk :+: KeySpec h) =
        do ~(pt, uf) <- makeTable b tr pr uk
           ii <- newTVar M.empty
           tv <- newTVar M.empty
           let pt' = pt {tabIndices = KeyBack (PreKeyBack h ii tv) : tabIndices pt}
           return (pt', uf :+: PreForeignKey (readTVarMap tv))
instance (CreateTable u, IsKeySpec ks) => CreateTable (u :+: ks) where
    makeTable = makeTableKS
    fixKeys b pt (uf :+: pfk) = fixKeys b pt uf :+: makeForeignKey b pt pfk
createTable :: CreateTable u => FullSpec a u -> STM (Table a, u a ForeignKey)
-- ^ This function creates an empty table,
-- given the table structure ('Data.HMemDb.TableSpec')
-- and the set of keys ('Data.HMemDb.KeySpec').
-- It returns the table itself, accompanied with the same set of foreign keys,
-- allowing one to search through the table quickly,
-- or to make a new 'Data.HMemDb.TableSpec'.
createTable fs =
    case makeRC $ tabSpec fs of
      RefConv b tr pr ->
          do (pt, pfks) <- makeTable b tr pr (keySpec fs)
             return (Table b pt, fixKeys b pt pfks)