module Database.Beam.Internal where


import Database.Beam.Schema.Tables
import Database.Beam.SQL.Types

import Control.Applicative
import Control.Monad
import Control.Arrow
import Control.Monad.Error
import Control.Monad.Reader

import Data.Text (Text, unpack)
import Data.Proxy
import Data.String
import Data.Typeable

import Database.HDBC

data DBSchemaComparison = Migration [MigrationAction]
                        | Unknown
                          deriving Show

data MigrationAction where
    MACreateTable :: Table table => Text -> Proxy table -> MigrationAction

instance Show MigrationAction where
    show (MACreateTable name t) = concat ["MACreateTable ", unpack name , " ", "(Proxy :: ", show (typeOf t), ")"]

class BeamBackend backendSettings where
    openBeam :: (MonadIO m, Database d) => DatabaseSettings d -> backendSettings -> m (Beam d m)

data Beam d m = Beam
              { beamDbSettings :: DatabaseSettings d
              , beamDebug :: Bool

              , closeBeam :: m ()

              , compareSchemas :: ReifiedDatabaseSchema -> DatabaseSettings d -> DBSchemaComparison
              , adjustColDescForBackend :: SQLColumnSchema -> SQLColumnSchema

              , getLastInsertedRow :: Text -> m [SqlValue]

              , withHDBCConnection :: forall a. (forall conn. IConnection conn => conn -> m a) -> m a }

newtype BeamT e d m a = BeamT { runBeamT :: Beam d m -> m (BeamResult e a) }

data BeamResult e a = Success a
                    | Rollback (BeamRollbackReason e)
                      deriving Show

data BeamRollbackReason e = InternalError String
                          | UserError e
                            deriving Show
instance Error (BeamRollbackReason e) where
    strMsg = InternalError


transBeam :: Functor m => (forall a. (s -> m (a, Maybe b)) -> n a) -> (forall a. n a -> s -> m (a, b)) -> Beam d m -> Beam d n
transBeam lift lower beam = beam
                          { closeBeam = lift (const ((,Nothing) <$> closeBeam beam))
                          , getLastInsertedRow = \s -> lift (const ((, Nothing) <$> getLastInsertedRow beam s))
                          , withHDBCConnection = \f -> lift (\s -> second Just <$> withHDBCConnection beam (flip lower s . f)) }

instance Monad m => Monad (BeamT e d m) where
    a >>= mkB = BeamT $ \beam ->
                do x <- runBeamT a beam
                   case x of
                     Success x -> runBeamT (mkB x) beam
                     Rollback e -> return (Rollback e)
    return = BeamT . const . return . Success

instance Monad m => Functor (BeamT e d m) where
    fmap  = liftM

instance Monad m => Applicative (BeamT e d m) where
    pure  = return
    (<*>) = ap

instance MonadIO m => MonadIO (BeamT e d m) where
    liftIO = lift . liftIO

instance MonadTrans (BeamT e d) where
    lift x = BeamT $ \_ ->
             do res <- x
                return (Success res)