{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK hide #-}

module Network.Xmpp.IM.Message where

import Data.Default
import Data.Function
import Data.List
import Data.Text (Text)
import Data.XML.Pickle
import Data.XML.Types
import Network.Xmpp.Marshal
import Network.Xmpp.Types

data MessageBody = MessageBody { bodyLang    :: Maybe LangTag
                               , bodyContent :: Text
                               }

data MessageThread = MessageThread { threadID     :: Text
                                   , threadParent :: Maybe Text
                                   }

data MessageSubject = MessageSubject { subjectLang    :: Maybe LangTag
                                     , subjectContent :: Text
                                     }

-- | The instant message (IM) specific part of a message.
data InstantMessage = InstantMessage { imThread  :: Maybe MessageThread
                                     , imSubject :: [MessageSubject]
                                     , imBody    :: [MessageBody]
                                     }

-- | Empty instant message.
instantMessage :: InstantMessage
instantMessage = InstantMessage { imThread  = Nothing
                                , imSubject = []
                                , imBody    = []
                                }

instance Default InstantMessage where
    def = instantMessage

-- | Get the IM specific parts of a message. Returns 'Nothing' when the received
-- payload is not valid IM data.
getIM :: Message -> Maybe InstantMessage
getIM im = either (const Nothing) Just . unpickle xpIM $ messagePayload im

sanitizeIM :: InstantMessage -> InstantMessage
sanitizeIM im = im{imBody = nubBy ((==) `on` bodyLang) $ imBody im}

-- | Append IM data to a message. Additional IM bodies with the same Langtag are
-- discarded.
withIM :: Message -> InstantMessage -> Message
withIM m im = m{ messagePayload = messagePayload m
                                 ++ pickleTree xpIM (sanitizeIM im) }

imToElements :: InstantMessage -> [Element]
imToElements im = pickle xpIM (sanitizeIM im)

-- | Generate a simple message
simpleIM :: Jid -- ^ recipient
         -> Text -- ^ body
         -> Message
simpleIM to bd = withIM message{messageTo = Just to}
                       instantMessage{imBody = [MessageBody Nothing bd]}

-- | Generate an answer from a received message. The recepient is
-- taken from the original sender, the sender is set to 'Nothing',
-- message ID, language tag, message type as well as subject and
-- thread are inherited.
--
-- Additional IM bodies with the same Langtag are discarded.
answerIM :: [MessageBody] -> Message -> Maybe Message
answerIM bd msg = case getIM msg of
    Nothing -> Nothing
    Just im -> Just $ flip withIM (im{imBody = bd}) $
        message { messageID      = messageID msg
                , messageFrom    = Nothing
                , messageTo      = messageFrom msg
                , messageLangTag = messageLangTag msg
                , messageType    = messageType msg
                }

--------------------------
-- Picklers --------------
--------------------------
xpIM :: PU [Element] InstantMessage
xpIM = xpWrap (\(t, s, b) -> InstantMessage t s b)
              (\(InstantMessage t s b) -> (t, s, b))
       . xpClean
       $ xp3Tuple
           xpMessageThread
           xpMessageSubject
           xpMessageBody


xpMessageSubject :: PU [Element] [MessageSubject]
xpMessageSubject = xpUnliftElems .
                   xpWrap (map $ \(l, s) -> MessageSubject l s)
                          (map $ \(MessageSubject l s) -> (l,s))
                   $ xpElems "{jabber:client}subject" xpLangTag $ xpContent xpId

xpMessageBody :: PU [Element] [MessageBody]
xpMessageBody = xpUnliftElems .
                xpWrap (map $ \(l, s) ->  MessageBody l s)
                       (map $ \(MessageBody l s) -> (l,s))
                   $ xpElems "{jabber:client}body" xpLangTag $ xpContent xpId

xpMessageThread :: PU [Element] (Maybe MessageThread)
xpMessageThread = xpUnliftElems
                  . xpOption
                  . xpWrap (\(t, p) ->  MessageThread p t)
                          (\(MessageThread p t) -> (t,p))
                   $ xpElem "{jabber:client}thread"
                      (xpAttrImplied "parent" xpId)
                      (xpContent xpId)