{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module HOwl ( Message
            , ConcreteMessage(..)
            , BaseMessage
            , Zephyr
            , asZephyr
            , ) where

import Network.Zephyr (ZAuth(..))

import qualified Data.Map as M
import Control.Monad

type FieldValue = String
type MessageId = Integer
type MessageType = String

-- I'm not sure whether we want typed fields or not.
-- data FieldValue = S String
--                 | D DateTime
--                 | I Integer

data ConcreteMessage = MkMessage {
      mid   :: Integer
    , body  :: String
    , attrs :: M.Map String FieldValue
    }

-- withField :: (Monad m) => String -> (FieldValue -> m ()) -> m ()

class Message m where
    msgId    :: m -> MessageId
    msgBody  :: m -> String
    msgAttrs :: m -> M.Map String FieldValue

(!) :: (Message m) => m -> String -> FieldValue
(!) = (M.!) . msgAttrs

getAttr :: (Message m) => m -> String -> Maybe FieldValue
getAttr = (flip M.lookup) . msgAttrs

checkAttrs :: (Message m) => m -> [String] -> Maybe ()
checkAttrs m = mapM_ (getAttr m)

instance Message ConcreteMessage where
    msgId    = mid
    msgBody  = body
    msgAttrs = attrs

class (Message m) => BaseMessage m where
    msgType   :: m -> MessageType
    msgSender :: m -> String
    msgRecip  :: m -> String

class (BaseMessage m) => Zephyr m where
    msgRealm    :: m -> String
    msgAuth     :: m -> ZAuth
    msgClass    :: m -> String
    msgZsig     :: m -> String
    msgInstance :: m -> String

newtype ZephyrImpl m = MkZ {unZ :: m}
    deriving (Message, BaseMessage)

instance (BaseMessage m) => Zephyr (ZephyrImpl m) where
    msgRealm    = (!"realm")
    msgAuth     = str2Auth . (!"zauth")
    msgClass    = (!"class")
    msgInstance = (!"instance")
    msgZsig     = (!"zsig")

str2Auth :: String -> ZAuth
str2Auth "yes"    = Authenticated
str2Auth "no"     = Unauthenticated
str2Auth "failed" = AuthenticationFailed

auth2Str :: ZAuth  -> String
auth2Str Authenticated        = "yes"
auth2Str Unauthenticated      = "no"
auth2Str AuthenticationFailed = "failed"

asZephyr :: (Message m) => m -> Maybe (ZephyrImpl m)
asZephyr m = do checkAttrs m $ ["realm", "class", "instance", "zsig"]
                auth <- getAttr m "zauth"
                guard $ auth == "yes" || auth == "no" || auth == "failed"
                return $ MkZ m
