{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}

module Inf.DB.Monad
  ( DB
  , runDB
  , DBException(..)
  , SQL.Only(..)
  , SQL.Connection
  , io
  , raise
  , f

  , query
  , queryMb
  , queryOne
  , execute
  , lastRow

  , SQL.open
  ) where

import qualified Control.Exception as Exn
import qualified Database.SQLite.Simple as SQL
import qualified Database.SQLite.Simple.ToField as SQL

import           Inf.Types

runDB :: SQL.Connection -> DB a -> IO a
runDB c db = fromDB db c

newtype DB r = DB { fromDB :: SQL.Connection -> IO r }

instance Functor DB where
  fmap fn (DB k) = DB (fmap fn . k)

instance Applicative DB where
  pure x = DB (\ _ -> pure x)
  DB fn <*> DB x =
    DB (\ c -> fn c <*> x c)

instance Monad DB where
  DB x >>= fn = DB $ \ c -> do
    x' <- x c
    fromDB (fn x') c


io :: IO a -> DB a
io k = DB (\ _ -> k)

raise :: DBException -> DB a
raise e = DB (\ _ -> Exn.throwIO e)

data Field where
  Field :: SQL.ToField r => r -> Field

instance SQL.ToField Field where
  toField (Field x) = SQL.toField x

f :: SQL.ToField r => r -> Field
f = Field

query :: SQL.FromRow r => SQL.Query -> [Field] -> DB [r]
query q [] = DB $ \conn -> SQL.query_ conn q
query q as = DB $ \conn -> SQL.query conn q as

queryMb :: SQL.FromRow r => SQL.Query -> [Field] -> DB (Maybe r)
queryMb q as = DB $ \conn -> do
  rs <- case as of
    [] -> SQL.query_ conn q
    _  -> SQL.query conn q as
  case rs of
    []  -> pure Nothing
    [x] -> pure (Just x)
    _   -> Exn.throwIO NonUniqueResult

queryOne :: (SQL.FromRow r) => SQL.Query -> [Field] -> DB r
queryOne q as = DB $ \conn -> do
  rs <- case as of
    [] -> SQL.query_ conn q
    _  -> SQL.query conn q as
  case rs of
    [x] -> pure x
    _   -> Exn.throwIO NonUniqueResult

execute :: SQL.Query -> [Field] -> DB ()
execute q as = DB $ \conn -> do
  case as of
    [] -> SQL.execute_ conn q
    _  -> SQL.execute conn q as

lastRow :: DB Int
lastRow = DB $ \conn -> fromIntegral `fmap` SQL.lastInsertRowId conn
