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

import Control.Lens ((^.))
import Data.Containers.ListUtils (nubOrd)
import Data.Maybe (maybeToList)
import System.FilePath.Posix (takeExtension, takeBaseName)
import Text.PrettyPrint.HughesPJ (Doc)
import Utils.Drasil (capitalize)

import Language.Drasil.Code.Imperative.Build.AST (asFragment, DocConfig(..),
  BuildConfig(BuildConfig), BuildDependencies(..), Ext(..), includeExt,
  NameOpts, nameOpts, packSep, Runnable(Runnable), BuildName(..), RunType(..))
import Language.Drasil.Code.Imperative.GOOL.ClassInterface (SoftwareDossierState,
  headers, sources, mainMod)

import Build.Drasil (Annotation, (+:+), genMake, makeS, MakeString, mkFile, mkRule,
  mkCheckedCommand, mkFreeVar, RuleTransformer(makeRule))
import Drasil.GOOL (FileData(..), ProgData(..))
import Drasil.Metadata (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 -> SoftwareDossierState
fileInfoState :: SoftwareDossierState,
  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 SoftwareDossierState
s ProgData
m Maybe DocConfig
d) = [Rule] -> (BuildConfig -> [Rule]) -> Maybe BuildConfig -> [Rule]
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 = MakeString
-> (BuildName -> MakeString) -> Maybe BuildName -> MakeString
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> MakeString
asFragment String
"") (SoftwareDossierState
-> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName SoftwareDossierState
s ProgData
m NameOpts
nameOpts) Maybe BuildName
onm
        addnm :: MakeString
