-- | A crude, unsafe and preliminary solution to building B9 'SharedImage's
-- from Shake.
module B9.Shake.SharedImageRules
  ( customSharedImageAction
  , needSharedImage
  , enableSharedImageRules
  )
where

import           Development.Shake
import           Development.Shake.Classes
import           Development.Shake.Rule
import qualified Data.ByteString               as ByteString
import qualified Data.ByteString.Lazy          as LazyByteString
import qualified Data.Binary                   as Binary
import           B9

-- | In order to use 'needSharedImage' and 'customSharedImageAction' you need to
-- call this action before using any of the afore mentioned.
enableSharedImageRules :: B9ConfigOverride -> Rules ()
enableSharedImageRules b9inv = addBuiltinRule noLint go
 where
  go
    :: SharedImageName
    -> Maybe ByteString.ByteString
    -> Bool
    -> Action (RunResult SharedImageBuildId)
  go nameQ mSerlializedBuildId dependenciesChanged =
    let mOld = decodeBuildId <$> mSerlializedBuildId
    in  do
          (rebuilt, newBuildId) <- if dependenciesChanged
            then (True, ) <$> rebuild
            else do
              mNewBuildId <- getImgBuildId
              maybe ((\i -> (True, i)) <$> rebuild)
                    (\i -> return (False, i))
                    mNewBuildId
          let newBuildIdBin = encodeBuildId newBuildId
              change        = if rebuilt
                then maybe
                  ChangedRecomputeDiff
                  (\buildIdChanged -> if buildIdChanged
                    then ChangedRecomputeSame
                    else ChangedRecomputeSame
                  )
                  ((/= newBuildId) <$> mOld)
                else maybe
                  ChangedStore
                  (\buildIdChanged -> if buildIdChanged
                    then ChangedRecomputeSame
                    else ChangedRecomputeSame
                  )
                  ((/= newBuildId) <$> mOld)
              result = RunResult change newBuildIdBin newBuildId
          return result
   where
    getImgBuildId = execB9ConfigAction (runLookupLocalSharedImage nameQ) b9inv

    decodeBuildId :: ByteString.ByteString -> SharedImageBuildId
    decodeBuildId = Binary.decode . LazyByteString.fromStrict

    encodeBuildId :: SharedImageBuildId -> ByteString.ByteString
    encodeBuildId = LazyByteString.toStrict . Binary.encode

    rebuild :: Action SharedImageBuildId
    rebuild = do
      rules <- getUserRules
      case userRuleMatch rules imgMatch of
        [] ->
          fail $ "No rules to build B9 shared image " ++ show nameQ ++ " found"
        [act] -> act b9inv
        _rs ->
          fail
            $  "Multiple rules for the B9 shared image "
            ++ show nameQ
            ++ " found"
     where
      imgMatch (SharedImageCustomActionRule name mkImage) =
        if name == nameQ then Just mkImage else Nothing

-- | Add a dependency to the creation of a 'SharedImage'. The build action
-- for the shared image must have been supplied by e.g. 'customSharedImageAction'.
-- NOTE: You must call 'enableSharedImageRules' before this action works.
needSharedImage :: SharedImageName -> Action SharedImageBuildId
needSharedImage = apply1

-- | Specify an arbitrary action that is supposed to build the given shared
-- image identified by a 'SharedImageName'.
-- NOTE: You must call 'enableSharedImageRules' before this action works.
customSharedImageAction :: SharedImageName -> Action () -> Rules ()
customSharedImageAction b9img customAction = addUserRule
  (SharedImageCustomActionRule b9img customAction')
 where
  customAction' b9inv = do
    customAction
    mCurrentBuildId <- execB9ConfigAction (runLookupLocalSharedImage b9img)
                                          b9inv
    putLoud
      (printf "Finished custom action, for %s, build-id is: %s"
              (show b9img)
              (show mCurrentBuildId)
      )
    maybe (errorSharedImageNotFound b9img) return mCurrentBuildId


type instance RuleResult SharedImageName = SharedImageBuildId

data SharedImageCustomActionRule =
  SharedImageCustomActionRule SharedImageName (B9ConfigOverride -> Action SharedImageBuildId)
  deriving Typeable

errorSharedImageNotFound :: Monad m => SharedImageName -> m a
errorSharedImageNotFound = fail . printf "Error: %s not found." . show