{-# LANGUAGE RecordWildCards #-}

{-
    The responsibility of the baker is to turn raw deployments into baked
    deployments. This takes care of everything that couldn't happen during
    compilation yet. The differences between raw deployments and baked
    deployments are:
    - Baked deployments only deal with absolute filepaths so as to be
      working-directory-independent.
    - Baked deployments are aware of the kind of things the checker/deployer
      will be operating on (files versus directories).

    The baker is not responsible for checking any existences.
-}
module SuperUserSpark.Bake where

import Import

import qualified Data.Aeson.Encode.Pretty as JSON
import qualified Data.ByteString.Lazy.Char8 as LB
import System.Environment (getEnvironment)
import System.FilePath (takeExtension)

import SuperUserSpark.Bake.Internal
import SuperUserSpark.Bake.Types
import SuperUserSpark.Compiler
import SuperUserSpark.Compiler.Types
import SuperUserSpark.Language.Types
import SuperUserSpark.OptParse.Types
import SuperUserSpark.Utils

bakeFromArgs :: BakeArgs -> IO ()
bakeFromArgs ba = do
    errOrAss <- bakeAssignment ba
    case errOrAss of
        Left be -> die $ unwords ["Failed to build bake assignment:", be]
        Right ass -> bake ass

bakeAssignment :: BakeArgs -> IO (Either String BakeAssignment)
bakeAssignment BakeArgs {..} = do
    errOrCardRef <- parseBakeCardReference bakeCardRef
    case errOrCardRef of
        Left err -> pure $ Left err
        Right cardRef ->
            BakeAssignment cardRef <$$> deriveBakeSettings cardRef bakeFlags

parseBakeCardReference :: String -> IO (Either String BakeCardReference)
parseBakeCardReference s =
    case words s of
        [fp] ->
            if takeExtension fp == ".sus"
                then BakeCardUncompiled <$$> parseStrongCardFileReference fp
                else BakeCardCompiled <$$> resolveFile'Either fp
        [f, c] ->
            BakeCardUncompiled <$$>
            ((\(StrongCardFileReference p _) ->
                  StrongCardFileReference p (Just $ CardNameReference c)) <$$>
             parseStrongCardFileReference f)
        _ -> pure $ Left $ unwords ["Could not parse card reference from:", s]

deriveBakeSettings :: BakeCardReference -> BakeFlags -> IO (Either String BakeSettings)
deriveBakeSettings bcr BakeFlags {..} =
    BakeSettings (rootOf bcr) <$$> (Right <$> getEnvironment) <**>
    deriveCompileSettings bakeCompileFlags

rootOf :: BakeCardReference -> Path Abs Dir
rootOf bcr =
    parent $
    case bcr of
        (BakeCardCompiled fp) -> fp
        (BakeCardUncompiled (StrongCardFileReference fp _)) -> fp

bake :: BakeAssignment -> IO ()
bake BakeAssignment {..} = do
    errOrDone <-
        runReaderT (runExceptT $ bakeByCardRef bakeCardReference) bakeSettings
    case errOrDone of
        Left err -> die $ formatBakeError err
        Right () -> pure ()

formatBakeError :: BakeError -> String
formatBakeError (BakeCompileError ce) = formatCompileError ce
formatBakeError (BakeError s) = unwords ["Bake failed:", s]

bakeByCardRef :: BakeCardReference -> SparkBaker ()
bakeByCardRef bakeCardReference = do
    deps <- compileBakeCardRef bakeCardReference
    bdeps <- bakeDeployments deps
    liftIO . LB.putStrLn $ JSON.encodePretty bdeps

compileBakeCardRef :: BakeCardReference -> SparkBaker [RawDeployment]
compileBakeCardRef (BakeCardCompiled fp) = bakerCompile $ inputCompiled fp
compileBakeCardRef (BakeCardUncompiled bcf) = bakerCompile $ compileJob bcf

bakerCompile :: ImpureCompiler a -> SparkBaker a
bakerCompile =
    withExceptT BakeCompileError . mapExceptT (withReaderT bakeCompileSettings)