{-# LANGUAGE DataKinds     #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeOperators #-}
module Main (main) where

import           Prelude ()
import           Prelude.Compat

import           Control.Concurrent
                 (threadDelay)
import           Control.Monad.IO.Class
                 (MonadIO (..))
import qualified Data.ByteString          as BS
import           Data.Maybe
                 (fromMaybe)
import           Network.HTTP.Client
                 (defaultManagerSettings, newManager)
import           Network.Wai
                 (Application)
import           System.Environment
                 (getArgs, lookupEnv)
import           System.IO
                 (IOMode (..))
import           Text.Read
                 (readMaybe)

import qualified Pipes                    as P
import           Pipes.ByteString         as PBS
import qualified Pipes.Prelude            as P
import           Pipes.Safe
                 (SafeT)
import qualified Pipes.Safe.Prelude       as P
import           Servant
import           Servant.Client.Streaming
import           Servant.Pipes ()

import qualified Network.Wai.Handler.Warp as Warp

type FastAPI = "get" :> Capture "num" Int :> StreamGet NewlineFraming JSON (P.Producer Int IO ())

-- TODO: Change IO to something with MonadError ServantUnrenderError
type API = FastAPI
    :<|> "slow" :> Capture "num" Int :> StreamGet NewlineFraming JSON (P.Producer Int IO ())
    -- monad can be SafeT IO too.
    :<|> "readme" :> StreamGet NoFraming OctetStream (P.Producer BS.ByteString (SafeT IO) ())
    -- we can have streaming request body
    :<|> "proxy"
        :> StreamBody NoFraming OctetStream (P.Producer BS.ByteString IO ())
        :> StreamPost NoFraming OctetStream (P.Producer BS.ByteString IO ())

api :: Proxy API
api = Proxy

server :: Server API
server = fast :<|> slow :<|> readme :<|> proxy
  where
    fast n = liftIO $ do
        putStrLn ("/get/" ++ show n)
        return $ fastPipe n

    slow n = liftIO $ do
        putStrLn ("/slow/" ++ show n)
        return $ slowPipe n

    readme = liftIO $ do
        putStrLn "/readme"
        return $ P.withFile "README.md" ReadMode PBS.fromHandle

    proxy c = liftIO $ do
        putStrLn "/proxy"
        return c

    -- for some reason unfold leaks?
    fastPipe m
        | m < 0     = return ()
        | otherwise = P.yield m >> fastPipe (m - 1)

    slowPipe m = fastPipe m P.>-> P.mapM (<$ threadDelay 1000000)

app :: Application
app = serve api server

cli :: Client ClientM FastAPI
cli :<|> _ :<|> _ :<|> _ = client api

main :: IO ()
main = do
    args <- getArgs
    case args of
        ("server":_) -> do
            putStrLn "Starting servant-pipes:example at http://localhost:8000"
            port <- fromMaybe 8000 . (>>= readMaybe) <$> lookupEnv "PORT"
            Warp.run port app
        ("client":ns:_) -> do
            n <- maybe (fail $ "not a number: " ++ ns) pure $ readMaybe ns
            mgr <- newManager defaultManagerSettings
            burl <- parseBaseUrl "http://localhost:8000/"
            withClientM (cli n) (mkClientEnv mgr burl) $ \me -> case me of
                Left err -> print err
                Right p  -> do
                    x <- P.fold (\c _ -> c + 1) (0 :: Int) id p
                    print x
        _ -> do
            putStrLn "Try:"
            putStrLn "cabal new-run servant-pipes:example server"
            putStrLn "cabal new-run servant-pipes:example client 10"
            putStrLn "time curl -H 'Accept: application/json' localhost:8000/slow/5"