module Language.Drasil.Code.Imperative.Build.Import (
  makeBuild
) where

import Language.Drasil.Code.Imperative.Build.AST (asFragment, DocConfig(..),
  BuildConfig(BuildConfig), BuildDependencies(..), Ext(..), includeExt,
  NameOpts, nameOpts, packSep, Runnable(Runnable), BuildName(..), RunType(..))

import GOOL.Drasil (FileData(..), ProgData(..), GOOLState(..), headers, sources,
  mainMod)

import Build.Drasil (Annotation, (+:+), genMake, makeS, MakeString, mkFile, mkRule,
  mkCheckedCommand, mkFreeVar, RuleTransformer(makeRule))

import Control.Lens ((^.))
import Data.Maybe (maybeToList)
import Data.List (nub)
import System.FilePath.Posix (takeExtension, takeBaseName)
import Text.PrettyPrint.HughesPJ (Doc)
import Utils.Drasil (capitalize)
import Metadata.Drasil.DrasilMetaCall (watermark)

-- | Holds all the needed information to run a program.
data CodeHarness = Ch {
  CodeHarness -> Maybe BuildConfig
buildConfig :: Maybe BuildConfig,
  CodeHarness -> Maybe Runnable
runnable :: Maybe Runnable,
  CodeHarness -> GOOLState
goolState :: GOOLState,
  CodeHarness -> ProgData
progData :: ProgData,
  CodeHarness -> Maybe DocConfig
docConfig :: Maybe DocConfig}

-- | Transforms information in 'CodeHarness' into a list of Makefile rules.
instance RuleTransformer CodeHarness where
  makeRule :: CodeHarness -> [Rule]
makeRule (Ch Maybe BuildConfig
b Maybe Runnable
r GOOLState
s ProgData
m Maybe DocConfig
d) = forall b a. b -> (a -> b) -> Maybe a -> b
maybe [[String] -> MakeString -> Dependencies -> [Command] -> Rule
mkRule (ProgData -> [String]
openingComments ProgData
m) MakeString
buildTarget [] []]
    (\(BuildConfig Dependencies -> MakeString -> MakeString -> [Dependencies]
comp Maybe BuildName
onm Maybe BuildName
anm BuildDependencies
bt) ->
    let outnm :: MakeString
outnm = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> MakeString
asFragment String
"") (GOOLState -> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName GOOLState
s ProgData
m NameOpts
nameOpts) Maybe BuildName
onm
        addnm :: MakeString
addnm = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> MakeString
asFragment String
"") (GOOLState -> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName GOOLState
s ProgData
m NameOpts
nameOpts) Maybe BuildName
anm
    in [
    [String] -> MakeString -> Dependencies -> [Command] -> Rule
mkRule (ProgData -> [String]
openingComments ProgData
m) MakeString
buildTarget [MakeString
outnm] [],
    [String] -> MakeString -> Dependencies -> [Command] -> Rule
mkFile [] MakeString
outnm (forall a b. (a -> b) -> [a] -> [b]
map (String -> MakeString
makeS forall b c a. (b -> c) -> (a -> b) -> a -> c
. FileData -> String
filePath) (ProgData -> [FileData]
progMods ProgData
m)) forall a b. (a -> b) -> a -> b
$
      forall a b. (a -> b) -> [a] -> [b]
map (MakeString -> Command
mkCheckedCommand forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr MakeString -> MakeString -> MakeString
(+:+) forall a. Monoid a => a
mempty) forall a b. (a -> b) -> a -> b
$
        Dependencies -> MakeString -> MakeString -> [Dependencies]
comp (BuildDependencies -> GOOLState -> ProgData -> Dependencies
getCompilerInput BuildDependencies
bt GOOLState
s ProgData
m) MakeString
outnm MakeString
addnm
    ]) Maybe BuildConfig
b forall a. [a] -> [a] -> [a]
++ forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (\(Runnable BuildName
nm NameOpts
no RunType
ty) -> [
    [String] -> MakeString -> Dependencies -> [Command] -> Rule
mkRule [] (String -> MakeString
makeS String
"run") [MakeString
buildTarget] [
      MakeString -> Command
mkCheckedCommand forall a b. (a -> b) -> a -> b
$ MakeString -> RunType -> MakeString
buildRunTarget (GOOLState -> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName GOOLState
s ProgData
m NameOpts
no BuildName
nm) RunType
ty MakeString -> MakeString -> MakeString
+:+
      String -> MakeString
mkFreeVar String
"RUNARGS"
      ]
    ]) Maybe Runnable
r forall a. [a] -> [a] -> [a]
++ forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (\(DocConfig Dependencies
dps [Command]
cmds) -> [
      [String] -> MakeString -> Dependencies -> [Command] -> Rule
mkRule [] (String -> MakeString
makeS String
"doc") (Dependencies
dps forall a. [a] -> [a] -> [a]
++ GOOLState -> Dependencies
getCommentedFiles GOOLState
s) [Command]
cmds
    ]) Maybe DocConfig
d where
      buildTarget :: MakeString
buildTarget = String -> MakeString
makeS String
"build"

openingComments :: ProgData -> Annotation
openingComments :: ProgData -> [String]
openingComments ProgData
m = [String
watermark,String
"Project Name: " forall a. [a] -> [a] -> [a]
++ ProgData -> String
progName ProgData
m, ProgData -> String
progPurpAdd ProgData
m]

-- | Helper that renders project purpose into a string if there is one.
progPurpAdd :: ProgData -> String
progPurpAdd :: ProgData -> String
progPurpAdd ProgData
m = if ProgData -> String
progPurp ProgData
m forall a. Eq a => a -> a -> Bool
/= [] then String
"Project Purpose: " forall a. [a] -> [a] -> [a]
++
                  String -> String
