{-# LANGUAGE OverloadedStrings #-}

module Language.ATS.Package.Build ( mkPkg
                                  , pkgToAction
                                  ) where

import           Control.Composition
import           Control.Concurrent.ParallelIO.Global
import           Control.Monad.IO.Class               (MonadIO)
import           Data.List                            (nub)
import           Data.Maybe                           (fromMaybe)
import           Data.Semigroup                       (Semigroup (..))
import qualified Data.Text.Lazy                       as TL
import           Development.Shake
import           Development.Shake.ATS
import           Development.Shake.Check
import           Development.Shake.FilePath
import           Development.Shake.Man
import           Dhall                                hiding (bool)
import           Language.ATS.Package.Dependency
import           Language.ATS.Package.Type
import           System.Directory                     (getCurrentDirectory)

-- TODO clean generated ATS
mkClean :: Rules ()
mkClean =
    "clean" ~> do
        removeFilesAfter "." ["//*.1","//*.c", "tags"]
        removeFilesAfter "target" ["//*"]
        removeFilesAfter ".atspkg" ["//*"]
        removeFilesAfter "ats-deps" ["//*"]

mkInstall :: Rules ()
mkInstall =
    "install" ~> do
        config <- getConfig
        bins <- fmap (TL.unpack . target) . bin <$> getConfig
        need bins
        home <- fromMaybe "" <$> getEnv "HOME"
        let binDest = ((home <> "/.local/bin/") <>) . takeBaseName <$> bins
        void $ zipWithM copyFile' bins binDest
        pa <- pandoc
        case man config of
            Just mt -> if not pa then pure () else do
                let mt' = manTarget mt
                    manDest = (home <> "/.local/share/man/man1/") <> mt'
                need [mt']
                copyFile' mt' manDest
            Nothing -> pure ()

mkManpage :: Rules ()
mkManpage = do
    c <- getConfig
    b <- pandoc
    case man c of
        Just _ -> bool (pure ()) manpages b
        _      -> pure ()

getConfig :: MonadIO m => m Pkg
getConfig = liftIO $ do
    d <- getCurrentDirectory
    input auto (TL.pack d <> "/atspkg.dhall")

manTarget :: Text -> FilePath
manTarget m = TL.unpack m -<.> "1"

mkTest :: Rules ()
mkTest =
    "test" ~> do
        config <- getConfig
        let tests = fmap (TL.unpack . target) . test $ config
        need tests
        mapM_ cmd_ tests

options :: ShakeOptions
options = shakeOptions { shakeFiles = ".atspkg"
                       , shakeThreads = 4
                       , shakeProgress = progressSimple
                       }

-- TODO verbosity & coloring?
mkPkg :: [String] -> IO ()
mkPkg rs = shake options $
    want rs >>
    mkTest >>
    mkClean >>
    mkManpage >>
    mkInstall >>
    (pkgToAction rs =<< getConfig)

asTuple :: TargetPair -> (Text, Text)
asTuple (TargetPair s t) = (s, t)

-- TODO infer dependencies on gc/atomic gc based on boolean flag.
pkgToAction :: [String] -> Pkg -> Rules ()
pkgToAction rs (Pkg bs ts mt v v' ds cds cc cf as cdir) = do
    unless (rs == ["clean"]) $
        let cdps = if any gc bs then libcAtomicOps : libcGC (Version [7,6,4]) : cds else cds in
        liftIO $ fetchDeps False ds cdps >> stopGlobalPool
    action (need ["atspkg.dhall"])
    mapM_ g (bs ++ ts)
    let bins = TL.unpack . target <$> bs
    pa <- pandoc
    when (null rs) $ do
        cDeps
        case mt of
            (Just m) -> want (bool bins (manTarget m : bins) pa)
            Nothing  -> want bins

    where g (Bin s t ls hs' atg gc') = atsBin (TL.unpack cc) (TL.unpack <$> cf) v v' gc' (TL.unpack <$> ls) (TL.unpack s) (TL.unpack <$> hs') (both TL.unpack . asTuple <$> atg) (TL.unpack t)
          cDeps = unless (null as) $ do
            let cedar = TL.unpack cdir
                atsSourceDirs = nub (takeDirectory . TL.unpack <$> as)
                targets = fmap (((cedar <> "/") <>) . (-<.> "c") . takeBaseName . TL.unpack) as
            want targets
            mapM_ (cgen v v') atsSourceDirs