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 ((+:+), 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)

-- | 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 b :: Maybe BuildConfig
b r :: Maybe Runnable
r s :: GOOLState
s m :: ProgData
m d :: Maybe DocConfig
d) = [Rule] -> (BuildConfig -> [Rule]) -> Maybe BuildConfig -> [Rule]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [Target -> Dependencies -> [Command] -> Rule
mkRule Target
buildTarget [] []] 
    (\(BuildConfig comp :: Dependencies -> Target -> Target -> [Dependencies]
comp onm :: Maybe BuildName
onm anm :: Maybe BuildName
anm bt :: BuildDependencies
bt) -> 
    let outnm :: Target
outnm = Target -> (BuildName -> Target) -> Maybe BuildName -> Target
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> Target
asFragment "") (GOOLState -> ProgData -> NameOpts -> BuildName -> Target
renderBuildName GOOLState
s ProgData
m NameOpts
nameOpts) Maybe BuildName
onm
        addnm :: Target
addnm = Target -> (BuildName -> Target) -> Maybe BuildName -> Target
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> Target
asFragment "") (GOOLState -> ProgData -> NameOpts -> BuildName -> Target
renderBuildName GOOLState
s ProgData
m NameOpts
nameOpts) Maybe BuildName
anm
    in [
    Target -> Dependencies -> [Command] -> Rule
mkRule Target
buildTarget [Target
outnm] [],
    Target -> Dependencies -> [Command] -> Rule
mkFile Target
outnm ((FileData -> Target) -> [FileData] -> Dependencies
forall a b. (a -> b) -> [a] -> [b]
map (String -> Target
makeS (String -> Target) -> (FileData -> String) -> FileData -> Target
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FileData -> String
filePath) (ProgData -> [FileData]
progMods ProgData
m)) ([Command] -> Rule) -> [Command] -> Rule
forall a b. (a -> b) -> a -> b
$
      (Dependencies -> Command) -> [Dependencies] -> [Command]
forall a b. (a -> b) -> [a] -> [b]
map (Target -> Command
mkCheckedCommand (Target -> Command)
-> (Dependencies -> Target) -> Dependencies -> Command
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Target -> Target -> Target) -> Target -> Dependencies -> Target
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr Target -> Target -> Target
(+:+) Target
forall a. Monoid a => a
mempty) ([Dependencies] -> [Command]) -> [Dependencies] -> [Command]
forall a b. (a -> b) -> a -> b
$ 
        Dependencies -> Target -> Target -> [Dependencies]
comp (BuildDependencies -> GOOLState -> ProgData -> Dependencies
getCompilerInput BuildDependencies
bt GOOLState
s ProgData
m) Target
outnm Target
addnm
    ]) Maybe BuildConfig
b [Rule] -> [Rule] -> [Rule]
forall a. [a] -> [a] -> [a]
++ [Rule] -> (Runnable -> [Rule]) -> Maybe Runnable -> [Rule]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (\(Runnable nm :: BuildName
nm no :: NameOpts
no ty :: RunType
ty) -> [
    Target -> Dependencies -> [Command] -> Rule
mkRule (String -> Target
makeS "run") [Target
buildTarget] [
      Target -> Command
mkCheckedCommand (Target -> Command) -> Target -> Command
forall a b. (a -> b) -> a -> b
$ Target -> RunType -> Target
buildRunTarget (GOOLState -> ProgData -> NameOpts -> BuildName -> Target
renderBuildName GOOLState
s ProgData
m NameOpts
no BuildName
nm) RunType
ty Target -> Target -> Target
+:+ String -> Target
mkFreeVar "RUNARGS"
      ]
    ]) Maybe Runnable
r [Rule] -> [Rule] -> [Rule]
forall a. [a] -> [a] -> [a]
++ [Rule] -> (DocConfig -> [Rule]) -> Maybe DocConfig -> [Rule]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (\(DocConfig dps :: Dependencies
dps cmds :: [Command]
cmds) -> [
      Target -> Dependencies -> [Command] -> Rule
mkRule (String -> Target
makeS "doc") (Dependencies
dps Dependencies -> Dependencies -> Dependencies
forall a. [a] -> [a] -> [a]
++ GOOLState -> Dependencies
getCommentedFiles GOOLState
s) [Command]
cmds
    ]) Maybe DocConfig
d where
      buildTarget :: Target
buildTarget = String -> Target
makeS "build"

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

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

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

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

-- | Helper that builds and runs a target.
buildRunTarget :: MakeString -> RunType -> MakeString
buildRunTarget :: Target -> RunType -> Target
buildRunTarget fn :: Target
fn Standalone = String -> Target
makeS "./" Target -> Target -> Target
forall a. Semigroup a => a -> a -> a
<> Target
fn
buildRunTarget fn :: Target
fn (Interpreter i :: Dependencies
i) = (Target -> Target -> Target) -> Target -> Dependencies -> Target
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr Target -> Target -> Target
(+:+) Target
forall a. Monoid a => a
mempty (Dependencies -> Target) -> Dependencies -> Target
forall a b. (a -> b) -> a -> b
$ Dependencies
i Dependencies -> Dependencies -> Dependencies
forall a. [a] -> [a] -> [a]
++ [Target
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 d :: Maybe DocConfig
d b :: Maybe BuildConfig
b r :: Maybe Runnable
r s :: GOOLState
s p :: ProgData
p = [CodeHarness] -> Doc
forall c. RuleTransformer c => [c] -> Doc
genMake [Ch :: Maybe BuildConfig
-> Maybe Runnable
-> GOOLState
-> ProgData
-> Maybe DocConfig
-> CodeHarness
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}]