{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

-- |
-- Module: BDCS.Export
-- Copyright: (c) 2017-2018 Red Hat, Inc.
-- License: LGPL
--
-- Maintainer: https://github.com/weldr
-- Stability: alpha
-- Portability: portable
--
-- Top-level function for exporting objects from the BDCS.

module BDCS.Export(export,
                   exportAndCustomize)
 where

import           Control.Monad.Except(MonadError, runExceptT, throwError)
import           Control.Monad.Logger(MonadLoggerIO, logDebugN)
import           Control.Monad.Trans(lift)
import           Control.Monad.Trans.Resource(MonadBaseControl, MonadResource)
import           Data.Conduit(Consumer, (.|), runConduit, runConduitRes)
import qualified Data.Conduit.List as CL
import           Data.ContentStore(openContentStore)
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import           Database.Esqueleto(SqlPersistT)

import qualified BDCS.CS as CS
import           BDCS.DB(Files)
import qualified BDCS.Export.Directory as Directory
import           BDCS.Export.Customize(Customization, filesToObjectsC, runCustomizations)
import           BDCS.Export.FSTree(filesToTree, fstreeSource)
import qualified BDCS.Export.Ostree as Ostree
import qualified BDCS.Export.Qcow2 as Qcow2
import qualified BDCS.Export.Tar as Tar
import           BDCS.Export.Types(ExportType(..))
import           BDCS.Export.Utils(runHacks, runTmpfiles)
import           BDCS.Files(groupIdToFilesC)
import           BDCS.Groups(getGroupIdC)

export :: (MonadBaseControl IO m, MonadError String m, MonadLoggerIO m, MonadResource m) =>
          FilePath
       -> FilePath
       -> ExportType
       -> [T.Text]
       -> SqlPersistT m ()
export repo out_path ty things = exportAndCustomize repo out_path ty things []

exportAndCustomize :: (MonadBaseControl IO m, MonadError String m, MonadLoggerIO m, MonadResource m) =>
                      FilePath
                   -> FilePath
                   -> ExportType
                   -> [T.Text]
                   -> [Customization]
                   -> SqlPersistT m ()
exportAndCustomize repo out_path ty things custom | kernelMissing ty things  = throwError "ERROR: ostree exports need a kernel package included"
                                                  | dracutMissing ty things  = throwError "ERROR: ostree exports need a dract package included"
                                                  | filesystemMissing things = throwError "ERROR: exports need a filesystem package included"
                                                  | otherwise                = do
    let objectSink = case ty of
                         ExportTar       -> CS.objectToTarEntry .| Tar.tarSink out_path
                         ExportQcow2     -> Qcow2.qcow2Sink out_path
                         ExportOstree    -> Ostree.ostreeSink out_path
                         ExportDirectory -> directoryOutput out_path

    runExceptT (openContentStore repo) >>= \case
        Left e   -> throwError $ show e
        Right cs -> do
            fstree <- runConduit $ CL.sourceList things
                          .| getGroupIdC
                          .| groupIdToFilesC
                          .| filesToTree

            let overlay = Map.empty
            (overlay', fstree') <- runCustomizations overlay cs fstree custom

            runConduitRes $ fstreeSource fstree' .| filesToObjectsC overlay' cs .| objectSink
 where
    directoryOutput :: (MonadBaseControl IO m, MonadError String m, MonadLoggerIO m) => FilePath -> Consumer (Files, CS.Object) m ()
    directoryOutput path = do
        -- Apply tmpfiles.d to the directory first
        logDebugN "Running tmpfiles"
        lift $ runTmpfiles path

        Directory.directorySink path
        logDebugN "Running standard hacks"
        lift $ runHacks path

    kernelMissing :: ExportType -> [T.Text] -> Bool
    kernelMissing exportTy lst = exportTy == ExportOstree && not (any ("kernel-" `T.isPrefixOf`) lst)

    dracutMissing :: ExportType -> [T.Text] -> Bool
    dracutMissing exportTy lst = exportTy == ExportOstree && not (any ("dracut-" `T.isPrefixOf`) lst)

    filesystemMissing :: [T.Text] -> Bool
    filesystemMissing lst = not (any ("filesystem-" `T.isPrefixOf`) lst)