capitalize (ProgData -> String
progPurp ProgData
m)
                else []

-- | Helper that renders information into a MakeString. Dependent on the 'BuildName' criteria.
renderBuildName :: GOOLState -> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName :: GOOLState -> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName GOOLState
s ProgData
_ NameOpts
_ BuildName
BMain = String -> MakeString
makeS forall a b. (a -> b) -> a -> b
$ forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall a. HasCallStack => String -> a
error String
"Main module missing")
  String -> String
takeBaseName (GOOLState
s forall s a. s -> Getting a s a -> a
^. Lens' GOOLState (Maybe String)
mainMod)
renderBuildName GOOLState
_ ProgData
p NameOpts
_ BuildName
BPackName = String -> MakeString
makeS (ProgData -> String
progName ProgData
p)
renderBuildName GOOLState
s ProgData
p NameOpts
o (BPack BuildName
a) = GOOLState -> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName GOOLState
s ProgData
p NameOpts
o BuildName
BPackName forall a. Semigroup a => a -> a -> a
<>
  String -> MakeString
makeS (NameOpts -> String
packSep NameOpts
o) forall a. Semigroup a => a -> a -> a
<> GOOLState -> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName GOOLState
s ProgData
p NameOpts
o BuildName
a
renderBuildName GOOLState
s ProgData
p NameOpts
o (BWithExt BuildName
a Ext
e) = GOOLState -> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName GOOLState
s ProgData
p NameOpts
o BuildName
a forall a. Semigroup a => a -> a -> a
<>
  if NameOpts -> Bool
includeExt NameOpts
o then Ext -> String -> MakeString
renderExt Ext
e (forall {a}. [a] -> a
takeSrc forall a b. (a -> b) -> a -> b
$ GOOLState
s forall s a. s -> Getting a s a -> a
^. Lens' GOOLState [String]
sources) else String -> MakeString
makeS String
""
  where takeSrc :: [a] -> a
takeSrc (a
src:[a]
_) = a
src
        takeSrc [] = forall a. HasCallStack => String -> a
error String
"Generated code has no source files"

-- | Helper that renders an extension onto a 'FilePath'.
renderExt :: Ext -> FilePath -> MakeString
renderExt :: Ext -> String -> MakeString
renderExt Ext
CodeExt String
f = String -> MakeString
makeS forall a b. (a -> b) -> a -> b
$ String -> String
takeExtension String
f
renderExt (OtherExt MakeString
e) String
_ = MakeString
e

-- | Helper that records the compiler input information.
getCompilerInput :: BuildDependencies -> GOOLState -> ProgData -> [MakeString]
getCompilerInput :: BuildDependencies -> GOOLState -> ProgData -> Dependencies
getCompilerInput BuildDependencies
BcSource GOOLState
s ProgData
_ = forall a b. (a -> b) -> [a] -> [b]
map String -> MakeString
makeS forall a b. (a -> b) -> a -> b
$ GOOLState
s forall s a. s -> Getting a s a -> a
^. Lens' GOOLState [String]
sources
getCompilerInput (BcSingle BuildName
n) GOOLState
s ProgData
p = [GOOLState -> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName GOOLState
s ProgData
p NameOpts
nameOpts BuildName
n]

-- | Helper that retrieves commented files.
getCommentedFiles :: GOOLState -> [MakeString]
getCommentedFiles :: GOOLState -> Dependencies
getCommentedFiles GOOLState
s = forall a b. (a -> b) -> [a] -> [b]
map String -> MakeString
makeS (forall a. Eq a => [a] -> [a]
nub (GOOLState
s forall s a. s -> Getting a s a -> a
^. Lens' GOOLState [String]
headers forall a. [a] -> [a] -> [a]
++
  forall a. Maybe a -> [a]
maybeToList (GOOLState
s forall s a. s -> Getting a s a -> a
^. Lens' GOOLState (Maybe String)
mainMod)))

-- | Helper that builds and runs a target.
buildRunTarget :: MakeString -> RunType -> MakeString
buildRunTarget :: MakeString -> RunType -> MakeString
buildRunTarget MakeString
fn RunType
Standalone = String -> MakeString
makeS String
"./" forall a. Semigroup a => a -> a -> a
<> MakeString
fn
buildRunTarget MakeString
fn (Interpreter Dependencies
i) = forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr MakeString -> MakeString -> MakeString
(+:+) forall a. Monoid a => a
mempty forall a b. (a -> b) -> a -> b
$ Dependencies
i forall a. [a] -> [a] -> [a]
++ [MakeString
fn]

-- | Creates a Makefile.
makeBuild :: Maybe DocConfig -> Maybe BuildConfig -> Maybe Runnable ->
  GOOLState -> ProgData -> Doc
makeBuild :: Maybe DocConfig
-> Maybe BuildConfig
-> Maybe Runnable
-> GOOLState
-> ProgData
-> Doc
makeBuild Maybe DocConfig
d Maybe BuildConfig
b Maybe Runnable
r GOOLState
s ProgData
p = forall c. RuleTransformer c => [c] -> Doc
genMake [Ch {
  buildConfig :: Maybe BuildConfig
buildConfig = Maybe BuildConfig
b,
  runnable :: Maybe Runnable
runnable = Maybe Runnable
r,
  goolState :: GOOLState
goolState = GOOLState
s,
  progData :: ProgData
progData = ProgData
p,
  docConfig :: Maybe DocConfig
docConfig = Maybe DocConfig
d}]