{-# LANGUAGE RecordWildCards #-}

-- | Core recipes including reading files, saving them.
module Achille.Recipe
    ( Recipe
    , liftIO
    , getInput
    , getCurrentDir
    , readText
    , readBS
    , saveFile
    , saveFileAs
    , copyFile
    , copyFileAs
    , copy
    , write
    , task
    , debug
    , callCommand
    , runCommandWith
    , toTimestamped
    ) where

import Control.Monad.IO.Class  (liftIO)
import Data.Binary             (Binary, encodeFile)
import Data.Functor            (void)
import Data.Text               (Text, pack)
import Data.Text.Encoding      (decodeUtf8)
import Data.ByteString         (ByteString)
import System.FilePath         ((</>))

import           Achille.Config
import           Achille.Writable (Writable)
import           Achille.Timestamped
import qualified Achille.Writable as Writable
import Achille.Internal    as Internal
import Achille.Internal.IO (AchilleIO)
import qualified Achille.Internal.IO as AchilleIO


data Color = Red | Blue

color :: Color -> String -> String
color :: Color -> String -> String
color c :: Color
c x :: String
x = "\x1b[" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
start String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
x String -> String -> String
forall a. Semigroup a => a -> a -> a
<> "\x1b[0m"
    where start :: String
start = case Color
c of
                      Red  -> "31m"
                      Blue -> "34m"

-------------------------------------
-- Recipe building blocks (uncached)
-------------------------------------

-- | Recipe returning its input.
getInput :: Applicative m
         => Recipe m a a
getInput :: Recipe m a a
getInput = (Context a -> m a) -> Recipe m a a
forall (m :: * -> *) a b.
Functor m =>
(Context a -> m b) -> Recipe m a b
nonCached ((Context a -> m a) -> Recipe m a a)
-> (Context a -> m a) -> Recipe m a a
forall a b. (a -> b) -> a -> b
$ a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (a -> m a) -> (Context a -> a) -> Context a -> m a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Context a -> a
forall a. Context a -> a
inputValue

-- | Recipe for retrieving the current directory
getCurrentDir :: Applicative m
              => Recipe m a FilePath
getCurrentDir :: Recipe m a String
getCurrentDir = (Context a -> m String) -> Recipe m a String
forall (m :: * -> *) a b.
Functor m =>
(Context a -> m b) -> Recipe m a b
nonCached (String -> m String
forall (f :: * -> *) a. Applicative f => a -> f a
pure (String -> m String)
-> (Context a -> String) -> Context a -> m String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Context a -> String
forall a. Context a -> String
Internal.currentDir)

-- | Recipe retrieving the contents of the input file as text
readText :: AchilleIO m
         => Recipe m FilePath Text
readText :: Recipe m String Text
readText = (Context String -> m Text) -> Recipe m String Text
forall (m :: * -> *) a b.
Functor m =>
(Context a -> m b) -> Recipe m a b
nonCached \Context{..} ->
    ByteString -> Text
decodeUtf8 (ByteString -> Text) -> m ByteString -> m Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> m ByteString
forall (m :: * -> *). AchilleIO m => String -> m ByteString
AchilleIO.readFile (String
inputDir String -> String -> String
</> String
currentDir String -> String -> String
</> String
inputValue)

-- | Recipe retrieving the contents of the input file as a bytestring.
readBS :: AchilleIO m => Recipe m FilePath ByteString
readBS :: Recipe m String ByteString
readBS = (Context String -> m ByteString) -> Recipe m String ByteString
forall (m :: * -> *) a b.
Functor m =>
(Context a -> m b) -> Recipe m a b
nonCached \Context{..} ->
    String -> m ByteString
forall (m :: * -> *). AchilleIO m => String -> m ByteString
AchilleIO.readFile (String
inputDir String -> String -> String
</> String
currentDir String -> String -> String
</> String
inputValue)

-- | Recipe for saving a value to the location given as input.
--   Returns the input filepath as is.
saveFile :: (AchilleIO m, Writable m a)
         => a -> Recipe m FilePath FilePath
saveFile :: a -> Recipe m String String
saveFile = (String -> String) -> a -> Recipe m String String
forall (m :: * -> *) a.
(AchilleIO m, Writable m a) =>
(String -> String) -> a -> Recipe m String String
saveFileAs String -> String
forall a. a -> a
id

-- | Recipe for saving a value to a file, using the path modifier applied to the input filepath.
--   Returns the path of the output file.
saveFileAs :: (AchilleIO m, Writable m a)
           => (FilePath -> FilePath) -> a -> Recipe m FilePath FilePath
saveFileAs :: (String -> String) -> a -> Recipe m String String
saveFileAs mod :: String -> String
mod x :: a
x = (String -> a -> Recipe m String String)
-> a -> String -> Recipe m String String
forall a b c. (a -> b -> c) -> b -> a -> c
flip String -> a -> Recipe m String String
forall (m :: * -> *) b a.
(AchilleIO m, Writable m b) =>
String -> b -> Recipe m a String
write a
x (String -> Recipe m String String)
-> Recipe m String String -> Recipe m String String
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< String -> String
mod (String -> String)
-> Recipe m String String -> Recipe m String String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Recipe m String String
forall (m :: * -> *) a. Applicative m => Recipe m a a
getInput

-- | Recipe for copying an input file to the same location in the output dir.
copyFile :: AchilleIO m
         => Recipe m FilePath FilePath
copyFile :: Recipe m String String
copyFile = (String -> String) -> Recipe m String String
forall (m :: * -> *).
AchilleIO m =>
(String -> String) -> Recipe m String String
copyFileAs String -> String
forall a. a -> a
id

-- | Recipe for copying an input file to the output dir, using the path modifier.
copyFileAs :: AchilleIO m
           => (FilePath -> FilePath) -> Recipe m FilePath FilePath
copyFileAs :: (String -> String) -> Recipe m String String
copyFileAs mod :: String -> String
mod = Recipe m String String
forall (m :: * -> *) a. Applicative m => Recipe m a a
getInput Recipe m String String
-> (String -> Recipe m String String) -> Recipe m String String
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \from :: String
from -> String -> String -> Recipe m String String
forall (m :: * -> *) a.
AchilleIO m =>
String -> String -> Recipe m a String
copy String
from (String -> String
mod String
from)

-- | Recipe for copying a file to a given destination.
--   Returns the output filepath.
copy :: AchilleIO m
     => FilePath -> FilePath -> Recipe m a FilePath
copy :: String -> String -> Recipe m a String
copy from :: String
from to :: String
to = (Context a -> m String) -> Recipe m a String
forall (m :: * -> *) a b.
Functor m =>
(Context a -> m b) -> Recipe m a b
nonCached \Context{..} -> do
    String -> String -> m ()
forall (m :: * -> *). AchilleIO m => String -> String -> m ()
AchilleIO.copyFile (String
inputDir  String -> String -> String
</> String
currentDir String -> String -> String
</> String
from)
                       (String
outputDir String -> String -> String
</> String
currentDir String -> String -> String
</> String
to)
    String -> m ()
forall (m :: * -> *). AchilleIO m => String -> m ()
AchilleIO.log (Color -> String -> String
color Color
Blue (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ (String
currentDir String -> String -> String
</> String
from) String -> String -> String
forall a. Semigroup a => a -> a -> a
<> " → " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> (String
currentDir String -> String -> String
</> String
to))
    String -> m String
forall (f :: * -> *) a. Applicative f => a -> f a
pure String
to

-- | Recipe for writing to a an output file.
--   Returns the output filepath.
write :: (AchilleIO m, Writable m b)
      => FilePath -> b -> Recipe m a FilePath
write :: String -> b -> Recipe m a String
write to :: String
to x :: b
x = (Context a -> m String) -> Recipe m a String
forall (m :: * -> *) a b.
Functor m =>
(Context a -> m b) -> Recipe m a b
nonCached \Context{..} -> do
    String -> b -> m ()
forall (m :: * -> *) a. Writable m a => String -> a -> m ()
Writable.write (String
outputDir  String -> String -> String
</> String
currentDir String -> String -> String
</> String
to) b
x
    String -> m ()
forall (m :: * -> *). AchilleIO m => String -> m ()
AchilleIO.log (Color -> String -> String
color Color
Blue (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ "writing " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> (String
currentDir String -> String -> String
</> String
to))
    String -> m String
forall (f :: * -> *) a. Applicative f => a -> f a
pure String
to

-- | Make a recipe out of a task. The input will simply be discarded.
task :: Task m b -> Recipe m a b
task :: Task m b -> Recipe m a b
task (Recipe r :: Context () -> m (b, Cache)
r) = (Context a -> m (b, Cache)) -> Recipe m a b
forall (m :: * -> *) a b.
(Context a -> m (b, Cache)) -> Recipe m a b
Recipe (Context () -> m (b, Cache)
r (Context () -> m (b, Cache))
-> (Context a -> Context ()) -> Context a -> m (b, Cache)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Context a -> Context ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void)



------------------------------
-- Lifted IO
------------------------------

-- | Recipe for printing a value to the console.
debug :: AchilleIO m
      => Show b => b -> Recipe m a ()
debug :: b -> Recipe m a ()
debug = (Context a -> m ()) -> Recipe m a ()
forall (m :: * -> *) a b.
Functor m =>
(Context a -> m b) -> Recipe m a b
nonCached ((Context a -> m ()) -> Recipe m a ())
-> (b -> Context a -> m ()) -> b -> Recipe m a ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. m () -> Context a -> m ()
forall a b. a -> b -> a
const (m () -> Context a -> m ())
-> (b -> m ()) -> b -> Context a -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> m ()
forall (m :: * -> *). AchilleIO m => String -> m ()
AchilleIO.log (String -> m ()) -> (b -> String) -> b -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. b -> String
forall a. Show a => a -> String
show

-- | Recipe for running a shell command in a new process.
callCommand :: AchilleIO m
            => String -> Recipe m a ()
callCommand :: String -> Recipe m a ()
callCommand = (Context a -> m ()) -> Recipe m a ()
forall (m :: * -> *) a b.
Functor m =>
(Context a -> m b) -> Recipe m a b
nonCached ((Context a -> m ()) -> Recipe m a ())
-> (String -> Context a -> m ()) -> String -> Recipe m a ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. m () -> Context a -> m ()
forall a b. a -> b -> a
const (m () -> Context a -> m ())
-> (String -> m ()) -> String -> Context a -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> m ()
forall (m :: * -> *). AchilleIO m => String -> m ()
AchilleIO.callCommand

-- | Recipe for running a shell command in a new process.
--   The command is defined with an helper function which depends on an input filepath
--   and the same filepath with a modifier applied.
--
--   Examples:
--
--   > cp :: Recipe IO FilePath FilePath
--   > cp = runCommandWith id (\a b -> "cp " <> a <> " " <> b)
runCommandWith :: AchilleIO m
               => (FilePath -> FilePath)
               -> (FilePath -> FilePath -> String)
               -> Recipe m FilePath FilePath
runCommandWith :: (String -> String)
-> (String -> String -> String) -> Recipe m String String
runCommandWith mod :: String -> String
mod cmd :: String -> String -> String
cmd = (Context String -> m String) -> Recipe m String String
forall (m :: * -> *) a b.
Functor m =>
(Context a -> m b) -> Recipe m a b
nonCached \Context{..} ->
    let p' :: String
p' = String -> String
mod String
inputValue
    in String -> m ()
forall (m :: * -> *). AchilleIO m => String -> m ()
AchilleIO.callCommand (String -> String -> String
cmd (String
inputDir String -> String -> String
</> String
currentDir String -> String -> String
</> String
inputValue)
                                  (String
outputDir String -> String -> String
</> String
currentDir String -> String -> String
</> String
p'))
       m () -> m String -> m String
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> String -> m String
forall (f :: * -> *) a. Applicative f => a -> f a
pure String
p'

-- | Recipe that will retrieve the datetime contained in a filepath.
toTimestamped :: Monad m => FilePath -> Recipe m a (Timestamped FilePath)
toTimestamped :: String -> Recipe m a (Timestamped String)
toTimestamped = Timestamped String -> Recipe m a (Timestamped String)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Timestamped String -> Recipe m a (Timestamped String))
-> (String -> Timestamped String)
-> String
-> Recipe m a (Timestamped String)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Timestamped String
forall a. IsTimestamped a => a -> Timestamped a
timestamped