{-# LANGUAGE ExistentialQuantification #-}

module Moo.Core where

import Control.Applicative ((<$>), (<*>))
import Control.Monad.Reader (ReaderT)
import qualified Data.Configurator as C
import Data.Configurator.Types (Config)
import qualified Data.Text as T
import Database.HDBC (IConnection)
import Database.HDBC.PostgreSQL (connectPostgreSQL)
import Database.HDBC.Sqlite3 (connectSqlite3)

import Database.Schema.Migrations ()
import Database.Schema.Migrations.Backend.HDBC ()
import Database.Schema.Migrations.Filesystem (FilesystemStore)
import Database.Schema.Migrations.Store (StoreData)


-- |The monad in which the application runs.
type AppT a = ReaderT AppState IO a


-- |The type of actions that are invoked to handle specific commands
type CommandHandler = StoreData -> AppT ()


-- |Application state which can be accessed by any command handler.
data AppState = AppState { _appOptions         :: CommandOptions
                         , _appCommand         :: Command
                         , _appRequiredArgs    :: [String]
                         , _appOptionalArgs    :: [String]
                         , _appStore           :: FilesystemStore
                         , _appDatabaseConnStr :: Maybe DbConnDescriptor
                         , _appDatabaseType    :: Maybe String
                         , _appStoreData       :: StoreData
                         }

type ShellEnvironment = [(String, String)]

data Configuration = Configuration
    { _connectionString   :: Maybe String
    , _databaseType       :: Maybe String
    , _migrationStorePath :: Maybe FilePath
    }

fromShellEnvironment :: ShellEnvironment -> Configuration
fromShellEnvironment env = Configuration connectionString
                                         databaseType
                                         migrationStorePath
    where
      connectionString = envLookup envDatabaseName
      databaseType = envLookup envDatabaseType
      migrationStorePath = envLookup envStoreName
      envLookup = (\evar -> lookup evar env)

fromConfigurator :: Config -> IO Configuration
fromConfigurator conf = Configuration <$> connectionString
                                      <*> databaseType
                                      <*> migrationStorePath
    where
      connectionString = configLookup envDatabaseName
      databaseType = configLookup envDatabaseType
      migrationStorePath = configLookup envStoreName
      configLookup = C.lookup conf . T.pack

-- |Type wrapper for IConnection instances so the makeConnection
-- function can return any type of connection.
data AnyIConnection = forall c. (IConnection c) => AnyIConnection c


-- |CommandOptions are those options that can be specified at the command
-- prompt to modify the behavior of a command.
data CommandOptions = CommandOptions { _configFilePath :: Maybe String
                                     , _test           :: Bool
                                     , _noAsk          :: Bool
                                     }


-- |A command has a name, a number of required arguments' labels, a
-- number of optional arguments' labels, and an action to invoke.
data Command = Command { _cName           :: String
                       , _cRequired       :: [String]
                       , _cOptional       :: [String]
                       , _cAllowedOptions :: [String]
                       , _cDescription    :: String
                       , _cHandler        :: CommandHandler
                       }


-- |ConfigOptions are those options read from configuration file
data ConfigData = ConfigData { _dbTypeStr     :: String
                             , _dbConnStr     :: String
                             , _fileStorePath :: String
                             }


newtype DbConnDescriptor = DbConnDescriptor String


-- |The values of DBM_DATABASE_TYPE and their corresponding connection
-- factory functions.
databaseTypes :: [(String, String -> IO AnyIConnection)]
databaseTypes = [ ("postgresql", fmap AnyIConnection . connectPostgreSQL)
                , ("sqlite3", fmap AnyIConnection . connectSqlite3)
                ]

envDatabaseType :: String
envDatabaseType = "DBM_DATABASE_TYPE"

envDatabaseName :: String
envDatabaseName = "DBM_DATABASE"

envStoreName :: String
envStoreName = "DBM_MIGRATION_STORE"