-- | This module contains functions to build assets (that is, run preprocessing
-- if necessary, and copy to destination directory).
module Web.Herringbone.BuildAsset where

import Control.Monad.Reader
import Data.Time
import Filesystem.Path.CurrentOS (FilePath, (</>))
import qualified Filesystem as F
import Prelude hiding (FilePath)

import Web.Herringbone.Types

-- | Build an asset to produce a 'Asset'. This action checks whether the
-- compilation is necessary based on the modified times of the source and
-- destination files.
buildAsset :: Herringbone
           -> LogicalPath -- ^ Logical path of asset to build
           -> FilePath    -- ^ Source file path
           -> [PP]        -- ^ List of preprocessors to run
           -> IO (Either CompileError Asset)
buildAsset hb logPath sourcePath pps = do
    let destPath = hbDestDir hb </> toFilePath logPath

    sourceModifiedTime <- F.getModified sourcePath
    compileNeeded <- shouldCompile sourceModifiedTime destPath

    result <- if compileNeeded
                then compileAsset hb logPath sourcePath destPath pps
                else return $ Right ()

    either (return . Left)
           (\_ -> do size <- F.getSize destPath
                     return . Right $ Asset
                                        size
                                        sourcePath
                                        destPath
                                        logPath
                                        sourceModifiedTime)
           result

-- | Should we compile an asset? True if either the asset doesn't exist, or if
-- its modified time is older than the supplied source modification time.
shouldCompile :: UTCTime -> FilePath -> IO Bool
shouldCompile sourceModifiedTime destPath = do
    exists <- F.isFile destPath
    if not exists
        then return True
        else do
            destModifiedTime <- F.getModified destPath
            return $ sourceModifiedTime > destModifiedTime

-- | Compile the given asset by invoking any preprocessors on the source path,
-- and copying the result to the destination path.
compileAsset :: Herringbone
             -> LogicalPath
             -> FilePath -- ^ Source path
             -> FilePath -- ^ Dest path
             -> [PP]     -- ^ List of preprocessors to apply
             -> IO (Either CompileError ())
compileAsset hb logPath sourcePath destPath pps = do
    sourceData <- F.readFile sourcePath

    let computation = chainEither (map ppAction pps) sourceData
    let readerData = PPReader
            { ppReaderHb          = hb
            , ppReaderLogicalPath = logPath
            , ppReaderSourcePath  = sourcePath
            , ppReaderPPs         = pps
            }
    result <- runPPM computation readerData

    either (return . Left)
           (\resultData -> do F.writeFile destPath resultData
                              return (Right ()))
           result

chainEither :: Monad m => [a -> m (Either b a)] -> a -> m (Either b a)
chainEither fs m = foldl go z fs
    where
        go = \acc f -> acc >>= either (return . Left) f
        z  = return (Right m)