{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE TemplateHaskell   #-}
import           Data.Monoid                    ((<>))
import           Data.String                    (fromString)
import qualified Data.Text                      as T
import           Network.HTTP.Client            (Manager, newManager)
import           Network.HTTP.Client.TLS        (tlsManagerSettings)
import           Network.HTTP.ReverseProxy      (ProxyDest (..), WaiProxyResponse (WPRProxyDest),
                                                 defaultOnExc, waiProxyTo)
import           Network.Wai                    (Application)
import           Network.Wai.Application.Static (defaultFileServerSettings,
                                                 staticApp, ssRedirectToIndex, ssAddTrailingSlash)
import           Network.Wai.Handler.Warp       (run)
import           Network.Wai.Middleware.Crowd
import           SimpleOptions
import           Web.ClientSession              (getKey)
import           Rdr2tls

data BasicSettings = BasicSettings
    { warpPort  :: Int
    , keyFile   :: FilePath
    , crowdRoot :: T.Text
    , age       :: Int
    , skipAuth  :: Bool
    , requireTls :: Bool
    }
    deriving Show

basicSettingsParser :: Parser BasicSettings
basicSettingsParser = BasicSettings
    <$> option auto
        ( long "listen-port"
       <> short 'p'
       <> metavar "LISTEN-PORT"
       <> help "Port to listen on for requests"
       <> value 3000 )
    <*> strOption
        ( long "key-file"
       <> short 'k'
       <> metavar "KEY-FILE"
       <> help "File containing the clientsession key"
       <> value "" )
    <*> (T.pack <$> strOption
        ( long "crowd-root"
       <> metavar "CROWD-ROOT"
       <> help "Base URL for the Crowd installation"
       <> value "" ))
    <*> option auto
        ( long "cookie-age"
       <> metavar "COOKIE-AGE"
       <> help "Number of seconds to keep auth cookie active"
       <> value 3600 )
    <*> switch
        ( long "skip-auth"
       <> help "Turn off Crowd authentication, useful for testing"
        )
    <*> switch
        ( long "require-tls"
       <> help "Require requests come in over a secure connection (determined via headers)"
        )

data Service = ServiceFiles FileServer
             | ServiceProxy ReverseProxy

data FileServer = FileServer
    { fsRoot :: FilePath
    , fsRedirectToIndex :: Bool
    , fsAddTrailingSlash :: Bool
    }

fileServerParser = FileServer
    <$> (argument str
         (metavar "ROOT-DIR" <> value "."))
    <*> switch
        ( long "redirect-to-index"
       <> help "Redirect to the actual index file, not leaving the URL containing the directory name"
        )
    <*> switch
        ( long "add-trailing-slash"
       <> help "Add a trailing slash to directory names"
        )

data ReverseProxy = ReverseProxy
    { rpHost :: String
    , rpPort :: Int
    }

reverseProxyParser :: Parser ReverseProxy
reverseProxyParser = ReverseProxy
    <$> (argument str (metavar "HOST"))
    <*> (argument auto (metavar "PORT"))

serviceToApp :: Manager -> Service -> IO Application
serviceToApp _ (ServiceFiles FileServer {..}) =
    return $ staticApp (defaultFileServerSettings $ fromString fsRoot)
        { ssRedirectToIndex = fsRedirectToIndex
        , ssAddTrailingSlash = fsAddTrailingSlash
        }
serviceToApp manager (ServiceProxy (ReverseProxy host port)) =
    return $ waiProxyTo
        (const $ return $ WPRProxyDest $ ProxyDest (fromString host) port)
        defaultOnExc
        manager

main :: IO ()
main = do
    (BasicSettings {..}, service) <- simpleOptions
        $(simpleVersion waiMiddlewareCrowdVersion)
        "wai-crowd - a Crowd-authenticated server"
        "Run a Crowd-authenticated file server or reverse proxy"
        basicSettingsParser $ do
            addCommand "file-server" "File server" ServiceFiles fileServerParser
            addCommand "reverse-proxy" "Reverse proxy" ServiceProxy reverseProxyParser
    manager <- newManager tlsManagerSettings
    let cs = (if null keyFile then id else setCrowdKey (getKey keyFile))
           $ (if T.null crowdRoot then id else setCrowdRoot crowdRoot)
           $ setCrowdManager (return manager)
           $ setCrowdAge age
           $ defaultCrowdSettings
    crowdMiddleware <- mkCrowdMiddleware cs
    app <- serviceToApp manager service
    putStrLn $ "Listening on port " ++ show warpPort
    run warpPort
        $ (if requireTls then rdr2tls else id)
          (if skipAuth then app else crowdMiddleware app)
