content: {-# LANGUAGE DeriveDataTypeable #-}
  -- To look up git hash
  {-# LANGUAGE TemplateHaskell #-}
  
  -- Necessary for cmdArgs
  {-# OPTIONS -Wno-partial-fields #-}
  
  module Main (main) where
  
  import Protolude (
    Applicative (pure),
    Eq ((==)),
    FilePath,
    IO,
    Int,
    Maybe (Just),
    Semigroup ((<>)),
    Show,
    Text,
    const,
    putText,
    repeat,
    show,
    ($),
    (&),
    (||),
   )
  import Protolude qualified as P
  
  import Data.Data (Data)
  import Data.Text qualified as T
  import Database.SQLite.Simple qualified as SS
  import GitHash (giDirty, giTag, tGitInfoCwd)
  import Network.HTTP.Client.MultipartFormData ()
  import Network.Wai (Middleware)
  import Network.Wai.Handler.Warp (
    defaultSettings,
    runSettings,
    setOnException,
    setPort,
   )
  import Network.Wai.Middleware.Cors (
    cors,
    corsMethods,
    corsRequestHeaders,
    simpleCorsResourcePolicy,
    simpleMethods,
   )
  import System.Console.CmdArgs as CmdArgs (
    Default (def),
    args,
    auto,
    cmdArgs,
    help,
    modes,
    program,
    summary,
    typ,
    (&=),
   )
  
  import AirGQL.ExternalAppContext (getExternalAppContext)
  import AirGQL.Utils (
    getGraphiQLVersion,
    getSqliteBinaryVersion,
    getSqliteEmbeddedVersion,
    withRetryConn,
   )
  import Server.Server (platformApp)
  
  
  data Cli
    = Help
    | Version
    | -- Start the AirGQL server
      -- serving the GraphQL endpoint for the specified SQLite database
      Serve
        { dbFilePath :: FilePath
        -- TODO: , readOnly :: Bool
        }
    deriving (Show, Data)
  
  
  cliHelp :: Cli
  cliHelp =
    Help
      &= auto
  
  
  cliVersion :: Cli
  cliVersion = Version
  
  
  cliServe :: Cli
  cliServe =
    Serve
      { dbFilePath = def &= typ "Path to database file" &= args
      -- TODO: , readOnly = def
      }
      &= help "Serve database via GraphQL"
  
  
  corsMiddleware :: Middleware
  corsMiddleware =
    let
      policy =
        simpleCorsResourcePolicy
          { corsRequestHeaders = ["Content-Type", "Authorization"]
          , corsMethods = "PUT" : simpleMethods
          }
    in
      cors (const $ Just policy)
  
  
  -- | Imitates output from `git describe --always --dirty`
  versionSlug :: Text
  versionSlug =
    T.pack $
      giTag $$tGitInfoCwd
        <> (if giDirty $$tGitInfoCwd then "-dirty" else "")
  
  
  main :: IO ()
  main = do
    let
      port :: Int = 4189
  
      separatorLine = "\n" <> T.concat (P.take 80 $ repeat "=")
      separatorLineThin = "\n" <> T.concat (P.take 80 $ repeat "-")
  
      runWarp =
        runSettings $
          defaultSettings
            & setPort port
            & setOnException
              ( \_ exception -> do
                  let exceptionText :: Text = show exception
                  if (exceptionText == "Thread killed by timeout manager")
                    || ( exceptionText
                          == "Warp: Client closed connection prematurely"
                       )
                    then pure ()
                    else do
                      putText exceptionText
              )
  
      buildBanner
        :: Text
        -> Text
        -> Text
        -> Text
        -> Text
      buildBanner
        sqliteEmbeddedVersion
        sqliteBinaryVersion
        graphiQLVersion
        baseUrl =
          separatorLine
            <> "\n\n"
            <> "AirGQL Server\n"
            <> separatorLineThin
            <> "\n\n"
            <> "Version:\t\t  "
            <> versionSlug
            <> "\n\
               \GraphQL URL:\t\t  "
            <> baseUrl
            <> "/graphql"
            <> "\n\
               \\n\
               \SQLite Embedded version:  "
            <> sqliteEmbeddedVersion
            <> "\n\
               \SQLite Binary version:\t  "
            <> sqliteBinaryVersion
            <> "\n\
               \GraphiQL version:\t  "
            <> graphiQLVersion
            <> "\n"
            <> separatorLine
            <> "\n"
  
    providedArgs <-
      cmdArgs $
        modes
          [ cliHelp
          , cliVersion
          , cliServe
          ]
          &= program "airgql"
          &= summary (T.unpack versionSlug)
          &= help "Automatic GraphQL API generation for SQLite databases"
  
    case providedArgs of
      Help ->
        putText "Run `airgql --help` for detailed usage instructions"
      ----------
      Version ->
        putText versionSlug
      ----------
      Serve{dbFilePath} -> do
        withRetryConn dbFilePath $ \conn -> do
          P.when (dbFilePath == "") $
            P.die "ERROR: No database file path was specified"
  
          let baseUrl :: Text = "http://localhost:" <> show port
          ctx <- getExternalAppContext baseUrl
          sqliteEmbeddedVersion <- getSqliteEmbeddedVersion conn
          sqliteBinaryVersion <- getSqliteBinaryVersion ctx
          graphiQLVersion <- getGraphiQLVersion
  
          putText $
            buildBanner
              sqliteEmbeddedVersion
              sqliteBinaryVersion
              graphiQLVersion
              baseUrl
  
          runWarp $ corsMiddleware $ platformApp ctx dbFilePath
  