{-# LANGUAGE DeriveDataTypeable, RecordWildCards, CPP #-}
-- | Command line version of wai-app-static, used for the warp-static server.
module WaiAppStatic.CmdLine
    ( runCommandLine
    , Args (..)
    ) where

import Network.Wai (Middleware)
import Network.Wai.Application.Static (staticApp, defaultFileServerSettings)
import Network.Wai.Handler.Warp
    ( runSettings, defaultSettings, setHost, setPort
    )
import Options.Applicative
import Text.Printf (printf)
import System.Directory (canonicalizePath)
import Control.Monad (unless)
import Network.Wai.Middleware.RequestLogger (logStdout)
import Network.Wai.Middleware.Gzip
import qualified Data.Map as Map
import qualified Data.ByteString.Char8 as S8
import Control.Arrow ((***))
import Data.Text (pack)
import Data.String (fromString)
import Network.Mime (defaultMimeMap, mimeByExt, defaultMimeType)
import WaiAppStatic.Types (ssIndices, toPiece, ssGetMimeType, fileName, fromPiece)
import Data.Maybe (mapMaybe)
import Control.Arrow (second)
import Data.Monoid ((<>))

data Args = Args
    { docroot :: FilePath
    , index :: [FilePath]
    , port :: Int
    , noindex :: Bool
    , quiet :: Bool
    , verbose :: Bool
    , mime :: [(String, String)]
    , host :: String
    }

#if MIN_VERSION_optparse_applicative(0, 10, 0)
option' :: Mod OptionFields Int -> Parser Int
option' = option auto
#else
option' = option
#endif

args :: Parser Args
args = Args
    <$> strOption
            ( long "docroot"
           <> short 'd'
           <> metavar "DOCROOT"
           <> value "."
           <> help "directory containing files to serve")
    <*> (defIndex <$> many (strOption
            ( long "index"
           <> short 'i'
           <> metavar "INDEX"
           <> help "index files to serve when a directory is required"
            )))
    <*> option'
            ( long "port"
           <> short 'p'
           <> metavar "PORT"
           <> value 3000)
    <*> switch
            ( long "noindex"
           <> short 'n')
    <*> switch
            ( long "quiet"
           <> short 'q')
    <*> switch
            ( long "verbose"
           <> short 'v')
    <*> many (toPair <$> strOption
            ( long "mime"
           <> short 'm'
           <> metavar "MIME"
           <> help "extra file extension/mime type mappings"))
    <*> strOption
            ( long "host"
           <> short 'h'
           <> metavar "HOST"
           <> value "*"
           <> help "interface to bind to, special values: *, *4, *6")
  where
    toPair = second (drop 1) . break (== '=')
    defIndex [] = ["index.html", "index.htm"]
    defIndex x = x

-- | Run with the given middleware and parsing options from the command line.
--
-- Since 2.0.1
runCommandLine :: (Args -> Middleware) -> IO ()
runCommandLine middleware = do
    args@Args {..} <- execParser $ info (helperOption <*> args) fullDesc
    let mime' = map (pack *** S8.pack) mime
    let mimeMap = Map.fromList mime' `Map.union` defaultMimeMap
    docroot' <- canonicalizePath docroot
    unless quiet $ printf "Serving directory %s on port %d with %s index files.\n" docroot' port (if noindex then "no" else show index)
    let middle = gzip def { gzipFiles = GzipCompress }
               . (if verbose then logStdout else id)
               . (middleware args)
    runSettings
        ( setPort port
        $ setHost (fromString host)
          defaultSettings
        )
        $ middle $ staticApp (defaultFileServerSettings $ fromString docroot)
        { ssIndices = if noindex then [] else mapMaybe (toPiece . pack) index
        , ssGetMimeType = return . mimeByExt mimeMap defaultMimeType . fromPiece . fileName
        }
    where
      helperOption :: Parser (a -> a)
      helperOption =
#if MIN_VERSION_optparse_applicative(0,16,0)
        abortOption (ShowHelpText Nothing) $
#else
        abortOption ShowHelpText $
#endif
        mconcat [long "help", help "Show this help text", hidden]