{-# LANGUAGE ScopedTypeVariables #-}
{-| Module      :  Database.HSQL.SQLite3
    Copyright   :  (c) Krasimir Angelov 2005
    License     :  BSD-style

    Maintainer  :  kr.angelov@gmail.com
    Stability   :  provisional
    Portability :  portable

    The module provides interface to SQLite3
-}
module Database.HSQL.SQLite3(connect
                            ,module Database.HSQL) where

import Foreign(Ptr,alloca,peek,nullPtr,peekElemOff,nullFunPtr)
import Foreign.C(CString,CInt,peekCString,withCString)
import System.IO(IOMode(..))
import Control.Monad(when)
import Control.Exception(throw)
import Control.Concurrent.MVar(newMVar,modifyMVar,readMVar)

import Database.HSQL
import Database.HSQL.Types(Connection(..),Statement(..))

import DB.HSQL.SQLite3.Functions

------------------------------------------------------------------------------
-- Connect
------------------------------------------------------------------------------
-- |
connect :: FilePath -> IOMode -> IO Connection
connect fpath mode =
    alloca $ \psqlite ->
	withCString fpath $ \pFPath -> do
	  res <- sqlite3_open pFPath psqlite
	  sqlite <- peek psqlite
	  when (res /= sqliteOk) $ do
	    pMsg <- sqlite3_errmsg sqlite
	    msg <- peekCString pMsg
	    throw SqlError { seState = "C"
			   , seNativeError = 0
			   , seErrorMsg = msg }
	  refFalse <- newMVar False
	  let connection 
                = Connection { connDisconnect = sqlite3_close sqlite
			     , connClosed     = refFalse
			     , connExecute    = sqlite3Execute sqlite
			     , connQuery      = sqlite3Query connection sqlite
			     , connTables     = sqlite3Tables connection sqlite
			     , connDescribe   = 
                                 sqlite3Describe connection sqlite
			     , connBeginTransaction = 
                                 sqlite3Execute sqlite "BEGIN TRANSACTION"
			     , connCommitTransaction = 
                                 sqlite3Execute sqlite "COMMIT TRANSACTION"
			     , connRollbackTransaction = 
                                 sqlite3Execute sqlite "ROLLBACK TRANSACTION" }
	  return connection
    where oflags1 = case mode of
	    ReadMode      -> oRdOnly
	    WriteMode     -> oWrOnly
	    ReadWriteMode -> oRdWr
	    AppendMode    -> oAppend

-- |
sqlite3Tables :: Connection -> SQLite3 -> IO [String]
sqlite3Tables connection sqlite = do
  stmt <- sqlite3Query connection sqlite "SELECT tbl_name FROM sqlite_master"
  collectRows (\stmt -> getFieldValue stmt "tbl_name") stmt

-- |
sqlite3Describe :: Connection -> SQLite3 -> String -> IO [ColDef]
sqlite3Describe connection sqlite table = do
  stmt <- sqlite3Query connection sqlite ("pragma table_info("++table++")")
  collectRows getRow stmt

-- |
sqlite3Query :: Connection -> SQLite3 -> String -> IO Statement
sqlite3Query connection sqlite query = do
  withCString query $ \pQuery -> do
  alloca $ \ppResult -> do
  alloca $ \pnRow -> do
  alloca $ \pnColumn -> do
  alloca $ \ppMsg -> do
    res <- sqlite3_get_table sqlite pQuery ppResult pnRow pnColumn ppMsg
    handleSqlResult res ppMsg
    pResult <- peek ppResult
    rows    <- fmap fromIntegral (peek pnRow)
    columns <- fmap fromIntegral (peek pnColumn)
    defs <- getFieldDefs pResult 0 columns
    refFalse <- newMVar False
    refIndex <- newMVar 0
    return Statement { stmtConn   = connection
		     , stmtClose  = sqlite3_free_table pResult
		     , stmtFetch  = sqlite3Fetch refIndex rows
		     , stmtGetCol = getColValue pResult refIndex columns rows
		     , stmtFields = defs
		     , stmtClosed = refFalse }

-- |
getRow stmt = do
  name <- getFieldValue stmt "name"
  notnull <- getFieldValue stmt "notnull"
  return (name, SqlText, notnull=="0")

-- |
getFieldDefs :: Ptr CString -> Int -> Int -> IO [ColDef]
getFieldDefs pResult index count
    | index >= count = return []
    | otherwise = do
        name <- peekElemOff pResult index >>= peekCString
	defs <- getFieldDefs pResult (index+1) count
	return ((name,SqlText,True):defs)

-- |
sqlite3Fetch tupleIndex countTuples =
    modifyMVar tupleIndex 
               (\index -> return (index+1,index < countTuples))

-- |
getColValue pResult refIndex columns rows colNumber fieldDef f = do
  index <- readMVar refIndex
  when (index > rows) (throw SqlNoMoreData)
  pStr <- peekElemOff pResult (columns*index+colNumber)
  if pStr == nullPtr
    then f fieldDef pStr 0
    else do strLen <- strlen pStr
	    f fieldDef pStr (fromIntegral strLen)

-- |
sqlite3Execute :: SQLite3 -> String -> IO ()
sqlite3Execute sqlite query =
    withCString query $ \pQuery -> do
      alloca $ \ppMsg -> do
	res <- sqlite3_exec sqlite pQuery nullFunPtr nullPtr ppMsg
	handleSqlResult res ppMsg


------------------------------------------------------------------------------
-- routines for handling exceptions
------------------------------------------------------------------------------
-- |
handleSqlResult :: CInt -> Ptr CString -> IO ()
handleSqlResult res ppMsg
    | fromIntegral res == sqliteOk = return ()
    | otherwise = do
        pMsg <- peek ppMsg
	msg <- peekCString pMsg
	sqlite3_free pMsg
	throw (SqlError "E" (fromIntegral res) msg)
