{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE CPP #-}
module Database.Beam.Migrate.Types
  ( 
    CheckedDatabaseSettings
  , IsCheckedDatabaseEntity(..)
  , CheckedDatabaseEntityDescriptor(..)
  , CheckedDatabaseEntity(..)
  , unCheckDatabase, collectChecks
  , renameCheckedEntity
    
    
    
  , CheckedFieldModification
  , checkedFieldNamed
  , modifyCheckedTable
  , checkedTableModification
    
  , DatabasePredicate(..)
  , SomeDatabasePredicate(..)
  , PredicateSpecificity(..)
  , QualifiedName(..)
  , p
    
  , TableCheck(..), DomainCheck(..)
  , FieldCheck(..)
    
  , MigrationStep(..), MigrationSteps(..)
  , Migration, MigrationF(..)
  , MigrationCommand(..), MigrationDataLoss(..)
  , runMigrationSteps, runMigrationSilenced
  , executeMigration, eraseMigrationType, migrationStep
  , upDown, migrationDataLoss
  , migrateScript, evaluateDatabase, stepNames ) where
import Database.Beam.Backend.SQL
import Database.Beam.Migrate.Types.CheckedEntities
import Database.Beam.Migrate.Types.Predicates
import Control.Monad.Free.Church
import Control.Arrow
import Control.Category (Category)
#if !MIN_VERSION_base(4, 11, 0)
import Data.Semigroup
#endif
import Data.Text (Text)
data MigrationStep be next where
    MigrationStep :: Text -> Migration be a -> (a -> next) -> MigrationStep be next
deriving instance Functor (MigrationStep be)
newtype MigrationSteps be from to = MigrationSteps (Kleisli (F (MigrationStep be)) from to)
  deriving (Category, Arrow)
data MigrationF be next where
  MigrationRunCommand
    :: { _migrationUpCommand   :: BeamSqlBackendSyntax be
       
       , _migrationDownCommand :: Maybe (BeamSqlBackendSyntax be)
       
       , _migrationNext :: next }
    -> MigrationF be next
deriving instance Functor (MigrationF be)
type Migration be = F (MigrationF be)
data MigrationDataLoss
  = MigrationLosesData
    
  | MigrationKeepsData
    
  deriving Show
instance Semigroup MigrationDataLoss where
    (<>) = mappend
instance Monoid MigrationDataLoss where
    mempty = MigrationKeepsData
    mappend MigrationLosesData _ = MigrationLosesData
    mappend _ MigrationLosesData = MigrationLosesData
    mappend MigrationKeepsData MigrationKeepsData = MigrationKeepsData
data MigrationCommand be
  = MigrationCommand
  { migrationCommand :: BeamSqlBackendSyntax be
    
  , migrationCommandDataLossPossible :: MigrationDataLoss
    
  }
deriving instance Show (BeamSqlBackendSyntax be) => Show (MigrationCommand be)
runMigrationSteps :: Monad m
                  => Int 
                  -> Maybe Int 
                  -> MigrationSteps be () a 
                  -> (forall a'. Int -> Text -> Migration be a' -> m a')
                  
                  
                  -> m a
runMigrationSteps firstIdx lastIdx (MigrationSteps steps) runMigration =
  runF (runKleisli steps ()) finish step 0
  where finish x _ = pure x
        step (MigrationStep nm doStep next) i =
          if i >= firstIdx && maybe True (i <) lastIdx
          then runMigration i nm doStep >>= \x -> next x (i + 1)
          else next (runMigrationSilenced doStep) (i + 1)
runMigrationSilenced :: Migration be a -> a
runMigrationSilenced m = runF m id step
  where
    step (MigrationRunCommand _ _ next) = next
eraseMigrationType :: a -> MigrationSteps be a a' -> MigrationSteps be () ()
eraseMigrationType a (MigrationSteps steps) = MigrationSteps (arr (const a) >>> steps >>> arr (const ()))
migrationStep :: Text -> (a -> Migration be a') -> MigrationSteps be a a'
migrationStep stepName migration =
    MigrationSteps (Kleisli (\a -> liftF (MigrationStep stepName (migration a) id)))
upDown :: BeamSqlBackendSyntax be -> Maybe (BeamSqlBackendSyntax be) -> Migration be ()
upDown up down = liftF (MigrationRunCommand up down ())
migrateScript :: forall be m a. (Monoid m, Semigroup m, BeamSqlBackend be)
              => (Text -> m)
              
              -> (BeamSqlBackendSyntax be -> m)
              
              -> MigrationSteps be () a
              
              -> m
migrateScript renderMigrationHeader renderMigrationSyntax (MigrationSteps steps) =
  runF (runKleisli steps ()) (\_ x -> x)
    (\(MigrationStep header migration next) x ->
       let (res, script) = renderMigration migration mempty
       in next res (x <> renderMigrationHeader header <> script)) mempty
  where
    renderMigration :: forall a'. Migration be a' -> m -> (a', m)
    renderMigration migrationSteps =
      runF migrationSteps (,)
           (\(MigrationRunCommand a _ next) x -> next (x <> renderMigrationSyntax a))
executeMigration :: Applicative m => (BeamSqlBackendSyntax be -> m ()) -> Migration be a -> m a
executeMigration runSyntax go = runF go pure doStep
  where
    doStep (MigrationRunCommand cmd _ next) =
      runSyntax cmd *> next
migrationDataLoss :: Migration be a -> MigrationDataLoss
migrationDataLoss go = runF go (\_ -> MigrationKeepsData)
                         (\(MigrationRunCommand _ x next) ->
                            case x of
                              Nothing -> MigrationLosesData
                              _ -> next)
evaluateDatabase :: forall be a. MigrationSteps be () a -> a
evaluateDatabase (MigrationSteps f) = runF (runKleisli f ()) id (\(MigrationStep _ migration next) -> next (runMigration migration))
  where
    runMigration :: forall a'. Migration be a' -> a'
    runMigration migration = runF migration id (\(MigrationRunCommand _ _ next) -> next)
stepNames :: forall be a. MigrationSteps be () a -> [Text]
stepNames (MigrationSteps f) = runF (runKleisli f ()) (\_ x -> x) (\(MigrationStep nm migration next) x -> next (runMigration migration) (x ++ [nm])) []
  where
    runMigration :: forall a'. Migration be a' -> a'
    runMigration migration = runF migration id (\(MigrationRunCommand _ _ next) -> next)