{-# LANGUAGE OverloadedStrings #-}

module TestTypes ( TestRequest (..)
                 , TestResponse (..)
                 , TestRpcError (..)
                 , TestId (..)
                 , jsonRpcVersion
                 , versionKey) where

import Data.Aeson
import Data.Aeson.Types
import Data.Maybe (catMaybes)
import Data.Text (Text)
import Data.Attoparsec.Number (Number)
import Data.HashMap.Strict (size)
import Control.Applicative

data TestRpcError = TestRpcError { errCode :: Int
                                 , errMsg :: Text
                                 , errData :: Maybe Value}
                    deriving (Eq, Show)

instance FromJSON TestRpcError where
    parseJSON (Object err) = (TestRpcError <$>
                             err .: "code" <*>
                             err .: "message" <*>
                             err .:? "data") >>= checkKeys
        where checkKeys e = let checkSize s = failIf $ size err /= s
                            in pure e <* checkSize (case errData e of
                                                        Nothing -> 2
                                                        Just _ -> 3)
    parseJSON _ = empty

data TestRequest = TestRequest Text (Maybe (Either Object Array)) (Maybe TestId)

instance ToJSON TestRequest where
    toJSON (TestRequest name params i) = object pairs
        where pairs = catMaybes [Just $ "method" .= toJSON name, idPair, paramsPair]
              idPair = ("id" .=) <$> i
              paramsPair = either toPair toPair <$> params
                  where toPair v = "params" .= toJSON v

data TestResponse = TestResponse { rspId :: TestId
                                 , rspResult :: Either TestRpcError Value }
                    deriving (Eq, Show)

instance FromJSON TestResponse where
    parseJSON (Object r) = failIf (size r /= 3) *> checkVersion *>
                           (TestResponse <$>
                           r .: "id" <*>
                           ((Left <$> r .: "error") <|> (Right <$> r .: "result")))
        where checkVersion = r .: versionKey >>= failIf . (/= jsonRpcVersion)
    parseJSON _ = empty

failIf :: Bool -> Parser ()
failIf b = if b then empty else pure ()

data TestId = IdString Text | IdNumber Number | IdNull
              deriving (Eq, Show)

instance FromJSON TestId where
    parseJSON (String x) = return $ IdString x
    parseJSON (Number x) = return $ IdNumber x
    parseJSON Null = return IdNull
    parseJSON _ = empty

instance ToJSON TestId where
    toJSON i = case i of
                 IdString x -> toJSON x
                 IdNumber x -> toJSON x
                 IdNull -> Null

versionKey :: Text
versionKey = "jsonrpc"

jsonRpcVersion :: Text
jsonRpcVersion = "2.0"