{-# LANGUAGE DeriveLift                 #-}
{-# LANGUAGE DerivingStrategies         #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE UndecidableInstances       #-}

module Aws.Lambda.Runtime.Common
  ( RunCallback
  , LambdaResult(..)
  , LambdaError(..)
  , LambdaOptions(..)
  , DispatcherOptions(..)
  , ApiGatewayDispatcherOptions(..)
  , DispatcherStrategy(..)
  , ToLambdaResponseBody(..)
  , unLambdaResponseBody
  , defaultDispatcherOptions
  ) where

import Aws.Lambda.Runtime.ApiGatewayInfo
import Aws.Lambda.Runtime.Context (Context)
import Aws.Lambda.Utilities
import Data.Aeson (FromJSON, ToJSON)
import qualified Data.ByteString.Lazy as Lazy
import Data.Text (Text)
import qualified Data.Text as Text
import GHC.Generics (Generic)
import Language.Haskell.TH.Syntax (Lift)

-- | API Gateway specific dispatcher options
newtype ApiGatewayDispatcherOptions = ApiGatewayDispatcherOptions
  { propagateImpureExceptions :: Bool
  -- ^ Should impure exceptions be propagated through the API Gateway interface
  } deriving (Lift)

-- | Options that the dispatcher generator expects
newtype DispatcherOptions = DispatcherOptions
  { apiGatewayDispatcherOptions :: ApiGatewayDispatcherOptions
  } deriving (Lift)

defaultDispatcherOptions :: DispatcherOptions
defaultDispatcherOptions =
  DispatcherOptions (ApiGatewayDispatcherOptions True)

-- | A strategy on how to generate the dispatcher functions
data DispatcherStrategy =
  UseWithAPIGateway |
  StandaloneLambda
  deriving (Lift)

-- | Callback that we pass to the dispatcher function
type RunCallback context =
  LambdaOptions context -> IO (Either LambdaError LambdaResult)

-- | Wrapper type for lambda response body
newtype LambdaResponseBody =
  LambdaResponseBody { unLambdaResponseBody :: Text }
  deriving newtype (ToJSON, FromJSON)

class ToLambdaResponseBody a where
  toStandaloneLambdaResponse :: a -> LambdaResponseBody

-- We need to special case String and Text to avoid unneeded encoding
-- which results in extra quotes put around plain text responses
instance {-# OVERLAPPING #-} ToLambdaResponseBody String where
  toStandaloneLambdaResponse = LambdaResponseBody . Text.pack

instance {-# OVERLAPPING #-} ToLambdaResponseBody Text where
  toStandaloneLambdaResponse = LambdaResponseBody

instance ToJSON a => ToLambdaResponseBody a where
  toStandaloneLambdaResponse = LambdaResponseBody . toJSONText

-- | Wrapper type for lambda execution results
data LambdaError =
  StandaloneLambdaError LambdaResponseBody |
  ApiGatewayLambdaError (ApiGatewayResponse ApiGatewayResponseBody)

-- | Wrapper type to handle the result of the user
data LambdaResult =
  StandaloneLambdaResult LambdaResponseBody |
  ApiGatewayResult (ApiGatewayResponse ApiGatewayResponseBody)

-- | Options that the generated main expects
data LambdaOptions context = LambdaOptions
  { eventObject     :: !Lazy.ByteString
  , functionHandler :: !String
  , executionUuid   :: !String
  , contextObject   :: !(Context context)
  } deriving (Generic)