addnm = MakeString
-> (BuildName -> MakeString) -> Maybe BuildName -> MakeString
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> MakeString
asFragment String
"") (SoftwareDossierState
-> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName SoftwareDossierState
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 ((FileData -> MakeString) -> [FileData] -> Dependencies
forall a b. (a -> b) -> [a] -> [b]
map (String -> MakeString
makeS (String -> MakeString)
-> (FileData -> String) -> FileData -> MakeString
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 (MakeString -> Command
mkCheckedCommand (MakeString -> Command)
-> (Dependencies -> MakeString) -> Dependencies -> Command
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (MakeString -> MakeString -> MakeString)
-> MakeString -> Dependencies -> MakeString
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr MakeString -> MakeString -> MakeString
(+:+) MakeString
forall a. Monoid a => a
mempty) ([Dependencies] -> [Command]) -> [Dependencies] -> [Command]
forall a b. (a -> b) -> a -> b
$
        Dependencies -> MakeString -> MakeString -> [Dependencies]
comp (BuildDependencies
-> SoftwareDossierState -> ProgData -> Dependencies
getCompilerInput BuildDependencies
bt SoftwareDossierState
s ProgData
m) MakeString
outnm MakeString
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 BuildName
nm NameOpts
no RunType
ty) -> [
    [String] -> MakeString -> Dependencies -> [Command] -> Rule
mkRule [] (String -> MakeString
makeS String
"run") [MakeString
buildTarget] [
      MakeString -> Command
mkCheckedCommand (MakeString -> Command) -> MakeString -> Command
forall a b. (a -> b) -> a -> b
$ MakeString -> RunType -> MakeString
buildRunTarget (SoftwareDossierState
-> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName SoftwareDossierState
s ProgData
m NameOpts
no BuildName
nm) RunType
ty MakeString -> MakeString -> MakeString
+:+
      String -> MakeString
mkFreeVar String
"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 Dependencies
dps [Command]
cmds) -> [
      [String] -> MakeString -> Dependencies -> [Command] -> Rule
mkRule [] (String -> MakeString
makeS String
"doc") (Dependencies
dps Dependencies -> Dependencies -> Dependencies
forall a. [a] -> [a] -> [a]
++ SoftwareDossierState -> Dependencies
getCommentedFiles SoftwareDossierState
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: " String -> String -> String
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 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= [] then String
"Project Purpose: " String -> String -> String
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 :: SoftwareDossierState -> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName :: SoftwareDossierState
-> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName SoftwareDossierState
s ProgData
_ NameOpts
_ BuildName
BMain = String -> MakeString
makeS (String -> MakeString) -> String -> MakeString
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 String
"Main module missing")
  String -> String
takeBaseName (SoftwareDossierState
s SoftwareDossierState
-> Getting (Maybe String) SoftwareDossierState (Maybe String)
-> Maybe String
forall s a. s -> Getting a s a -> a
^. Getting (Maybe String) SoftwareDossierState (Maybe String)
Lens' SoftwareDossierState (Maybe String)
mainMod)
renderBuildName SoftwareDossierState
_ ProgData
p NameOpts
_ BuildName
BPackName = String -> MakeString
makeS (ProgData -> String
progName ProgData
p)
renderBuildName SoftwareDossierState
s ProgData
p NameOpts
o (BPack BuildName
a) = SoftwareDossierState
-> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName SoftwareDossierState
s ProgData
p NameOpts
o BuildName
BPackName MakeString -> MakeString -> MakeString
forall a. Semigroup a => a -> a -> a
<>
  String -> MakeString
makeS (NameOpts -> String
packSep NameOpts
o) MakeString -> MakeString -> MakeString
forall a. Semigroup a => a -> a -> a
<> SoftwareDossierState
-> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName SoftwareDossierState
s ProgData
p NameOpts
o BuildName
a
renderBuildName SoftwareDossierState
s ProgData
p NameOpts
o (BWithExt BuildName
a Ext
e) = SoftwareDossierState
-> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName SoftwareDossierState
s ProgData
p NameOpts
o BuildName
a MakeString -> MakeString -> MakeString
forall a. Semigroup a => a -> a -> a
<>
  if NameOpts -> Bool
includeExt NameOpts
o then Ext -> String -> MakeString
renderExt Ext
e ([String] -> String
forall {a}. [a] -> a
takeSrc ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ SoftwareDossierState
s SoftwareDossierState
-> Getting [String] SoftwareDossierState [String] -> [String]
forall s a. s -> Getting a s a -> a
^. Getting [String] SoftwareDossierState [String]
Lens' SoftwareDossierState [String]
sources) else String -> MakeString
makeS String
""
  where takeSrc :: [a] -> a
takeSrc (a
src:[a]
_) = a
src
        takeSrc [] = String -> a
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 (String -> MakeString) -> String -> MakeString
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 -> SoftwareDossierState -> ProgData -> [MakeString]
getCompilerInput :: BuildDependencies
-> SoftwareDossierState -> ProgData -> Dependencies
getCompilerInput BuildDependencies
BcSource SoftwareDossierState
s ProgData
_ = (String -> MakeString) -> [String] -> Dependencies
forall a b. (a -> b) -> [a] -> [b]
map String -> MakeString
makeS ([String] -> Dependencies) -> [String] -> Dependencies
forall a b. (a -> b) -> a -> b
$ SoftwareDossierState
s SoftwareDossierState
-> Getting [String] SoftwareDossierState [String] -> [String]
forall s a. s -> Getting a s a -> a
^. Getting [String] SoftwareDossierState [String]
Lens' SoftwareDossierState [String]
sources
getCompilerInput (BcSingle BuildName
n) SoftwareDossierState
s ProgData
p = [SoftwareDossierState
-> ProgData -> NameOpts -> BuildName -> MakeString
renderBuildName SoftwareDossierState
s ProgData
p NameOpts
nameOpts BuildName
n]

-- | Helper that retrieves commented files.
getCommentedFiles :: SoftwareDossierState -> [MakeString]
getCommentedFiles :: SoftwareDossierState -> Dependencies
getCommentedFiles SoftwareDossierState
s = (String -> MakeString) -> [String] -> Dependencies
forall a b. (a -> b) -> [a] -> [b]
map String -> MakeString
makeS ([String] -> [String]
forall a. Ord a => [a] -> [a]
nubOrd (SoftwareDossierState
s SoftwareDossierState
-> Getting [String] SoftwareDossierState [String] -> [String]
forall s a. s -> Getting a s a -> a
^. Getting [String] SoftwareDossierState [String]
Lens' SoftwareDossierState [String]
headers [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++
  Maybe String -> [String]
forall a. Maybe a -> [a]
maybeToList (SoftwareDossierState
s SoftwareDossierState
-> Getting (Maybe String) SoftwareDossierState (Maybe String)
-> Maybe String
forall s a. s -> Getting a s a -> a
^. Getting (Maybe String) SoftwareDossierState (Maybe String)
Lens' SoftwareDossierState (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
"./" MakeString -> MakeString -> MakeString
forall a. Semigroup a => a -> a -> a
<> MakeString
fn
buildRunTarget MakeString
fn (Interpreter Dependencies
i) = (MakeString -> MakeString -> MakeString)
-> MakeString -> Dependencies -> MakeString
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr MakeString -> MakeString -> MakeString
(+:+) MakeString
forall a. Monoid a => a
mempty (Dependencies -> MakeString) -> Dependencies -> MakeString
forall a b. (a -> b) -> a -> b
$ Dependencies
i Dependencies -> Dependencies -> Dependencies
forall a. [a] -> [a] -> [a]
++ [MakeString
fn]

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