{- |
Module      :  Servant.Static.TH

Copyright   :  Dennis Gosnell 2017
License     :  BSD3

Maintainer  :  Dennis Gosnell (cdep.illabout@gmail.com)
Stability   :  experimental
Portability :  unknown

This module provides the 'createApiAndServerDecs' function.  At compile time,
it will read all the files under a specified directory, embed their contents,
create a Servant \"API\" type synonym representing their directory layout, and
create a 'ServerT' function for serving their contents statically.

Let's assume that we have a directory called @\"dir\"@ in the root of our
Haskell web API that looks like this:

@
  $ tree dir\/
  dir\/
  ├── js
  │   └── test.js
  └── hello.html
@

Here's the contents of @\"hello.html\"@ and @\"js\/test.js\"@:

@
  $ cat dir\/index.html
  \<p\>Hello World\<\/p\>
  $ cat dir\/js\/test.js
  console.log(\"hello world\");
@

The 'createApiAndServerDecs' function can be used like the following:

@
  \{\-\# LANGUAGE DataKinds \#\-\}
  \{\-\# LANGUAGE TemplateHaskell \#\-\}

  import "Data.Proxy" ('Data.Proxy.Proxy'('Data.Proxy.Proxy'))
  import "Network.Wai" ('Network.Wai.Application')
  import "Network.Wai.Handler.Warp" ('Network.Wai.Handler.Warp.run')
  import "Servant.Server" ('Servant.Server.serve')
  import "Servant.Static.TH" ('createApiAndServerDecs')

  $(createApiAndServerDecs \"FrontEndApi\" \"frontEndServer\" \"dir\")

  app :: 'Network.Wai.Application'
  app = 'Servant.Server.serve' ('Data.Proxy.Proxy' :: 'Data.Proxy.Proxy' FrontEndApi) frontEndServer

  main :: IO ()
  main = 'Network.Wai.Handler.Warp.run' 8080 app
@

'createApiAndServerDecs' will expand to something like the following at
compile time:

@
  type FrontEndAPI =
         \"js\" 'Servant.API.:>' \"test.js\" 'Servant.API.:>' 'Servant.API.Get' \'['JS'] 'Data.ByteString.ByteString'
    ':<|>' \"index.html\" 'Servant.API.:>' 'Servant.API.Get' \'['HTML'] 'Html'

  frontEndServer :: 'Applicative' m => 'Servant.Server.ServerT' FrontEndAPI m
  frontEndServer =
         'pure' "console.log(\\"hello world\\");"
    ':<|>' 'pure' "\<p\>Hello World\<\/p\>"
@

If this WAI application is running, it is possible to use @curl@ to access
the server:

@
  $ curl localhost:8080\/hello.html
  \<p\>Hello World\<\/p\>
  $ curl localhost:8080\/js\/test.js
  console.log(\"hello world\");
@

This 'createApiAndServerDecs' function is convenient to use when you want to
make a Servant application easy to deploy.  All the static frontend files are
bundled into the Haskell binary at compile-time, so all you need to do is
deploy the Haskell binary.  This works well for low-traffic websites like
prototypes and internal applications.

This shouldn't be used for high-traffic websites.  Instead, you should serve
your static files from something like Apache, nginx, or a CDN.

Note:

If you are creating a @cabal@ package that needs to work with @cabal-install@,
the @\"dir\"@ you want to serve needs to be a relative path inside your
project root, and all contained files need to be listed in your
@.cabal@-file under the @extra-source-files@ field so that they are
included and available at compile-time.
-}

module Servant.Static.TH
  ( -- * Create API
    createApiType
  , createApiDec
    -- * Create Server
  , createServerExp
  , createServerDec
    -- * Create Both API and Server
  , createApiAndServerDecs
    -- * MIME Types

    -- | The following types are the MIME types supported by servant-static-th.
    -- If you need additional MIME types supported, feel free to create an
    -- <https://github.com/cdepillabout/servant-static-th/issues issue> or
    -- <https://github.com/cdepillabout/servant-static-th/pulls PR>.
  , CSS
  , EOT
  , GEXF
  , GIF
  , HTML
  , Html
  , ICO
  , JPEG
  , JS
  , JSON
  , PNG
  , SVG
  , TTF
  , TXT
  , WOFF
  , WOFF2
  , XML
    -- * Easy-To-Use Names and Paths

    -- | The functions in this section pick defaults for the template
    -- directory, api name, and the server function name. This makes it easy to
    -- use for quick-and-dirty code.

    -- ** Paths and Names
  , frontEndTemplateDir
  , frontEndApiName
  , frontEndServerName
    -- ** API
  , createApiFrontEndType
  , createApiFrontEndDec
    -- ** Server
  , createServerFrontEndExp
  , createServerFrontEndDec
    -- ** Server and API
  , createApiAndServerFrontEndDecs
  ) where

import Language.Haskell.TH (Dec, Exp, Q, Type, mkName, tySynD)
import System.FilePath ((</>))
import Servant.HTML.Blaze (HTML)
import Text.Blaze.Html (Html)

import Servant.Static.TH.Internal
  ( CSS
  , EOT
  , GEXF
  , GIF
  , ICO
  , JPEG
  , JSON
  , JS
  , PNG
  , SVG
  , TTF
  , TXT
  , WOFF
  , WOFF2
  , XML
  , createApiDec
  , createApiType
  , createServerDec
  , createServerExp
  )

------------------------------------
-- Hard-coded Frontend file paths --
------------------------------------

-- | This is the directory @\"frontend\/dist\"@.
frontEndTemplateDir :: FilePath
frontEndTemplateDir = "frontend" </> "dist"

-- | This is the 'String' @\"FrontEnd\"@.
frontEndApiName :: String
frontEndApiName = "FrontEnd"

-- | This is the 'String' @\"frontEndServer\"@.
frontEndServerName :: String
frontEndServerName = "frontEndServer"

---------
-- Api --
---------

-- | This is the same as @'createApiType' 'frontEndTemplateDir'@.
createApiFrontEndType :: Q Type
createApiFrontEndType = createApiType frontEndTemplateDir

-- | This is the same as
-- @'createApiDec' 'frontEndApiName' 'frontEndTemplateDir'@.
createApiFrontEndDec :: Q [Dec]
createApiFrontEndDec =
  pure <$> tySynD (mkName "FrontEnd") [] createApiFrontEndType

------------
-- Server --
------------

-- | This is the same as @'createServerExp' 'frontEndTemplateDir'@.
createServerFrontEndExp :: Q Exp
createServerFrontEndExp = createServerExp frontEndTemplateDir

-- | This is the same as
-- @'createServerDec' 'frontEndApiName' 'frontEndServerName' 'frontEndTemplateDir'@.
createServerFrontEndDec :: Q [Dec]
createServerFrontEndDec =
  createServerDec frontEndApiName frontEndServerName frontEndTemplateDir

--------------------
-- Server and API --
--------------------

-- | This is a combination of 'createApiDec' and 'createServerDec'.  This
-- function is the one most users should use.
--
-- Given the following code:
--
-- @
--   \{\-\# LANGUAGE DataKinds \#\-\}
--   \{\-\# LANGUAGE TemplateHaskell \#\-\}
--
--   $('createApiAndServerDecs' \"FrontAPI\" \"frontServer\" \"dir\")
-- @
--
-- You can think of it as expanding to the following:
--
-- @
--   $('createApiDec' \"FrontAPI\" \"dir\")
--
--   $('createServerDec' \"FrontAPI\" \"frontServer\" \"dir\")
-- @
createApiAndServerDecs
  :: String   -- ^ name of the api type synonym
  -> String   -- ^ name of the server function
  -> FilePath -- ^ directory name to read files from
  -> Q [Dec]
createApiAndServerDecs apiName serverName templateDir =
  let apiDecs = createApiDec apiName templateDir
      serverDecs = createServerDec apiName serverName templateDir
  in mappend <$> apiDecs <*> serverDecs

-- | This is the same as
-- @'createApiAndServerDecs' 'frontEndApiName' 'frontEndServerName' 'frontEndTemplateDir'@.
createApiAndServerFrontEndDecs :: Q [Dec]
createApiAndServerFrontEndDecs =
  createApiAndServerDecs frontEndApiName frontEndServerName frontEndTemplateDir