module Buffet
  ( main
  ) where

import qualified Buffet.Facade as Facade
import qualified Control.Applicative as Applicative
import qualified Data.Foldable as Foldable
import qualified Options.Applicative as Options
import qualified Options.Applicative.Help.Pretty as Pretty
import Prelude
  ( FilePath
  , IO
  , Maybe(Just)
  , String
  , ($)
  , (.)
  , (<$>)
  , (<*>)
  , (>>=)
  , concatMap
  , fmap
  , mconcat
  , words
  )

main :: IO ()
main = Options.execParser root >>= Facade.get

root :: Options.ParserInfo Facade.Command
root =
  Options.info parser $ Options.progDesc "Assembles many Dockerfiles in one."
  where
    parser = versionOption <*> Options.helper <*> raw
    raw =
      Options.hsubparser $
      mconcat
        [ Options.command "build" build
        , Options.command "document" document
        , Options.command "parse" parse
        , Options.command "test" test
        ]

versionOption :: Options.Parser (a -> a)
versionOption =
  Options.infoOption "Buffet 0.2.0" $
  mconcat
    [Options.long "version", Options.helpDoc $ Just versionHelp, Options.hidden]
  where
    versionHelp =
      paragraph
        [ "Prints the program name with version on stdout"
        , "and exits successfully"
        , "according to the GNU coding standards."
        ]

build :: Options.ParserInfo Facade.Command
build =
  Options.info parser $
  Options.progDesc "Assembles a Dockerfile from a list of Dockerfiles."
  where
    parser = fmap Facade.Build $ Facade.BuildArguments <$> menuOperand

menuOperand :: Options.Parser FilePath
menuOperand =
  Options.argument Options.str $
  mconcat [Options.metavar "menu_path", Options.helpDoc $ Just menuHelp]

menuHelp :: Pretty.Doc
menuHelp =
  paragraphs
    [ paragraph
        [ "File or folder that"
        , "lists the input Dockerfiles and"
        , "configures the output Dockerfile."
        ]
    , paragraph
        [ "If the path is a file,"
        , "then it is a JSON or YAML file"
        , "with the following entries:"
        ]
    , list
        [ paragraphs
            [ paragraph ["`copy_dummy_source_path`:"]
            , paragraph
                [ "File or folder"
                , "that is added as a source path"
                , "to `COPY` instructions"
                , "because at least one source must exist."
                , "Default: \"/var/empty\""
                ]
            ]
        , paragraphs
            [ paragraph ["`option_to_dish`:"]
            , paragraph
                [ "Map from `ARG` variable name to Dockerfile path."
                , "These Dockerfiles are assembled,"
                , "with their instructions conditioned"
                , "on the respective variable being nonempty."
                , "Default: {}"
                ]
            ]
        ]
    , paragraph
        [ "If the path is a folder,"
        , "then the behavior is as if a file is given"
        , "with above defaults except for the following."
        , "`option_to_dish` is a map based on the path subfolders:"
        , "for each subfolder `x`, the map has an entry"
        , "with key `x` and value `x/Dockerfile`."
        ]
    ]

paragraphs :: [Pretty.Doc] -> Pretty.Doc
paragraphs = Pretty.vsep

paragraph :: [String] -> Pretty.Doc
paragraph =
  Foldable.foldr (Pretty.</>) Pretty.line . fmap Pretty.text . concatMap words

list :: [Pretty.Doc] -> Pretty.Doc
list =
  paragraphs .
  fmap (\element -> Pretty.char '-' Pretty.<+> Pretty.align element)

document :: Options.ParserInfo Facade.Command
document = Options.info parser $ Options.progDesc "Generates documentation."
  where
    parser =
      fmap Facade.Document $
      Facade.DocumentArguments <$> templateOption <*> menuOperand
    templateOption :: Options.Parser (Maybe FilePath)
    templateOption =
      Applicative.optional
        (Options.strOption $
         mconcat
           [ Options.long "template"
           , Options.metavar "mustache_file"
           , Options.helpDoc $ Just templateHelp
           ])
    templateHelp =
      paragraph
        [ "Mustache template to render (see https://mustache.github.io)."
        , "To print the template context instead, omit this option."
        ]

parse :: Options.ParserInfo Facade.Command
parse =
  Options.info parser $
  Options.progDesc "Prints an intermediate representation for API usage."
  where
    parser = fmap Facade.Parse $ Facade.ParseArguments <$> menuOperand

test :: Options.ParserInfo Facade.Command
test = Options.info parser $ Options.progDesc "Performs health checks."
  where
    parser =
      fmap Facade.Test $
      Facade.TestArguments <$> argumentsOption <*> menuOperand
    argumentsOption :: Options.Parser (Maybe FilePath)
    argumentsOption =
      Applicative.optional
        (Options.strOption $
         mconcat
           [ Options.long "arguments"
           , Options.metavar "yaml_file"
           , Options.helpDoc $ Just argumentsHelp
           ])
    argumentsHelp =
      paragraph
        [ "JSON or YAML file with a map that"
        , "sets the Docker build arguments."
        , "Health checks are only done for dishes in this map."
        ]