{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoImplicitPrelude #-}

module Omni.Cli
  ( Plan (..),
    main,
    Docopt.Docopt (..),
    Docopt.Arguments,
    Docopt.argument,
    Docopt.docopt,
    Docopt.getAllArgs,
    Docopt.getArg,
    Docopt.getArgWithDefault,
    Docopt.longOption,
    Docopt.shortOption,
    Docopt.command,
    has,
  )
where

import Alpha
import qualified Omni.Test as Test
import qualified System.Console.Docopt as Docopt
import qualified System.Environment as Environment

-- | Plan is the main data structure that describes a CLI program. It's not the
-- best name, but it works. This type is parameterized with `cfg` so you can
-- load configuration from the environment and pass it into your Plan.
data Plan cfg = Plan
  { -- | Usage info, shows when given --help
    help :: Docopt.Docopt,
    -- | The main function takes arguments and produces effects. Maybe it should
    -- also take `cfg` as an argument?
    move :: Docopt.Arguments -> IO (),
    -- | The test suite for the gram, invoked when 'test' is passed as the first
    -- argument to the program
    test :: Test.Tree,
    -- | Function for cleaning up any files or resources, presumably on
    -- shutdown. Can be just `pure` if you have nothing to tidy.
    tidy :: cfg -> IO ()
  }

-- | The entrypoint for CLI programs, use this in your own `main`.
main :: Plan cfg -> IO ()
main Plan {..} =
  Environment.getArgs
    /> Docopt.parseArgs help
    +> \case
      Left err -> panic <| show err
      Right args ->
        if args `has` Docopt.command "test"
          then Test.run test
          else
            if args `has` Docopt.longOption "help" || args `has` Docopt.shortOption 'h'
              then Docopt.exitWithUsage help
              else move args

has :: Docopt.Arguments -> Docopt.Option -> Bool
has = Docopt.isPresent