    {-# LANGUAGE ExistentialQuantification, Rank2Types #-}
    -----------------------------------------------------------
    -- |
    -- Module      :  DriverAPI
    -- Copyright   :  Anders Hockersten (c), chucky@dtek.chalmers.se
    -- License     :  BSD-style
    --
    -- Maintainer  :  chucky@dtek.chalmers.se
    -- Stability   :  experimental
    -- Portability :  portable
    --
    -- This exports an API that all drivers must conform to. It
    -- is used by the end user to load drivers either dynamically
    -- or statically.
    -----------------------------------------------------------

    module Database.HaskellDB.DriverAPI (
    				     DriverInterface(..),
                                         MonadIO,
    				     defaultdriver,
                                         getOptions,
                                         getAnnotatedOptions,
                                         getGenerator
    				    ) where

    import Database.HaskellDB.Database (Database)

    import Database.HaskellDB.Sql.Generate (SqlGenerator)
    import Database.HaskellDB.Sql.Default (defaultSqlGenerator)
    import Database.HaskellDB.Sql.MySQL as MySQL
    import Database.HaskellDB.Sql.PostgreSQL as PostgreSQL
    import Database.HaskellDB.Sql.SQLite as SQLite

    import Control.Monad (liftM)
    import Control.Monad.Trans (MonadIO)

    -- | Interface which drivers should implement.
    --   The 'connect' function takes some driver specific name, value pairs
    --   use to setup the database connection, and a database action to run.
    --   'requiredOptions' lists all required options with a short description,
    --   that is printed as help in the DBDirect program.
    data DriverInterface = DriverInterface
        { connect :: forall m a. MonadIO m => [(String,String)] -> (Database -> m a) -> m a,
          requiredOptions :: [(String, String)]
        }

    -- | Default dummy driver, real drivers should overload this
    defaultdriver :: DriverInterface
    defaultdriver =
        DriverInterface {
            connect = error "DriverAPI.connect: not implemented",
            requiredOptions = error "DriverAPI.requiredOptions: not implemented"}

    -- | Can be used by drivers to get option values from the given
    --   list of name, value pairs.
    getOptions ::Monad m => [String] -- ^ names of options to get
               -> [(String,String)] -- ^ options given
               -> m [String] -- ^ a list of the same length as the first argument
                             --   with the values of each option. Fails in the given
                             --   monad if any options is not found.
    getOptions [] _ = return []
    getOptions (x:xs) ys =
        case lookup x ys of
                         Nothing -> fail $ "Missing field " ++ x
                         Just v -> liftM (v:) $ getOptions xs ys

    -- | Can be used by drivers to get option values from the given
    --   list of name, value pairs.
    --   It is intended for use with the 'requiredOptions' value of the driver.
    getAnnotatedOptions :: Monad m =>
                  [(String,String)] -- ^ names and descriptions of options to get
               -> [(String,String)] -- ^ options given
               -> m [String] -- ^ a list of the same length as the first argument
                             --   with the values of each option. Fails in the given
                             --   monad if any options is not found.
    getAnnotatedOptions opts = getOptions (map fst opts)

    -- | Gets an 'SqlGenerator' from the "generator" option in the given list.
    --   Currently available generators: "mysql", "postgresql", "sqlite", "default"
    getGenerator :: Monad m =>
                    [(String,String)] -- ^ options given
               -> m SqlGenerator -- ^ An SQL generator. If there was no
                                 --   "generator" option, the default is used.
                                 -- Fails if the generator is unknown
    getGenerator opts = maybe (return defaultSqlGenerator) f $ lookup "generator" opts
        where f n = maybe (fail msg) return $ lookup n generators
                  where msg = "Unknown SqlGenerator: " ++ n

    generators :: [(String,SqlGenerator)]
    generators = [("mysql",      MySQL.generator),
                  ("postgresql", PostgreSQL.generator),
                  ("sqlite",     SQLite.generator),
                  ("default",    defaultSqlGenerator)]