-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2022 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module Wire.ConversationSubsystem.MLS.IncomingMessage
  ( IncomingMessage (..),
    IncomingMessageContent (..),
    IncomingPublicMessageContent (..),
    IncomingBundle (..),
    SenderIdentity (..),
    mkIncomingMessage,
    incomingMessageAuthenticatedContent,
    mkIncomingBundle,
  )
where

import GHC.Records
import Imports
import Wire.API.MLS.AuthenticatedContent
import Wire.API.MLS.Commit
import Wire.API.MLS.CommitBundle
import Wire.API.MLS.Credential
import Wire.API.MLS.Epoch
import Wire.API.MLS.Group
import Wire.API.MLS.GroupInfo
import Wire.API.MLS.LeafNode
import Wire.API.MLS.Message
import Wire.API.MLS.Serialisation
import Wire.API.MLS.Welcome

data IncomingMessage = IncomingMessage
  { epoch :: Epoch,
    groupId :: GroupId,
    content :: IncomingMessageContent,
    rawMessage :: RawMLS Message
  }

instance HasField "sender" IncomingMessage (Maybe Sender) where
  getField msg = case msg.content of
    IncomingMessageContentPublic pub -> Just pub.sender
    _ -> Nothing

data IncomingMessageContent
  = IncomingMessageContentPublic IncomingPublicMessageContent
  | IncomingMessageContentPrivate

data IncomingPublicMessageContent = IncomingPublicMessageContent
  { sender :: Sender,
    content :: FramedContentData,
    -- for verification
    framedContent :: RawMLS FramedContent,
    authData :: RawMLS FramedContentAuthData
  }

data IncomingBundle = IncomingBundle
  { epoch :: Epoch,
    groupId :: GroupId,
    sender :: Sender,
    commit :: RawMLS Commit,
    rawMessage :: RawMLS Message,
    appMessage :: Maybe IncomingMessage,
    welcome :: Maybe (RawMLS Welcome),
    groupInfo :: RawMLS GroupInfo,
    serialized :: ByteString
  }

-- | Client information about the sender of a message.
data SenderIdentity = SenderIdentity
  { -- | Sender client.
    client :: ClientIdentity,
    -- | Index of the client in the ratchet tree, if available.
    index :: Maybe LeafIndex
  }
  deriving (Show)

mkIncomingMessage :: RawMLS Message -> Maybe IncomingMessage
mkIncomingMessage msg = case msg.value.content of
  MessagePublic pmsg ->
    Just
      IncomingMessage
        { epoch = pmsg.content.value.epoch,
          groupId = pmsg.content.value.groupId,
          content =
            IncomingMessageContentPublic
              IncomingPublicMessageContent
                { sender = pmsg.content.value.sender,
                  content = pmsg.content.value.content,
                  framedContent = pmsg.content,
                  authData = pmsg.authData
                },
          rawMessage = msg
        }
  MessagePrivate pmsg
    | pmsg.value.tag == FramedContentApplicationDataTag ->
        Just
          IncomingMessage
            { epoch = pmsg.value.epoch,
              groupId = pmsg.value.groupId,
              content = IncomingMessageContentPrivate,
              rawMessage = msg
            }
  _ -> Nothing

incomingMessageAuthenticatedContent :: IncomingPublicMessageContent -> AuthenticatedContent
incomingMessageAuthenticatedContent pmsg =
  AuthenticatedContent
    { wireFormat = WireFormatPublicTag,
      content = pmsg.framedContent,
      authData = pmsg.authData
    }

mkIncomingBundle :: RawMLS CommitBundle -> Maybe IncomingBundle
mkIncomingBundle bundle = do
  imsg <- mkIncomingMessage bundle.value.commitMsg
  content <- case imsg.content of
    IncomingMessageContentPublic c -> pure c
    _ -> Nothing
  commit <- case content.content of
    FramedContentCommit c -> pure c
    _ -> Nothing
  pure
    IncomingBundle
      { epoch = imsg.epoch,
        groupId = imsg.groupId,
        sender = content.sender,
        commit = commit,
        rawMessage = bundle.value.commitMsg,
        appMessage = bundle.value.appMessage >>= mkIncomingMessage,
        welcome = bundle.value.welcome,
        groupInfo = bundle.value.groupInfo,
        serialized = bundle.raw
      }
