content: {-# LANGUAGE DataKinds             #-}
  {-# LANGUAGE FlexibleContexts      #-}
  {-# LANGUAGE FlexibleInstances     #-}
  {-# LANGUAGE MultiParamTypeClasses #-}
  {-# LANGUAGE OverloadedStrings     #-}
  {-# LANGUAGE ScopedTypeVariables   #-}
  {-# LANGUAGE TypeOperators         #-}
  
  module Servant.PureScript (
    HasBridge
  , languageBridge
  , defaultBridge
  , defaultBridgeProxy
  , DefaultBridge
  , writeAPIModule
  , writeAPIModuleWithSettings
  , Settings (..)
  , apiModuleName
  , readerParams
  , standardImports
  , defaultSettings
  , addReaderParam
  , jsonParseUrlPiece
  , jsonToUrlPiece
  , jsonParseHeader
  , jsonToHeader
  ) where
  
  
  import           Control.Lens
  import           Control.Monad                 (when)
  import           Data.Aeson
  import           Data.Bifunctor
  import           Data.ByteString               (ByteString)
  import qualified Data.ByteString.Lazy          as BS
  import           Data.Monoid
  import           Data.Proxy
  import           Data.Text                     (Text)
  import qualified Data.Text                     as T
  import qualified Data.Text.Encoding            as T
  import qualified Data.Text.IO                          as T
  import           Language.PureScript.Bridge
  import           Network.HTTP.Types            (urlDecode, urlEncode)
  import           Servant.Foreign
  import           Servant.PureScript.CodeGen
  import           Servant.PureScript.Internal
  import qualified Servant.PureScript.Subscriber as SubGen
  import qualified Servant.PureScript.MakeRequests as MakeRequests
  import           System.Directory
  import           System.FilePath
  import           System.IO                     (IOMode (..), withFile)
  import           Text.PrettyPrint.Mainland     (hPutDocLn, Doc)
  
  -- | Standard entry point - just create a purescript module with default settings
  --   for accessing the servant API.
  writeAPIModule :: forall bridgeSelector api.
    ( HasForeign (PureScript bridgeSelector) PSType api
    , GenerateList PSType (Foreign PSType api)
    , HasBridge bridgeSelector
    ) => FilePath -> Proxy bridgeSelector -> Proxy api -> IO ()
  writeAPIModule = writeAPIModuleWithSettings defaultSettings
  
  writeAPIModuleWithSettings :: forall bridgeSelector api.
    ( HasForeign (PureScript bridgeSelector) PSType api
    , GenerateList PSType (Foreign PSType api)
    , HasBridge bridgeSelector
    ) => Settings -> FilePath -> Proxy bridgeSelector -> Proxy api -> IO ()
  writeAPIModuleWithSettings opts root pBr pAPI = do
      writeModule (opts ^. apiModuleName) genModule
      when (opts ^. generateSubscriberAPI) $ do
        writeModule (opts ^. apiModuleName <> ".Subscriber") SubGen.genModule
        writeModule (opts ^. apiModuleName <> ".MakeRequests") MakeRequests.genModule
      T.putStrLn "\nSuccessfully created your servant API purescript functions!"
      T.putStrLn "Please make sure you have purescript-servant-support version 5.0.0 or above installed:\n"
      T.putStrLn "  bower i --save purescript-servant-support\n"
    where
      apiList  = apiToList pAPI pBr
  
      writeModule :: Text -> (Settings -> [Req PSType] -> Doc) -> IO ()
      writeModule mName genModule' = let
          fileName = (joinPath . map T.unpack . T.splitOn "." $ mName) <> ".purs"
          mPath = root </> fileName
          mDir = takeDirectory mPath
          contents = genModule' opts apiList
        in do
          unlessM (doesDirectoryExist mDir) $ createDirectoryIfMissing True mDir
          withFile mPath WriteMode $ flip hPutDocLn contents
  
  
  -- | Use this function for implementing 'parseUrlPiece' in your FromHttpApiData instances
  --   in order to be compatible with the generated PS code.
  --
  -- >  
  -- > instance ToHttpApiData MyDataType where
  -- >   toUrlPiece = jsonToUrlPiece
  -- >   toHeader   = jsonToHeader
  -- >
  -- > instance FromHttpApiData MyDataType where
  -- >   parseUrlPiece = jsonParseUrlPiece
  -- >   parseHeader   = jsonParseHeader
  -- >
  --
  jsonParseUrlPiece :: FromJSON a => Text -> Either Text a
  jsonParseUrlPiece = jsonParseHeader . T.encodeUtf8
  
  -- | Use this function for implementing 'toUrlPiece' in your ToHttpApiData instances
  --   in order to be compatible with the generated PS code.
  jsonToUrlPiece :: ToJSON a => a -> Text
  jsonToUrlPiece = T.decodeUtf8 . jsonToHeader
  
  -- | Use this function for implementing 'parseHeader' in your FromHttpApiData instances
  --   in order to be compatible with the generated PS code.
  jsonParseHeader :: FromJSON a => ByteString -> Either Text a
  jsonParseHeader = first T.pack . eitherDecodeStrict
  
  -- | Use this function for implementing 'toHeader' in your ToHttpApiData instances
  --   in order to be compatible with the generated PS code.
  jsonToHeader :: ToJSON a => a -> ByteString
  jsonToHeader = BS.toStrict . encode
  