package im.zego;

import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import im.zego.zim.entity.*;
import im.zego.zim.enums.*;
import java.lang.Integer;
import java.lang.System;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;

public class RNZIMConverter {

    public static ZIMPushConfig convertZIMPushConfig(ReadableMap config) {
        if (config.hasKey("pushConfig")) {
            ReadableMap pushMap = config.getMap("pushConfig");
            ZIMPushConfig pushConfig = new ZIMPushConfig();
            pushConfig.title = pushMap.hasKey("title") ? pushMap.getString("title") : "";
            pushConfig.content = pushMap.hasKey("content") ? pushMap.getString("content") : "";
            pushConfig.payload = pushMap.hasKey("payload") ? pushMap.getString("payload") : "";
            pushConfig.resourcesID = pushMap.hasKey("resourcesID") ? pushMap.getString("resourcesID") : "";
            pushConfig.badgeIncrement = pushMap.hasKey("badgeIncrement") ? pushMap.getInt("badgeIncrement") : 0;
            pushConfig.enableBadge = pushMap.hasKey("enableBadge") ? pushMap.getBoolean("enableBadge") : false;
            if (pushMap.getMap("voIPConfig") != null) {
                ReadableMap map = pushMap.getMap("voIPConfig");
                ZIMVoIPConfig voIPConfig = new ZIMVoIPConfig();
                voIPConfig.iOSVoIPHandleType = ZIMCXHandleType.getZIMCXHandleType(map.getInt("iOSVoIPHandleType"));
                voIPConfig.iOSVoIPHandleValue =
                    map.hasKey("iOSVoIPHandleValue") ? map.getString("iOSVoIPHandleValue") : "";
                voIPConfig.iOSVoIPHasVideo = map.hasKey("iOSVoIPHasVideo") ? map.getBoolean("iOSVoIPHasVideo") : false;
                pushConfig.voIPConfig = voIPConfig;
            }
            return pushConfig;
        }
        return null;
    }

    public static ReadableMap convertMessageReceivedInfo(ZIMMessageReceivedInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putBoolean("isOfflineMessage", info.isOfflineMessage);

        return map;
    }

    public static ReadableMap convertUserInfo(ZIMUserInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putString("userID", info.userID);
        map.putString("userName", info.userName);
        map.putString("userAvatarUrl", info.userAvatarUrl);

        return map;
    }

    public static ReadableArray convertUserInfoList(ArrayList<ZIMUserInfo> infos) {
        if (infos == null) {
            return null;
        }

        WritableArray memberArray = Arguments.createArray();
        for (ZIMUserInfo userInfo : infos) {
            memberArray.pushMap(convertUserInfo(userInfo));
        }

        return memberArray;
    }

    public static ReadableMap convertUserFullInfo(ZIMUserFullInfo userInfo) {
        if (userInfo == null) {
            return null;
        }

        WritableMap userMap = Arguments.createMap();
        userMap.putMap("baseInfo", convertUserInfo(userInfo.baseInfo));
        userMap.putString("userAvatarUrl", userInfo.baseInfo.userAvatarUrl);
        userMap.putString("extendedData", userInfo.extendedData);

        return userMap;
    }

    public static ReadableMap convertUserRule(ZIMUserOfflinePushRule info) {
        if (info == null) {
            return null;
        }

        WritableMap ruleMap = Arguments.createMap();

        WritableArray onlineArray = Arguments.createArray();
        for (int item : info.onlinePlatforms) {
            onlineArray.pushInt(item);
        }
        ruleMap.putArray("onlinePlatforms", onlineArray);

        WritableArray offlineArray = Arguments.createArray();
        for (int item : info.notToReceiveOfflinePushPlatforms) {
            offlineArray.pushInt(item);
        }
        ruleMap.putArray("notToReceiveOfflinePushPlatforms", offlineArray);

        WritableMap map = Arguments.createMap();
        map.putMap("offlinePushRule", ruleMap);
        return map;
    }

    public static ReadableMap convertUserStatus(ZIMUserStatus info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putString("userID", info.userID);
        map.putString("customStatus", info.customStatus);
        map.putInt("onlineStatus", info.onlineStatus.value());
        map.putDouble("lastUpdateTime", info.lastUpdateTime);
        map.putDouble("lastOnlineStatusUpdateTime", info.lastOnlineStatusUpdateTime);
        map.putDouble("lastCustomStatusUpdateTime", info.lastCustomStatusUpdateTime);

        WritableArray onlineArray = Arguments.createArray();
        for (ZIMPlatformType item : info.onlinePlatforms) {
            onlineArray.pushInt(item.value());
        }
        map.putArray("onlinePlatforms", onlineArray);

        return map;
    }

    public static ReadableArray convertErrorUserInfoList(ArrayList<ZIMErrorUserInfo> infos) {
        if (infos == null) {
            return null;
        }

        WritableArray errorUserArray = Arguments.createArray();
        for (ZIMErrorUserInfo user : infos) {
            WritableMap errorUserMap = Arguments.createMap();
            errorUserMap.putString("userID", user.userID);
            errorUserMap.putInt("reason", user.reason);
            errorUserArray.pushMap(errorUserMap);
        }

        return errorUserArray;
    }

    public static ReadableMap convertRoomMemberInfo(ZIMRoomMemberInfo userInfo) {
        if (userInfo == null) {
            return null;
        }

        WritableMap userMap = Arguments.createMap();
        userMap.putString("userID", userInfo.userID);
        userMap.putString("userName", userInfo.userName);
        userMap.putString("userAvatarUrl", userInfo.userAvatarUrl);

        return userMap;
    }

    public static ReadableMap convertConversation(ZIMConversation conversation) {
        if (conversation == null) {
            return null;
        }

        int type = conversation.type.value();

        WritableMap convMap = Arguments.createMap();
        convMap.putString("conversationID", conversation.conversationID);
        convMap.putString("conversationName", conversation.conversationName);
        convMap.putString("conversationAlias", conversation.conversationAlias);
        convMap.putString("conversationAvatarUrl", conversation.conversationAvatarUrl);
        convMap.putInt("type", type);
        convMap.putInt("unreadMessageCount", conversation.unreadMessageCount);
        convMap.putDouble("orderKey", conversation.orderKey);
        convMap.putInt("notificationStatus", conversation.notificationStatus.value());
        convMap.putBoolean("isPinned", conversation.isPinned);
        convMap.putString("draft", conversation.draft);
        if (conversation.lastMessage != null) {
            convMap.putMap("lastMessage", convertMessage(conversation.lastMessage));
        } else {
            convMap.putNull("lastMessage");
        }
        if (conversation.mentionedInfoList != null) {
            WritableArray infosArray = Arguments.createArray();
            for (ZIMMessageMentionedInfo info : conversation.mentionedInfoList) {
                WritableMap map = Arguments.createMap();
                map.putInt("type", info.type.value());
                map.putString("fromUserID", info.fromUserID);
                map.putString("messageID", String.valueOf(info.messageID));
                map.putDouble("messageSeq", info.messageSeq);
                infosArray.pushMap(map);
            }
            convMap.putArray("mentionedInfoList", infosArray);
        }
        if (conversation.marks != null) {
            WritableArray infosArray = Arguments.createArray();
            for (int info : conversation.marks) {
                infosArray.pushInt(info);
            }
            convMap.putArray("marks", infosArray);
        }

        if (type == 2 && conversation instanceof ZIMGroupConversation) {
            convMap.putBoolean("isDisabled", ((ZIMGroupConversation) conversation).isDisabled);
            convMap.putDouble("mutedExpiredTime", ((ZIMGroupConversation) conversation).mutedExpiredTime);
        }

        return convMap;
    }

    public static ZIMConversation convertConversationFromJsToJava(ReadableMap map) {
        if (map == null) {
            return null;
        }

        ZIMConversation conversation = new ZIMConversation();
        conversation.conversationID = map.hasKey("conversationID") ? map.getString("conversationID") : "";
        conversation.conversationName = map.hasKey("conversationName") ? map.getString("conversationName") : "";
        conversation.conversationAlias = map.hasKey("conversationAlias") ? map.getString("conversationAlias") : "";
        conversation.conversationAvatarUrl =
            map.hasKey("conversationAvatarUrl") ? map.getString("conversationAvatarUrl") : "";
        conversation.unreadMessageCount = map.hasKey("unreadMessageCount") ? map.getInt("unreadMessageCount") : 0;
        long orderKey = map.hasKey("orderKey") ? Double.valueOf(map.getDouble("orderKey")).longValue() : 0;
        conversation.orderKey = orderKey;
        conversation.type = ZIMConversationType.getZIMConversationType(map.getInt("type"));
        conversation.notificationStatus =
            ZIMConversationNotificationStatus.getZIMConversationNotificationStatus(map.getInt("notificationStatus"));
        conversation.isPinned = map.hasKey("isPinned") ? map.getBoolean("isPinned") : false;
        conversation.draft = map.hasKey("draft") ? map.getString("draft") : "";
        conversation.lastMessage = null;

        return conversation;
    }

    public static ReadableArray convertMessageReceiptInfoList(ArrayList<ZIMMessageReceiptInfo> infos) {
        if (infos == null) {
            return null;
        }

        WritableArray infosArray = Arguments.createArray();
        for (ZIMMessageReceiptInfo info : infos) {
            WritableMap map = Arguments.createMap();
            map.putString("conversationID", info.conversationID);
            map.putInt("conversationType", info.conversationType.value());
            map.putString("messageID", String.valueOf(info.messageID));
            map.putInt("status", info.status.value());
            map.putInt("readMemberCount", info.readMemberCount);
            map.putInt("unreadMemberCount", info.unreadMemberCount);
            map.putBoolean("isSelfOperated", info.isSelfOperated);

            infosArray.pushMap(map);
        }

        return infosArray;
    }

    public static ReadableMap convertMessageReaction(ZIMMessageReaction info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putString("conversationID", info.conversationID);
        map.putInt("conversationType", info.conversationType.value());
        map.putString("messageID", String.valueOf(info.messageID));
        map.putString("reactionType", info.reactionType);
        map.putInt("totalCount", info.totalCount);
        map.putBoolean("isSelfIncluded", info.isSelfIncluded);

        WritableArray userArray = Arguments.createArray();
        for (ZIMMessageReactionUserInfo user : info.userList) {
            WritableMap userMap = Arguments.createMap();
            userMap.putString("userID", user.userID);
            userArray.pushMap(userMap);
        }
        map.putArray("userList", userArray);

        return map;
    }

    public static ReadableMap convertMessageSentStatusChangeInfo(ZIMMessageSentStatusChangeInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putString("reason", info.reason);
        map.putInt("status", info.status.value());
        map.putMap("message", convertMessage(info.message));

        return map;
    }

    // Used for incoming base message
    public static ZIMMessage convertMessageFromJsToJava(ReadableMap messageMap) {
        if (messageMap == null) {
            return null;
        }

        ZIMMessage message = null;
        ZIMMessageType type = ZIMMessageType.getZIMMessageType(messageMap.getInt("type"));
        switch (type) {
            case TEXT:
                String textContent = messageMap.hasKey("message") ? messageMap.getString("message") : "";
                message = new ZIMTextMessage(textContent);
                break;
            case COMMAND:
                String byteContent = messageMap.hasKey("message") ? messageMap.getString("message") : "";
                String[] split = byteContent.split(",");
                int size = split.length;
                byte[] bytes = new byte[size];
                for (int i = 0; i < size; i++) {
                    int b = Integer.parseInt(split[i]);
                    bytes[i] = (byte) (b < 128 ? b : b - 256);
                }

                message = new ZIMCommandMessage(bytes);
                break;
            case BARRAGE:
                String barrageContent = messageMap.hasKey("message") ? messageMap.getString("message") : "";
                message = new ZIMBarrageMessage(barrageContent);
                break;
            case REVOKE:
                message = new ZIMRevokeMessage();
                try {
                    Field revokeTypeField = ZIMRevokeMessage.class.getDeclaredField("revokeType");
                    revokeTypeField.setAccessible(true);
                    int revokeType = messageMap.hasKey("revokeType") ? messageMap.getInt("revokeType") : -1;
                    revokeTypeField.set(message, ZIMRevokeType.getZIMRevokeType(revokeType));
                    revokeTypeField.setAccessible(false);

                    Field originalMessageTypeField = ZIMRevokeMessage.class.getDeclaredField("originalMessageType");
                    originalMessageTypeField.setAccessible(true);
                    int originalMessageType = messageMap.hasKey("originalMessageType")
                        ? messageMap.getInt("originalMessageType")
                        : 0;
                    originalMessageTypeField.set(message, ZIMMessageType.getZIMMessageType(originalMessageType));
                    originalMessageTypeField.setAccessible(false);

                    Field revokeStatusField = ZIMRevokeMessage.class.getDeclaredField("revokeStatus");
                    revokeStatusField.setAccessible(true);
                    int revokeStatus = messageMap.hasKey("revokeStatus") ? messageMap.getInt("revokeStatus") : -1;
                    revokeStatusField.set(message, ZIMMessageRevokeStatus.getZIMMessageRevokeStatus(revokeStatus));
                    revokeStatusField.setAccessible(false);

                    Field revokeTimestampField = ZIMRevokeMessage.class.getDeclaredField("revokeTimestamp");
                    revokeTimestampField.setAccessible(true);
                    long revokeTimestamp = messageMap.hasKey("revokeTimestamp")
                        ? Double.valueOf(messageMap.getDouble("revokeTimestamp")).longValue()
                        : 0;
                    revokeTimestampField.set(message, revokeTimestamp);
                    revokeTimestampField.setAccessible(false);

                    Field operatedUserIDField = ZIMRevokeMessage.class.getDeclaredField("operatedUserID");
                    operatedUserIDField.setAccessible(true);
                    String operatedUserID = messageMap.hasKey("operatedUserID")
                        ? messageMap.getString("operatedUserID")
                        : "";
                    operatedUserIDField.set(message, operatedUserID);
                    operatedUserIDField.setAccessible(false);

                    Field revokeExtendedDataField = ZIMRevokeMessage.class.getDeclaredField("revokeExtendedData");
                    revokeExtendedDataField.setAccessible(true);
                    String revokeExtendedData = messageMap.hasKey("revokeExtendedData")
                        ? messageMap.getString("revokeExtendedData")
                        : "";
                    revokeExtendedDataField.set(message, revokeExtendedData);
                    revokeExtendedDataField.setAccessible(false);

                    Field originalTextMessageContentField =
                        ZIMRevokeMessage.class.getDeclaredField("originalTextMessageContent");
                    originalTextMessageContentField.setAccessible(true);
                    String originalTextMessageContent = messageMap.hasKey("originalTextMessageContent")
                        ? messageMap.getString("originalTextMessageContent")
                        : "";
                    originalTextMessageContentField.set(message, originalTextMessageContent);
                    originalTextMessageContentField.setAccessible(false);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
            case TIPS:
                message = new ZIMTipsMessage();
                ((ZIMTipsMessage) message).event =
                    ZIMTipsMessageEvent.getZIMTipsMessageEvent(
                        messageMap.hasKey("event") ? messageMap.getInt("event") : 0
                    );
                ((ZIMTipsMessage) message).operatedUser =
                    messageMap.hasKey("operatedUser")
                        ? convertUserOrGroupMemberFromJsToJava(messageMap.getMap("operatedUser"))
                        : null;
                ArrayList<ZIMUserInfo> targetUserList = new ArrayList<>();
                if (messageMap.getArray("targetUserList") != null) {
                    for (Object infoObj : messageMap.getArray("targetUserList").toArrayList()) {
                        targetUserList.add(convertUserOrGroupMemberFromJsToJava((HashMap<String, Object>) infoObj));
                    }
                }
                ((ZIMTipsMessage) message).targetUserList = targetUserList;
                ((ZIMTipsMessage) message).changeInfo =
                    messageMap.hasKey("changeInfo")
                        ? convertTipsMessageChangeInfoFromJsToJava(messageMap.getMap("changeInfo"))
                        : null;
                break;
            case CUSTOM:
                String customContent = messageMap.hasKey("message") ? messageMap.getString("message") : "";
                int subType = messageMap.hasKey("subType") ? messageMap.getInt("subType") : 0;
                message = new ZIMCustomMessage(customContent, subType);
                ((ZIMCustomMessage) message).searchedContent =
                    messageMap.hasKey("searchedContent") ? messageMap.getString("searchedContent") : "";
                break;
            case COMBINE:
                String combineTitle = messageMap.hasKey("title") ? messageMap.getString("title") : "";
                String combineSummary = messageMap.hasKey("summary") ? messageMap.getString("summary") : "";
                ArrayList<ZIMMessage> combineMessageList = new ArrayList<>();
                if (messageMap.getArray("messageList") != null) {
                    for (Object messageObj : messageMap.getArray("messageList").toArrayList()) {
                        combineMessageList.add(convertMessageFromJsToJava((HashMap<String, Object>) messageObj));
                    }
                }

                message = new ZIMCombineMessage(combineTitle, combineSummary, combineMessageList);
                try {
                    if (messageMap.hasKey("combineID")) {
                        Field combineIDField = ZIMCombineMessage.class.getDeclaredField("combineID");
                        combineIDField.setAccessible(true);
                        combineIDField.set(message, messageMap.getString("combineID"));
                        combineIDField.setAccessible(false);
                    }
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
            case IMAGE:
                String imageFileLocalPath = messageMap.hasKey("fileLocalPath")
                    ? messageMap.getString("fileLocalPath")
                    : "";
                message = new ZIMImageMessage(imageFileLocalPath);
                if (messageMap.hasKey("thumbnailDownloadUrl")) {
                    ((ZIMImageMessage) message).setThumbnailDownloadUrl(messageMap.getString("thumbnailDownloadUrl"));
                }

                if (messageMap.hasKey("largeImageDownloadUrl")) {
                    ((ZIMImageMessage) message).setLargeImageDownloadUrl(messageMap.getString("largeImageDownloadUrl"));
                }

                try {
                    if (messageMap.hasKey("thumbnailLocalPath")) {
                        Field thumbnailLocalPathField = ZIMImageMessage.class.getDeclaredField("thumbnailLocalPath");
                        thumbnailLocalPathField.setAccessible(true);
                        thumbnailLocalPathField.set(message, messageMap.getString("thumbnailLocalPath"));
                        thumbnailLocalPathField.setAccessible(false);
                    }

                    if (messageMap.hasKey("largeImageLocalPath")) {
                        Field largeImageLocalPathField = ZIMImageMessage.class.getDeclaredField("largeImageLocalPath");
                        largeImageLocalPathField.setAccessible(true);
                        largeImageLocalPathField.set(message, messageMap.getString("largeImageLocalPath"));
                        largeImageLocalPathField.setAccessible(false);
                    }

                    if (messageMap.hasKey("originalImageWidth")) {
                        Field originalImageWidthField = ZIMImageMessage.class.getDeclaredField("originalImageWidth");
                        originalImageWidthField.setAccessible(true);
                        originalImageWidthField.set(message, messageMap.getInt("originalImageWidth"));
                        originalImageWidthField.setAccessible(false);
                    }

                    if (messageMap.hasKey("originalImageHeight")) {
                        Field originalImageHeightField = ZIMImageMessage.class.getDeclaredField("originalImageHeight");
                        originalImageHeightField.setAccessible(true);
                        originalImageHeightField.set(message, messageMap.getInt("originalImageHeight"));
                        originalImageHeightField.setAccessible(false);
                    }

                    if (messageMap.hasKey("thumbnailWidth")) {
                        Field thumbnailWidthField = ZIMImageMessage.class.getDeclaredField("thumbnailWidth");
                        thumbnailWidthField.setAccessible(true);
                        thumbnailWidthField.set(message, messageMap.getInt("thumbnailWidth"));
                        thumbnailWidthField.setAccessible(false);
                    }

                    if (messageMap.hasKey("thumbnailHeight")) {
                        Field thumbnailHeightField = ZIMImageMessage.class.getDeclaredField("thumbnailHeight");
                        thumbnailHeightField.setAccessible(true);
                        thumbnailHeightField.set(message, messageMap.getInt("thumbnailHeight"));
                        thumbnailHeightField.setAccessible(false);
                    }

                    if (messageMap.hasKey("largeImageWidth")) {
                        Field largeImageWidthField = ZIMImageMessage.class.getDeclaredField("largeImageWidth");
                        largeImageWidthField.setAccessible(true);
                        largeImageWidthField.set(message, messageMap.getInt("largeImageWidth"));
                        largeImageWidthField.setAccessible(false);
                    }

                    if (messageMap.hasKey("largeImageHeight")) {
                        Field largeImageHeightField = ZIMImageMessage.class.getDeclaredField("largeImageHeight");
                        largeImageHeightField.setAccessible(true);
                        largeImageHeightField.set(message, messageMap.getInt("largeImageHeight"));
                        largeImageHeightField.setAccessible(false);
                    }
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
            case FILE:
                String fileLocalPath = messageMap.hasKey("fileLocalPath") ? messageMap.getString("fileLocalPath") : "";
                message = new ZIMFileMessage(fileLocalPath);
                break;
            case VIDEO:
                String videoFileLocalPath = messageMap.hasKey("fileLocalPath")
                    ? messageMap.getString("fileLocalPath")
                    : "";
                long videoDuration = messageMap.hasKey("videoDuration")
                    ? Double.valueOf(messageMap.getDouble("videoDuration")).longValue()
                    : 0;
                message = new ZIMVideoMessage(videoFileLocalPath, videoDuration);
                try {
                    if (messageMap.hasKey("videoFirstFrameDownloadUrl")) {
                        ((ZIMVideoMessage) message).setVideoFirstFrameDownloadUrl(
                                messageMap.getString("videoFirstFrameDownloadUrl")
                            );
                    }

                    if (messageMap.hasKey("videoFirstFrameLocalPath")) {
                        Field videoFirstFrameLocalPathField =
                            ZIMVideoMessage.class.getDeclaredField("videoFirstFrameLocalPath");
                        videoFirstFrameLocalPathField.setAccessible(true);
                        videoFirstFrameLocalPathField.set(message, messageMap.getString("videoFirstFrameLocalPath"));
                        videoFirstFrameLocalPathField.setAccessible(false);
                    }

                    if (messageMap.hasKey("videoFirstFrameWidth")) {
                        Field videoFirstFrameWidthField =
                            ZIMVideoMessage.class.getDeclaredField("videoFirstFrameWidth");
                        videoFirstFrameWidthField.setAccessible(true);
                        videoFirstFrameWidthField.set(message, messageMap.getInt("videoFirstFrameWidth"));
                        videoFirstFrameWidthField.setAccessible(false);
                    }

                    if (messageMap.hasKey("videoFirstFrameHeight")) {
                        Field videoFirstFrameHeightField =
                            ZIMVideoMessage.class.getDeclaredField("videoFirstFrameHeight");
                        videoFirstFrameHeightField.setAccessible(true);
                        videoFirstFrameHeightField.set(message, messageMap.getInt("videoFirstFrameHeight"));
                        videoFirstFrameHeightField.setAccessible(false);
                    }
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
            case AUDIO:
                String audioFileLocalPath = messageMap.hasKey("fileLocalPath")
                    ? messageMap.getString("fileLocalPath")
                    : "";
                long audioDuration = messageMap.hasKey("audioDuration")
                    ? Double.valueOf(messageMap.getDouble("audioDuration")).longValue()
                    : 0;
                message = new ZIMAudioMessage(audioFileLocalPath, audioDuration);
                break;
            case MULTIPLE:
                message = new ZIMMultipleMessage();
                if (messageMap.getArray("messageInfoList") != null) {
                    ArrayList<ZIMMessageLiteInfo> messageInfoList = new ArrayList<>();
                    for (Object messageObj : messageMap.getArray("messageInfoList").toArrayList()) {
                        messageInfoList.add(convertLiteInfoFromJsToJava((HashMap<String, Object>) messageObj));
                    }
                    ((ZIMMultipleMessage) message).setMessageInfoList(messageInfoList);
                }
                break;    
            default:
                message = new ZIMMessage(ZIMMessageType.UNKNOWN);
                break;
        }

        try {
            // public field

            message.extendedData = messageMap.hasKey("extendedData") ? messageMap.getString("extendedData") : "";
            message.localExtendedData =
                messageMap.hasKey("localExtendedData") ? messageMap.getString("localExtendedData") : "";

            // private field

            Field msgTypeField = ZIMMessage.class.getDeclaredField("type");
            msgTypeField.setAccessible(true);
            msgTypeField.set(message, ZIMMessageType.getZIMMessageType(messageMap.getInt("type")));
            msgTypeField.setAccessible(false);

            Field messageIDField = ZIMMessage.class.getDeclaredField("messageID");
            messageIDField.setAccessible(true);
            long messageID = messageMap.hasKey("messageID")
                ? Long.valueOf(messageMap.getString("messageID")).longValue()
                : 0;
            messageIDField.set(message, messageID);
            messageIDField.setAccessible(false);

            Field conversationIDField = ZIMMessage.class.getDeclaredField("conversationID");
            conversationIDField.setAccessible(true);
            String conversationID = messageMap.hasKey("conversationID") ? messageMap.getString("conversationID") : "";
            conversationIDField.set(message, conversationID);
            conversationIDField.setAccessible(false);

            Field conversationSeqField = ZIMMessage.class.getDeclaredField("conversationSeq");
            conversationSeqField.setAccessible(true);
            long conversationSeq = messageMap.hasKey("conversationSeq")
                ? Double.valueOf(messageMap.getDouble("conversationSeq")).longValue()
                : 0;
            conversationSeqField.set(message, conversationSeq);
            conversationSeqField.setAccessible(false);

            Field messageSeqField = ZIMMessage.class.getDeclaredField("messageSeq");
            messageSeqField.setAccessible(true);
            long messageSeq = messageMap.hasKey("messageSeq")
                ? Double.valueOf(messageMap.getDouble("messageSeq")).longValue()
                : 0;
            messageSeqField.set(message, messageSeq);
            messageSeqField.setAccessible(false);

            Field senderUserIDField = ZIMMessage.class.getDeclaredField("senderUserID");
            senderUserIDField.setAccessible(true);
            String senderUserID = messageMap.hasKey("senderUserID") ? messageMap.getString("senderUserID") : "";
            senderUserIDField.set(message, senderUserID);
            senderUserIDField.setAccessible(false);

            Field timestampField = ZIMMessage.class.getDeclaredField("timestamp");
            timestampField.setAccessible(true);
            long timestamp = messageMap.hasKey("timestamp")
                ? Double.valueOf(messageMap.getDouble("timestamp")).longValue()
                : 0;
            timestampField.set(message, timestamp);
            timestampField.setAccessible(false);

            Field localMessageIDField = ZIMMessage.class.getDeclaredField("localMessageID");
            localMessageIDField.setAccessible(true);
            long localMessageID = messageMap.hasKey("localMessageID")
                ? Long.valueOf(messageMap.getString("localMessageID")).longValue()
                : 0;
            localMessageIDField.set(message, localMessageID);
            localMessageIDField.setAccessible(false);

            Field conversationTypeField = ZIMMessage.class.getDeclaredField("conversationType");
            conversationTypeField.setAccessible(true);
            int conversationType = messageMap.hasKey("conversationType") ? messageMap.getInt("conversationType") : -1;
            conversationTypeField.set(message, ZIMConversationType.getZIMConversationType(conversationType));
            conversationTypeField.setAccessible(false);

            Field directionField = ZIMMessage.class.getDeclaredField("direction");
            directionField.setAccessible(true);
            int direction = messageMap.hasKey("direction") ? messageMap.getInt("direction") : -1;
            directionField.set(message, ZIMMessageDirection.getZIMMessageDirection(direction));
            directionField.setAccessible(false);

            Field sentStatusField = ZIMMessage.class.getDeclaredField("sentStatus");
            sentStatusField.setAccessible(true);
            int sentStatus = messageMap.hasKey("sentStatus") ? messageMap.getInt("sentStatus") : -1;
            sentStatusField.set(message, ZIMMessageSentStatus.getZIMMessageSentStatus(sentStatus));
            sentStatusField.setAccessible(false);

            Field orderKeyField = ZIMMessage.class.getDeclaredField("orderKey");
            orderKeyField.setAccessible(true);
            long orderKey = messageMap.hasKey("orderKey")
                ? Double.valueOf(messageMap.getDouble("orderKey")).longValue()
                : 0;
            orderKeyField.set(message, orderKey);
            orderKeyField.setAccessible(false);

            Field receiptStatusField = ZIMMessage.class.getDeclaredField("receiptStatus");
            receiptStatusField.setAccessible(true);
            int receiptStatus = messageMap.hasKey("receiptStatus") ? messageMap.getInt("receiptStatus") : 0;
            receiptStatusField.set(message, ZIMMessageReceiptStatus.getZIMMessageReceiptStatus(receiptStatus));
            receiptStatusField.setAccessible(false);

            Field isUserInsertedField = ZIMMessage.class.getDeclaredField("isUserInserted");
            isUserInsertedField.setAccessible(true);
            boolean isUserInserted = messageMap.hasKey("isUserInserted")
                ? messageMap.getBoolean("isUserInserted")
                : false;
            isUserInsertedField.set(message, isUserInserted);
            isUserInsertedField.setAccessible(false);

            Field isBroadcastMessageField = ZIMMessage.class.getDeclaredField("isBroadcastMessage");
            isBroadcastMessageField.setAccessible(true);
            boolean isBroadcastMessage = messageMap.hasKey("isBroadcastMessage")
                ? messageMap.getBoolean("isBroadcastMessage")
                : false;
            isBroadcastMessageField.set(message, isBroadcastMessage);
            isBroadcastMessageField.setAccessible(false);

            Field isServerMessageField = ZIMMessage.class.getDeclaredField("isServerMessage");
            isServerMessageField.setAccessible(true);
            boolean isServerMessage = messageMap.hasKey("isServerMessage")
                ? messageMap.getBoolean("isServerMessage")
                : false;
            isServerMessageField.set(message, isServerMessage);
            isServerMessageField.setAccessible(false);

            Field isMentionAllField = ZIMMessage.class.getDeclaredField("isMentionAll");
            isMentionAllField.setAccessible(true);
            boolean isMentionAll = messageMap.hasKey("isMentionAll") ? messageMap.getBoolean("isMentionAll") : false;
            isMentionAllField.set(message, isMentionAll);
            isMentionAllField.setAccessible(false);

            if (messageMap.getArray("mentionedUserIDs") != null) {
                ArrayList<String> mentionList = new ArrayList<>();
                for (Object key : messageMap.getArray("mentionedUserIDs").toArrayList()) {
                    mentionList.add((String) key);
                }

                Field mentionedUserIDsField = ZIMMessage.class.getDeclaredField("mentionedUserIDs");
                mentionedUserIDsField.setAccessible(true);
                mentionedUserIDsField.set(message, mentionList);
                mentionedUserIDsField.setAccessible(false);
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        if (message instanceof ZIMMediaMessage) {
            try {
                if (messageMap.hasKey("fileDownloadUrl")) {
                    ((ZIMMediaMessage) message).setFileDownloadUrl(messageMap.getString("fileDownloadUrl"));
                }
                if (messageMap.hasKey("fileUID")) {
                    Field fileUIDField = ZIMMediaMessage.class.getDeclaredField("fileUID");
                    fileUIDField.setAccessible(true);
                    fileUIDField.set(message, messageMap.getString("fileUID"));
                    fileUIDField.setAccessible(false);
                }

                if (messageMap.hasKey("fileName")) {
                    Field fileNameField = ZIMMediaMessage.class.getDeclaredField("fileName");
                    fileNameField.setAccessible(true);
                    fileNameField.set(message, messageMap.getString("fileName"));
                    fileNameField.setAccessible(false);
                }

                if (messageMap.hasKey("fileSize")) {
                    Field fileSizeField = ZIMMediaMessage.class.getDeclaredField("fileSize");
                    fileSizeField.setAccessible(true);
                    fileSizeField.set(message, Double.valueOf(messageMap.getDouble("fileSize")).longValue());
                    fileSizeField.setAccessible(false);
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        return message;
    }

    // Used for queried messages
    public static ZIMMessage convertMessageFromJsToJava(HashMap<String, Object> messageMap) {
        if (messageMap == null) {
            return null;
        }

        ZIMMessage message = null;
        ZIMMessageType type = ZIMMessageType.getZIMMessageType(
            Double.valueOf((double) messageMap.get("type")).intValue()
        );
        switch (type) {
            case TEXT:
                message = new ZIMTextMessage((String) messageMap.get("message"));
                break;
            case COMMAND:
                String[] split = ((String) messageMap.get("message")).split(",");
                int size = split.length;
                byte[] bytes = new byte[size];
                for (int i = 0; i < size; i++) {
                    int b = Integer.parseInt(split[i]);
                    bytes[i] = (byte) (b < 128 ? b : b - 256);
                }

                message = new ZIMCommandMessage(bytes);
                break;
            case BARRAGE:
                message = new ZIMBarrageMessage((String) messageMap.get("message"));
                break;
            case REVOKE:
                message = new ZIMRevokeMessage();
                try {
                    Field revokeTypeField = ZIMRevokeMessage.class.getDeclaredField("revokeType");
                    revokeTypeField.setAccessible(true);
                    int revokeType = messageMap.containsKey("revokeType")
                        ? Double.valueOf((double) messageMap.get("revokeType")).intValue()
                        : -1;
                    revokeTypeField.set(message, ZIMRevokeType.getZIMRevokeType(revokeType));
                    revokeTypeField.setAccessible(false);

                    Field originalMessageTypeField = ZIMRevokeMessage.class.getDeclaredField("originalMessageType");
                    originalMessageTypeField.setAccessible(true);
                    int originalMessageType = messageMap.containsKey("originalMessageType")
                        ? Double.valueOf((double) messageMap.get("originalMessageType")).intValue()
                        : 0;
                    originalMessageTypeField.set(message, ZIMMessageType.getZIMMessageType(originalMessageType));
                    originalMessageTypeField.setAccessible(false);

                    Field revokeStatusField = ZIMRevokeMessage.class.getDeclaredField("revokeStatus");
                    revokeStatusField.setAccessible(true);
                    int revokeStatus = messageMap.containsKey("revokeStatus")
                        ? Double.valueOf((double) messageMap.get("revokeStatus")).intValue()
                        : -1;
                    revokeStatusField.set(message, ZIMMessageRevokeStatus.getZIMMessageRevokeStatus(revokeStatus));
                    revokeStatusField.setAccessible(false);

                    Field revokeTimestampField = ZIMRevokeMessage.class.getDeclaredField("revokeTimestamp");
                    revokeTimestampField.setAccessible(true);
                    long revokeTimestamp = messageMap.containsKey("revokeTimestamp")
                        ? Double.valueOf((double) messageMap.get("revokeTimestamp")).longValue()
                        : 0;
                    revokeTimestampField.set(message, revokeTimestamp);
                    revokeTimestampField.setAccessible(false);

                    Field operatedUserIDField = ZIMRevokeMessage.class.getDeclaredField("operatedUserID");
                    operatedUserIDField.setAccessible(true);
                    String operatedUserID = messageMap.containsKey("operatedUserID")
                        ? (String) messageMap.get("operatedUserID")
                        : "";
                    operatedUserIDField.set(message, operatedUserID);
                    operatedUserIDField.setAccessible(false);

                    Field revokeExtendedDataField = ZIMRevokeMessage.class.getDeclaredField("revokeExtendedData");
                    revokeExtendedDataField.setAccessible(true);
                    String revokeExtendedData = messageMap.containsKey("revokeExtendedData")
                        ? (String) messageMap.get("revokeExtendedData")
                        : "";
                    revokeExtendedDataField.set(message, revokeExtendedData);
                    revokeExtendedDataField.setAccessible(false);

                    Field originalTextMessageContentField =
                        ZIMRevokeMessage.class.getDeclaredField("originalTextMessageContent");
                    originalTextMessageContentField.setAccessible(true);
                    String originalTextMessageContent = messageMap.containsKey("originalTextMessageContent")
                        ? (String) messageMap.get("originalTextMessageContent")
                        : "";
                    originalTextMessageContentField.set(message, originalTextMessageContent);
                    originalTextMessageContentField.setAccessible(false);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
            case TIPS:
                message = new ZIMTipsMessage();
                ((ZIMTipsMessage) message).event =
                    ZIMTipsMessageEvent.getZIMTipsMessageEvent(
                        messageMap.containsKey("event")
                            ? Double.valueOf((double) messageMap.get("event")).intValue()
                            : 0
                    );
                ((ZIMTipsMessage) message).operatedUser =
                    messageMap.containsKey("operatedUser")
                        ? convertUserOrGroupMemberFromJsToJava((HashMap<String, Object>) messageMap.get("operatedUser"))
                        : null;
                ArrayList<ZIMUserInfo> targetUserList = new ArrayList<>();
                if (messageMap.get("targetUserList") != null) {
                    for (Object infoObj : (ArrayList) messageMap.get("targetUserList")) {
                        targetUserList.add(convertUserOrGroupMemberFromJsToJava((HashMap<String, Object>) infoObj));
                    }
                }
                ((ZIMTipsMessage) message).targetUserList = targetUserList;
                ((ZIMTipsMessage) message).changeInfo =
                    messageMap.containsKey("changeInfo")
                        ? convertTipsMessageChangeInfoFromJsToJava(
                            (HashMap<String, Object>) messageMap.get("changeInfo")
                        )
                        : null;
                break;
            case CUSTOM:
                int subType = messageMap.containsKey("subType")
                    ? Double.valueOf((double) messageMap.get("subType")).intValue()
                    : 0;
                message = new ZIMCustomMessage((String) messageMap.get("message"), subType);
                if (messageMap.containsKey("searchedContent")) {
                    ((ZIMCustomMessage) message).searchedContent = (String) messageMap.get("searchedContent");
                }
                break;
            case COMBINE:
                String combineTitle = messageMap.containsKey("title") ? (String) messageMap.get("title") : "";
                String combineSummary = messageMap.containsKey("summary") ? (String) messageMap.get("summary") : "";
                ArrayList<ZIMMessage> combineMessageList = new ArrayList<>();
                if (messageMap.get("messageList") != null) {
                    for (Object messageObj : (ArrayList) messageMap.get("messageList")) {
                        combineMessageList.add(convertMessageFromJsToJava((HashMap<String, Object>) messageObj));
                    }
                }

                message = new ZIMCombineMessage(combineTitle, combineSummary, combineMessageList);
                try {
                    if (messageMap.containsKey("combineID")) {
                        Field combineIDField = ZIMCombineMessage.class.getDeclaredField("combineID");
                        combineIDField.setAccessible(true);
                        combineIDField.set(message, (String) messageMap.get("combineID"));
                        combineIDField.setAccessible(false);
                    }
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
            case IMAGE:
                message = new ZIMImageMessage((String) messageMap.get("fileLocalPath"));
                if (messageMap.containsKey("thumbnailDownloadUrl")) {
                    ((ZIMImageMessage) message).setThumbnailDownloadUrl(
                            (String) messageMap.get("thumbnailDownloadUrl")
                        );
                }

                if (messageMap.containsKey("largeImageDownloadUrl")) {
                    ((ZIMImageMessage) message).setLargeImageDownloadUrl(
                            (String) messageMap.get("largeImageDownloadUrl")
                        );
                }

                try {
                    if (messageMap.containsKey("thumbnailLocalPath")) {
                        Field thumbnailLocalPathField = ZIMImageMessage.class.getDeclaredField("thumbnailLocalPath");
                        thumbnailLocalPathField.setAccessible(true);
                        thumbnailLocalPathField.set(message, (String) messageMap.get("thumbnailLocalPath"));
                        thumbnailLocalPathField.setAccessible(false);
                    }

                    if (messageMap.containsKey("largeImageLocalPath")) {
                        Field largeImageLocalPathField = ZIMImageMessage.class.getDeclaredField("largeImageLocalPath");
                        largeImageLocalPathField.setAccessible(true);
                        largeImageLocalPathField.set(message, (String) messageMap.get("largeImageLocalPath"));
                        largeImageLocalPathField.setAccessible(false);
                    }

                    if (messageMap.containsKey("originalImageWidth")) {
                        Field originalImageWidthField = ZIMImageMessage.class.getDeclaredField("originalImageWidth");
                        originalImageWidthField.setAccessible(true);
                        originalImageWidthField.set(
                            message,
                            Double.valueOf((double) messageMap.get("originalImageWidth")).intValue()
                        );
                        originalImageWidthField.setAccessible(false);
                    }

                    if (messageMap.containsKey("originalImageHeight")) {
                        Field originalImageHeightField = ZIMImageMessage.class.getDeclaredField("originalImageHeight");
                        originalImageHeightField.setAccessible(true);
                        originalImageHeightField.set(
                            message,
                            Double.valueOf((double) messageMap.get("originalImageHeight")).intValue()
                        );
                        originalImageHeightField.setAccessible(false);
                    }

                    if (messageMap.containsKey("thumbnailWidth")) {
                        Field thumbnailWidthField = ZIMImageMessage.class.getDeclaredField("thumbnailWidth");
                        thumbnailWidthField.setAccessible(true);
                        thumbnailWidthField.set(
                            message,
                            Double.valueOf((double) messageMap.get("thumbnailWidth")).intValue()
                        );
                        thumbnailWidthField.setAccessible(false);
                    }

                    if (messageMap.containsKey("thumbnailHeight")) {
                        Field thumbnailHeightField = ZIMImageMessage.class.getDeclaredField("thumbnailHeight");
                        thumbnailHeightField.setAccessible(true);
                        thumbnailHeightField.set(
                            message,
                            Double.valueOf((double) messageMap.get("thumbnailHeight")).intValue()
                        );
                        thumbnailHeightField.setAccessible(false);
                    }

                    if (messageMap.containsKey("largeImageWidth")) {
                        Field largeImageWidthField = ZIMImageMessage.class.getDeclaredField("largeImageWidth");
                        largeImageWidthField.setAccessible(true);
                        largeImageWidthField.set(
                            message,
                            Double.valueOf((double) messageMap.get("largeImageWidth")).intValue()
                        );
                        largeImageWidthField.setAccessible(false);
                    }

                    if (messageMap.containsKey("largeImageHeight")) {
                        Field largeImageHeightField = ZIMImageMessage.class.getDeclaredField("largeImageHeight");
                        largeImageHeightField.setAccessible(true);
                        largeImageHeightField.set(
                            message,
                            Double.valueOf((double) messageMap.get("largeImageHeight")).intValue()
                        );
                        largeImageHeightField.setAccessible(false);
                    }
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
            case FILE:
                message = new ZIMFileMessage((String) messageMap.get("fileLocalPath"));
                break;
            case VIDEO:
                message =
                    new ZIMVideoMessage(
                        (String) messageMap.get("fileLocalPath"),
                        Double.valueOf((double) messageMap.get("videoDuration")).longValue()
                    );
                try {
                    if (messageMap.containsKey("videoFirstFrameDownloadUrl")) {
                        ((ZIMVideoMessage) message).setVideoFirstFrameDownloadUrl(
                                (String) messageMap.get("videoFirstFrameDownloadUrl")
                            );
                    }

                    if (messageMap.containsKey("videoFirstFrameLocalPath")) {
                        Field videoFirstFrameLocalPathField =
                            ZIMVideoMessage.class.getDeclaredField("videoFirstFrameLocalPath");
                        videoFirstFrameLocalPathField.setAccessible(true);
                        videoFirstFrameLocalPathField.set(message, (String) messageMap.get("videoFirstFrameLocalPath"));
                        videoFirstFrameLocalPathField.setAccessible(false);
                    }

                    if (messageMap.containsKey("videoFirstFrameWidth")) {
                        Field videoFirstFrameWidthField =
                            ZIMVideoMessage.class.getDeclaredField("videoFirstFrameWidth");
                        videoFirstFrameWidthField.setAccessible(true);
                        videoFirstFrameWidthField.set(
                            message,
                            Double.valueOf((double) messageMap.get("videoFirstFrameWidth")).intValue()
                        );
                        videoFirstFrameWidthField.setAccessible(false);
                    }

                    if (messageMap.containsKey("videoFirstFrameHeight")) {
                        Field videoFirstFrameHeightField =
                            ZIMVideoMessage.class.getDeclaredField("videoFirstFrameHeight");
                        videoFirstFrameHeightField.setAccessible(true);
                        videoFirstFrameHeightField.set(
                            message,
                            Double.valueOf((double) messageMap.get("videoFirstFrameHeight")).intValue()
                        );
                        videoFirstFrameHeightField.setAccessible(false);
                    }
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                break;
            case AUDIO:
                message =
                    new ZIMAudioMessage(
                        (String) messageMap.get("fileLocalPath"),
                        Double.valueOf((double) messageMap.get("audioDuration")).longValue()
                    );
                break;
             case MULTIPLE:
                message = new ZIMMultipleMessage();
                if (messageMap.get("messageInfoList") != null) {
                    ArrayList<ZIMMessageLiteInfo> messageInfoList = new ArrayList<>();
                    for (Object messageObj : (ArrayList) messageMap.get("messageInfoList")) {
                        messageInfoList.add(convertLiteInfoFromJsToJava((HashMap<String, Object>) messageObj));
                    }
                    ((ZIMMultipleMessage) message).setMessageInfoList(messageInfoList);
                }
                break;      
            default:
                message = new ZIMMessage(ZIMMessageType.UNKNOWN);
                break;
        }

        try {
            // public field

            message.extendedData =
                messageMap.containsKey("extendedData") ? (String) messageMap.get("extendedData") : "";
            message.localExtendedData =
                messageMap.containsKey("localExtendedData") ? (String) messageMap.get("localExtendedData") : "";

            // private field

            Field msgTypeField = ZIMMessage.class.getDeclaredField("type");
            msgTypeField.setAccessible(true);
            msgTypeField.set(message, type);
            msgTypeField.setAccessible(false);

            Field messageIDField = ZIMMessage.class.getDeclaredField("messageID");
            messageIDField.setAccessible(true);
            long messageID = messageMap.containsKey("messageID")
                ? Long.valueOf((String) messageMap.get("messageID")).longValue()
                : 0;
            messageIDField.set(message, messageID);
            messageIDField.setAccessible(false);

            Field conversationIDField = ZIMMessage.class.getDeclaredField("conversationID");
            conversationIDField.setAccessible(true);
            String conversationID = messageMap.containsKey("conversationID")
                ? (String) messageMap.get("conversationID")
                : "";
            conversationIDField.set(message, conversationID);
            conversationIDField.setAccessible(false);

            Field conversationSeqField = ZIMMessage.class.getDeclaredField("conversationSeq");
            conversationSeqField.setAccessible(true);
            long conversationSeq = messageMap.containsKey("conversationSeq")
                ? Double.valueOf((double) messageMap.get("conversationSeq")).longValue()
                : 0;
            conversationSeqField.set(message, conversationSeq);
            conversationSeqField.setAccessible(false);

            Field messageSeqField = ZIMMessage.class.getDeclaredField("messageSeq");
            messageSeqField.setAccessible(true);
            long messageSeq = messageMap.containsKey("messageSeq")
                ? Double.valueOf((double) messageMap.get("messageSeq")).longValue()
                : 0;
            messageSeqField.set(message, messageSeq);
            messageSeqField.setAccessible(false);

            Field senderUserIDField = ZIMMessage.class.getDeclaredField("senderUserID");
            senderUserIDField.setAccessible(true);
            String senderUserID = messageMap.containsKey("senderUserID") ? (String) messageMap.get("senderUserID") : "";
            senderUserIDField.set(message, senderUserID);
            senderUserIDField.setAccessible(false);

            Field timestampField = ZIMMessage.class.getDeclaredField("timestamp");
            timestampField.setAccessible(true);
            long timestamp = messageMap.containsKey("timestamp")
                ? Double.valueOf((double) messageMap.get("timestamp")).longValue()
                : 0;
            timestampField.set(message, timestamp);
            timestampField.setAccessible(false);

            Field localMessageIDField = ZIMMessage.class.getDeclaredField("localMessageID");
            localMessageIDField.setAccessible(true);
            long localMessageID = messageMap.containsKey("localMessageID")
                ? Long.valueOf((String) messageMap.get("localMessageID")).longValue()
                : 0;
            localMessageIDField.set(message, localMessageID);
            localMessageIDField.setAccessible(false);

            Field conversationTypeField = ZIMMessage.class.getDeclaredField("conversationType");
            conversationTypeField.setAccessible(true);
            int conversationType = messageMap.containsKey("conversationType")
                ? Double.valueOf((double) messageMap.get("conversationType")).intValue()
                : -1;
            conversationTypeField.set(message, ZIMConversationType.getZIMConversationType(conversationType));
            conversationTypeField.setAccessible(false);

            Field directionField = ZIMMessage.class.getDeclaredField("direction");
            directionField.setAccessible(true);
            int direction = messageMap.containsKey("direction")
                ? Double.valueOf((double) messageMap.get("direction")).intValue()
                : -1;
            directionField.set(message, ZIMMessageDirection.getZIMMessageDirection(direction));
            directionField.setAccessible(false);

            Field sentStatusField = ZIMMessage.class.getDeclaredField("sentStatus");
            sentStatusField.setAccessible(true);
            int sentStatus = messageMap.containsKey("sentStatus")
                ? Double.valueOf((double) messageMap.get("sentStatus")).intValue()
                : -1;
            sentStatusField.set(message, ZIMMessageSentStatus.getZIMMessageSentStatus(sentStatus));
            sentStatusField.setAccessible(false);

            Field orderKeyField = ZIMMessage.class.getDeclaredField("orderKey");
            orderKeyField.setAccessible(true);
            long orderKey = messageMap.containsKey("orderKey")
                ? Double.valueOf((double) messageMap.get("orderKey")).longValue()
                : 0;
            orderKeyField.set(message, orderKey);
            orderKeyField.setAccessible(false);

            Field receiptStatusField = ZIMMessage.class.getDeclaredField("receiptStatus");
            receiptStatusField.setAccessible(true);
            int receiptStatus = messageMap.containsKey("receiptStatus")
                ? Double.valueOf((double) messageMap.get("receiptStatus")).intValue()
                : 0;
            receiptStatusField.set(message, ZIMMessageReceiptStatus.getZIMMessageReceiptStatus(receiptStatus));
            receiptStatusField.setAccessible(false);

            Field isUserInsertedField = ZIMMessage.class.getDeclaredField("isUserInserted");
            isUserInsertedField.setAccessible(true);
            boolean isUserInserted = messageMap.containsKey("isUserInserted")
                ? ((Boolean) messageMap.get("isUserInserted")).booleanValue()
                : false;
            isUserInsertedField.set(message, isUserInserted);
            isUserInsertedField.setAccessible(false);

            Field isBroadcastMessageField = ZIMMessage.class.getDeclaredField("isBroadcastMessage");
            isBroadcastMessageField.setAccessible(true);
            boolean isBroadcastMessage = messageMap.containsKey("isBroadcastMessage")
                ? ((Boolean) messageMap.get("isBroadcastMessage")).booleanValue()
                : false;
            isBroadcastMessageField.set(message, isBroadcastMessage);
            isBroadcastMessageField.setAccessible(false);

            Field isServerMessageField = ZIMMessage.class.getDeclaredField("isServerMessage");
            isServerMessageField.setAccessible(true);
            boolean isServerMessage = messageMap.containsKey("isServerMessage")
                ? ((Boolean) messageMap.get("isServerMessage")).booleanValue()
                : false;
            isServerMessageField.set(message, isServerMessage);
            isServerMessageField.setAccessible(false);

            Field isMentionAllField = ZIMMessage.class.getDeclaredField("isMentionAll");
            isMentionAllField.setAccessible(true);
            boolean isMentionAll = messageMap.containsKey("isMentionAll")
                ? ((Boolean) messageMap.get("isMentionAll")).booleanValue()
                : false;
            isMentionAllField.set(message, isMentionAll);
            isMentionAllField.setAccessible(false);

            if (messageMap.get("mentionedUserIDs") != null) {
                Field mentionedUserIDsField = ZIMMessage.class.getDeclaredField("mentionedUserIDs");
                mentionedUserIDsField.setAccessible(true);
                mentionedUserIDsField.set(message, (ArrayList<String>) messageMap.get("mentionedUserIDs"));
                mentionedUserIDsField.setAccessible(false);
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        if (message instanceof ZIMMediaMessage) {
            if (messageMap.containsKey("fileDownloadUrl")) {
                ((ZIMMediaMessage) message).setFileDownloadUrl((String) messageMap.get("fileDownloadUrl"));
            }
            try {
                if (messageMap.containsKey("fileUID")) {
                    Field fileUIDField = ZIMMediaMessage.class.getDeclaredField("fileUID");
                    fileUIDField.setAccessible(true);
                    fileUIDField.set(message, (String) messageMap.get("fileUID"));
                    fileUIDField.setAccessible(false);
                }

                if (messageMap.containsKey("fileName")) {
                    Field fileNameField = ZIMMediaMessage.class.getDeclaredField("fileName");
                    fileNameField.setAccessible(true);
                    fileNameField.set(message, (String) messageMap.get("fileName"));
                    fileNameField.setAccessible(false);
                }

                if (messageMap.containsKey("fileSize")) {
                    Field fileSizeField = ZIMMediaMessage.class.getDeclaredField("fileSize");
                    fileSizeField.setAccessible(true);
                    fileSizeField.set(message, Double.valueOf((double) messageMap.get("fileSize")).longValue());
                    fileSizeField.setAccessible(false);
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        return message;
    }

    public static WritableMap convertMessage(ZIMMessage message) {
        WritableMap messageMap = Arguments.createMap();
        messageMap.putInt("type", message.getType().value());
        messageMap.putString("messageID", String.valueOf(message.getMessageID()));
        messageMap.putString("conversationID", message.getConversationID());
        messageMap.putDouble("conversationSeq", message.getConversationSeq());
        messageMap.putDouble("messageSeq", message.getMessageSeq());
        messageMap.putString("senderUserID", message.getSenderUserID());
        messageMap.putDouble("timestamp", message.getTimestamp());
        messageMap.putString("localMessageID", String.valueOf(message.getLocalMessageID()));
        messageMap.putInt("conversationType", message.getConversationType().value());
        messageMap.putInt("direction", message.getDirection().value());
        messageMap.putInt("sentStatus", message.getSentStatus().value());
        messageMap.putDouble("orderKey", message.getOrderKey());
        messageMap.putBoolean("isUserInserted", message.isUserInserted());
        messageMap.putInt("receiptStatus", message.getReceiptStatus().value());
        messageMap.putString("extendedData", message.extendedData);
        messageMap.putString("localExtendedData", message.localExtendedData);
        messageMap.putBoolean("isBroadcastMessage", message.isBroadcastMessage());
        messageMap.putBoolean("isServerMessage", message.isServerMessage());
        messageMap.putBoolean("isMentionAll", message.isMentionAll());
        messageMap.putInt("rootRepliedCount", message.getRootRepliedCount());
        messageMap.putMap("repliedInfo", convertRepliedInfo(message.getRepliedInfo()));
        messageMap.putString("editorUserID", message.getEditorUserID());
        messageMap.putDouble("editedTime", message.getEditedTime());

        ArrayList<ZIMMessageReaction> reactions = message.getReactions();
        WritableArray reactionArray = Arguments.createArray();
        for (ZIMMessageReaction info : reactions) {
            reactionArray.pushMap(convertMessageReaction(info));
        }
        messageMap.putArray("reactions", reactionArray);

        ArrayList<String> mentions = message.getMentionedUserIDs();
        WritableArray mentionArray = Arguments.createArray();
        for (String info : mentions) {
            mentionArray.pushString(info);
        }
        messageMap.putArray("mentionedUserIDs", mentionArray);

        switch (message.getType()) {
            case TEXT:
                assert message instanceof ZIMTextMessage;
                messageMap.putString("message", ((ZIMTextMessage) message).message);
                break;
            case COMMAND:
                assert message instanceof ZIMCommandMessage;
                byte[] data = ((ZIMCommandMessage) message).message;
                WritableMap dataMap = Arguments.createMap();
                for (int i = 0; i < data.length; i++) {
                    int b = data[i];
                    dataMap.putInt(String.valueOf(i), b < 0 ? 256 + b : b);
                }
                messageMap.putMap("message", dataMap);
                break;
            case BARRAGE:
                assert message instanceof ZIMBarrageMessage;
                messageMap.putString("message", ((ZIMBarrageMessage) message).message);
                break;
            case REVOKE:
                assert message instanceof ZIMRevokeMessage;
                messageMap.putInt("revokeType", ((ZIMRevokeMessage) message).getRevokeType().value());
                messageMap.putInt("revokeStatus", ((ZIMRevokeMessage) message).getRevokeStatus().value());
                messageMap.putDouble("revokeTimestamp", ((ZIMRevokeMessage) message).getRevokeTimestamp());
                messageMap.putString("operatedUserID", ((ZIMRevokeMessage) message).getOperatedUserID());
                messageMap.putString("revokeExtendedData", ((ZIMRevokeMessage) message).getRevokeExtendedData());
                messageMap.putInt("originalMessageType", ((ZIMRevokeMessage) message).getOriginalMessageType().value());
                messageMap.putString(
                    "originalTextMessageContent",
                    ((ZIMRevokeMessage) message).getOriginalTextMessageContent()
                );
                break;
            case TIPS:
                assert message instanceof ZIMTipsMessage;
                messageMap.putInt("event", ((ZIMTipsMessage) message).event.value());
                messageMap.putMap("operatedUser", convertUserOrGroupMember(((ZIMTipsMessage) message).operatedUser));
                messageMap.putArray(
                    "targetUserList",
                    convertUserOrGroupMemberList(((ZIMTipsMessage) message).targetUserList)
                );
                messageMap.putMap("changeInfo", convertTipsMessageChangeInfo(((ZIMTipsMessage) message).changeInfo));
                break;
            case CUSTOM:
                assert message instanceof ZIMCustomMessage;
                messageMap.putString("message", ((ZIMCustomMessage) message).message);
                messageMap.putInt("subType", ((ZIMCustomMessage) message).subType);
                messageMap.putString("searchedContent", ((ZIMCustomMessage) message).searchedContent);
                break;
            case COMBINE:
                assert message instanceof ZIMCombineMessage;
                messageMap.putString("title", ((ZIMCombineMessage) message).title);
                messageMap.putString("summary", ((ZIMCombineMessage) message).summary);
                messageMap.putString("combineID", ((ZIMCombineMessage) message).getCombineID());
                messageMap.putArray("messageList", convertMessageList(((ZIMCombineMessage) message).messageList));
                break;
            case IMAGE:
                assert message instanceof ZIMImageMessage;

                messageMap.putString(
                    "thumbnailDownloadUrl",
                    ((ZIMImageMessage) message).getThumbnailDownloadUrl() != null
                        ? ((ZIMImageMessage) message).getThumbnailDownloadUrl()
                        : ""
                );
                messageMap.putString(
                    "thumbnailLocalPath",
                    ((ZIMImageMessage) message).getThumbnailLocalPath() != null
                        ? ((ZIMImageMessage) message).getThumbnailLocalPath()
                        : ""
                );
                messageMap.putString(
                    "largeImageDownloadUrl",
                    ((ZIMImageMessage) message).getLargeImageDownloadUrl() != null
                        ? ((ZIMImageMessage) message).getLargeImageDownloadUrl()
                        : ""
                );
                messageMap.putString(
                    "largeImageLocalPath",
                    ((ZIMImageMessage) message).getLargeImageLocalPath() != null
                        ? ((ZIMImageMessage) message).getLargeImageLocalPath()
                        : ""
                );
                messageMap.putInt("originalImageWidth", ((ZIMImageMessage) message).getOriginalImageWidth());
                messageMap.putInt("originalImageHeight", ((ZIMImageMessage) message).getOriginalImageHeight());
                messageMap.putInt("thumbnailWidth", ((ZIMImageMessage) message).getThumbnailWidth());
                messageMap.putInt("thumbnailHeight", ((ZIMImageMessage) message).getThumbnailHeight());
                messageMap.putInt("largeImageWidth", ((ZIMImageMessage) message).getLargeImageWidth());
                messageMap.putInt("largeImageHeight", ((ZIMImageMessage) message).getLargeImageHeight());
                break;
            case VIDEO:
                assert message instanceof ZIMVideoMessage;

                messageMap.putDouble("videoDuration", ((ZIMVideoMessage) message).getVideoDuration());
                messageMap.putString(
                    "videoFirstFrameDownloadUrl",
                    ((ZIMVideoMessage) message).getVideoFirstFrameDownloadUrl()
                );
                messageMap.putString(
                    "videoFirstFrameLocalPath",
                    ((ZIMVideoMessage) message).getVideoFirstFrameLocalPath()
                );
                messageMap.putInt("videoFirstFrameWidth", ((ZIMVideoMessage) message).getVideoFirstFrameWidth());
                messageMap.putInt("videoFirstFrameHeight", ((ZIMVideoMessage) message).getVideoFirstFrameHeight());
                break;
            case AUDIO:
                assert message instanceof ZIMAudioMessage;
                messageMap.putDouble("audioDuration", ((ZIMAudioMessage) message).getAudioDuration());
                break;
            case FILE:
                assert message instanceof ZIMFileMessage;
                break;
            case MULTIPLE:
                assert message instanceof ZIMMultipleMessage;
                WritableArray infosArray = Arguments.createArray();
                for (ZIMMessageLiteInfo info : ((ZIMMultipleMessage) message).getMessageInfoList()) {
                    infosArray.pushMap(convertLiteInfo(info));
                }
                messageMap.putArray("messageInfoList", infosArray);
                break;    
            default:
                break;
        }

        if (message instanceof ZIMMediaMessage) {
            messageMap.putString("fileLocalPath", ((ZIMMediaMessage) message).getFileLocalPath());
            messageMap.putString("fileDownloadUrl", ((ZIMMediaMessage) message).getFileDownloadUrl());
            messageMap.putString("fileUID", ((ZIMMediaMessage) message).getFileUID());
            messageMap.putString("fileName", ((ZIMMediaMessage) message).getFileName());
            messageMap.putDouble("fileSize", ((ZIMMediaMessage) message).getFileSize());
        }

        return messageMap;
    }

    public static WritableMap convertLiteInfo(ZIMMessageLiteInfo message) {
        if (message == null) {
            return null;
        }

        WritableMap messageMap = Arguments.createMap();
        messageMap.putInt("type", message.type.value());

        switch (message.type) {
            case TEXT:
                assert message instanceof ZIMTextMessageLiteInfo;
                messageMap.putString("message", ((ZIMTextMessageLiteInfo) message).message);
                break;
            case CUSTOM:
                assert message instanceof ZIMCustomMessageLiteInfo;
                messageMap.putString("message", ((ZIMCustomMessageLiteInfo) message).message);
                messageMap.putInt("subType", ((ZIMCustomMessageLiteInfo) message).subType);
                messageMap.putString("searchedContent", ((ZIMCustomMessageLiteInfo) message).searchedContent);
                break;
            case COMBINE:
                assert message instanceof ZIMCombineMessageLiteInfo;
                messageMap.putString("title", ((ZIMCombineMessageLiteInfo) message).title);
                messageMap.putString("summary", ((ZIMCombineMessageLiteInfo) message).summary);
                break;
            case IMAGE:
                assert message instanceof ZIMImageMessageLiteInfo;

                messageMap.putString("thumbnailDownloadUrl", ((ZIMImageMessageLiteInfo) message).thumbnailDownloadUrl);
                messageMap.putString("thumbnailLocalPath", ((ZIMImageMessageLiteInfo) message).thumbnailLocalPath);
                messageMap.putString(
                    "largeImageDownloadUrl",
                    ((ZIMImageMessageLiteInfo) message).largeImageDownloadUrl
                );
                messageMap.putString("largeImageLocalPath", ((ZIMImageMessageLiteInfo) message).largeImageLocalPath);
                messageMap.putInt("originalImageWidth", ((ZIMImageMessageLiteInfo) message).originalImageWidth);
                messageMap.putInt("originalImageHeight", ((ZIMImageMessageLiteInfo) message).originalImageHeight);
                messageMap.putInt("thumbnailWidth", ((ZIMImageMessageLiteInfo) message).thumbnailWidth);
                messageMap.putInt("thumbnailHeight", ((ZIMImageMessageLiteInfo) message).thumbnailHeight);
                messageMap.putInt("largeImageWidth", ((ZIMImageMessageLiteInfo) message).largeImageWidth);
                messageMap.putInt("largeImageHeight", ((ZIMImageMessageLiteInfo) message).largeImageHeight);
                break;
            case VIDEO:
                assert message instanceof ZIMVideoMessageLiteInfo;

                messageMap.putDouble("videoDuration", ((ZIMVideoMessageLiteInfo) message).videoDuration);
                messageMap.putString(
                    "videoFirstFrameDownloadUrl",
                    ((ZIMVideoMessageLiteInfo) message).videoFirstFrameDownloadUrl
                );
                messageMap.putString(
                    "videoFirstFrameLocalPath",
                    ((ZIMVideoMessageLiteInfo) message).videoFirstFrameLocalPath
                );
                messageMap.putInt("videoFirstFrameWidth", ((ZIMVideoMessageLiteInfo) message).videoFirstFrameWidth);
                messageMap.putInt("videoFirstFrameHeight", ((ZIMVideoMessageLiteInfo) message).videoFirstFrameHeight);
                break;
            case AUDIO:
                assert message instanceof ZIMAudioMessageLiteInfo;
                messageMap.putDouble("audioDuration", ((ZIMAudioMessageLiteInfo) message).audioDuration);
                break;
            case MULTIPLE:
                assert message instanceof ZIMMultipleMessageLiteInfo;
                WritableArray infosArray = Arguments.createArray();
                for (ZIMMessageLiteInfo info : ((ZIMMultipleMessageLiteInfo) message).messageInfoList) {
                    infosArray.pushMap(convertLiteInfo(info));
                }
                messageMap.putArray("messageInfoList", infosArray);
                break;    
            default:
                break;
        }

        if (message instanceof ZIMMediaMessageLiteInfo) {
            messageMap.putString("fileLocalPath", ((ZIMMediaMessageLiteInfo) message).fileLocalPath);
            messageMap.putString("fileDownloadUrl", ((ZIMMediaMessageLiteInfo) message).fileDownloadUrl);
            messageMap.putString("fileName", ((ZIMMediaMessageLiteInfo) message).fileName);
            messageMap.putDouble("fileSize", ((ZIMMediaMessageLiteInfo) message).fileSize);
        }

        return messageMap;
    }

    public static ZIMMessageLiteInfo convertLiteInfoFromJsToJava(HashMap<String, Object> messageMap) {
        if (messageMap == null) {
            return null;
        }

        ZIMMessageLiteInfo message = null;
        ZIMMessageType type = ZIMMessageType.getZIMMessageType(
            Double.valueOf((double) messageMap.get("type")).intValue()
        );
        switch (type) {
            case TEXT:
                message = new ZIMTextMessageLiteInfo();
                ((ZIMTextMessageLiteInfo) message).message = (String) messageMap.get("message");
                break;
            case REVOKE:
                message = new ZIMRevokeMessageLiteInfo();
                break;
            case CUSTOM:
                message = new ZIMCustomMessageLiteInfo();
                if (messageMap.containsKey("subType")) {
                    ((ZIMCustomMessageLiteInfo) message).subType = Double.valueOf((double) messageMap.get("subType")).intValue();
                }
                if (messageMap.containsKey("message")) {
                    ((ZIMCustomMessageLiteInfo) message).message = (String) messageMap.get("message");
                }
                if (messageMap.containsKey("searchedContent")) {
                    ((ZIMCustomMessageLiteInfo) message).searchedContent = (String) messageMap.get("searchedContent");
                }
                break;
            case COMBINE:
                message = new ZIMCombineMessageLiteInfo();
                if (messageMap.containsKey("title")) {
                    ((ZIMCombineMessageLiteInfo) message).title = (String) messageMap.get("title");
                }
                if (messageMap.containsKey("summary")) {
                    ((ZIMCombineMessageLiteInfo) message).summary = (String) messageMap.get("summary");
                }
                break;
            case IMAGE:
                message = new ZIMImageMessageLiteInfo();
                if (messageMap.containsKey("originalImageWidth")) {
                    ((ZIMImageMessageLiteInfo) message).originalImageWidth = Double.valueOf((double) messageMap.get("originalImageWidth")).intValue();    
                }
                if (messageMap.containsKey("originalImageHeight")) {
                    ((ZIMImageMessageLiteInfo) message).originalImageHeight = Double.valueOf((double) messageMap.get("originalImageHeight")).intValue();    
                }
                if (messageMap.containsKey("thumbnailWidth")) {
                    ((ZIMImageMessageLiteInfo) message).thumbnailWidth = Double.valueOf((double) messageMap.get("thumbnailWidth")).intValue();    
                }
                if (messageMap.containsKey("thumbnailHeight")) {
                    ((ZIMImageMessageLiteInfo) message).thumbnailHeight = Double.valueOf((double) messageMap.get("thumbnailHeight")).intValue();    
                }
                if (messageMap.containsKey("largeImageWidth")) {
                    ((ZIMImageMessageLiteInfo) message).largeImageWidth = Double.valueOf((double) messageMap.get("largeImageWidth")).intValue();    
                }
                if (messageMap.containsKey("largeImageHeight")) {
                    ((ZIMImageMessageLiteInfo) message).largeImageHeight = Double.valueOf((double) messageMap.get("largeImageHeight")).intValue();    
                }
                if (messageMap.containsKey("thumbnailDownloadUrl")) {
                    ((ZIMImageMessageLiteInfo) message).thumbnailDownloadUrl = (String) messageMap.get("thumbnailDownloadUrl");
                }
                if (messageMap.containsKey("thumbnailLocalPath")) {
                    ((ZIMImageMessageLiteInfo) message).thumbnailLocalPath = (String) messageMap.get("thumbnailLocalPath");
                }
                if (messageMap.containsKey("largeImageDownloadUrl")) {
                    ((ZIMImageMessageLiteInfo) message).largeImageDownloadUrl = (String) messageMap.get("largeImageDownloadUrl");
                }
                if (messageMap.containsKey("largeImageLocalPath")) {
                    ((ZIMImageMessageLiteInfo) message).largeImageLocalPath = (String) messageMap.get("largeImageLocalPath");
                }
                break;
            case FILE:
                message = new ZIMFileMessageLiteInfo();
                break;
            case VIDEO:
                message = new ZIMVideoMessageLiteInfo();
                if (messageMap.containsKey("videoFirstFrameDownloadUrl")) {
                    ((ZIMVideoMessageLiteInfo) message).videoFirstFrameDownloadUrl = (String) messageMap.get("videoFirstFrameDownloadUrl");
                }
                if (messageMap.containsKey("videoFirstFrameLocalPath")) {
                    ((ZIMVideoMessageLiteInfo) message).videoFirstFrameLocalPath = (String) messageMap.get("videoFirstFrameLocalPath");
                }
                if (messageMap.containsKey("videoDuration")) {
                    ((ZIMVideoMessageLiteInfo) message).videoDuration = Double.valueOf((double) messageMap.get("videoDuration")).longValue();    
                }
                if (messageMap.containsKey("videoFirstFrameWidth")) {
                    ((ZIMVideoMessageLiteInfo) message).videoFirstFrameWidth = Double.valueOf((double) messageMap.get("videoFirstFrameWidth")).intValue();    
                }
                if (messageMap.containsKey("videoFirstFrameHeight")) {
                    ((ZIMVideoMessageLiteInfo) message).videoFirstFrameHeight = Double.valueOf((double) messageMap.get("videoFirstFrameHeight")).intValue();    
                }
                break;
            case AUDIO:
                message = new ZIMAudioMessageLiteInfo();
                if (messageMap.containsKey("audioDuration")) {
                    ((ZIMAudioMessageLiteInfo) message).audioDuration = Double.valueOf((double) messageMap.get("audioDuration")).longValue();    
                }
                break;
            case MULTIPLE:
                message = new ZIMMultipleMessageLiteInfo();
                if (messageMap.get("messageInfoList") != null) {
                    ArrayList<ZIMMessageLiteInfo> messageInfoList = new ArrayList<>();
                    for (Object messageObj : (ArrayList) messageMap.get("messageInfoList")) {
                        messageInfoList.add(convertLiteInfoFromJsToJava((HashMap<String, Object>) messageObj));
                    }
                    ((ZIMMultipleMessageLiteInfo) message).messageInfoList = messageInfoList;    
                }
                break;
            default:
                message = new ZIMMessageLiteInfo();
                break;
        }

        if (message instanceof ZIMMediaMessageLiteInfo) {
            if (messageMap.containsKey("fileSize")) {
                ((ZIMMediaMessageLiteInfo) message).fileSize = Double.valueOf((double) messageMap.get("fileSize")).longValue();
            }
            if (messageMap.containsKey("fileName")) {
                ((ZIMMediaMessageLiteInfo) message).fileName = (String) messageMap.get("fileName");
            }
            if (messageMap.containsKey("fileDownloadUrl")) {
                ((ZIMMediaMessageLiteInfo) message).fileDownloadUrl = (String) messageMap.get("fileDownloadUrl");
            }
            if (messageMap.containsKey("fileLocalPath")) {
                ((ZIMMediaMessageLiteInfo) message).fileLocalPath = (String) messageMap.get("fileLocalPath");
            }
        }

        return message;
    }

    public static WritableMap convertRepliedInfo(ZIMMessageRepliedInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap infoMap = Arguments.createMap();
        infoMap.putInt("state", info.state.value());
        infoMap.putDouble("messageSeq", info.messageSeq);
        infoMap.putString("messageID", String.valueOf(info.messageID));
        infoMap.putString("senderUserID", info.senderUserID);
        infoMap.putDouble("sentTime", info.sentTime);

        ZIMMessageLiteInfo message = info.messageInfo;
        if (message == null) {
            infoMap.putNull("messageInfo");
            return infoMap;
        }

        infoMap.putMap("messageInfo", convertLiteInfo(message));
        return infoMap;
    }

    public static ReadableArray convertMessageList(ArrayList<ZIMMessage> infos) {
        if (infos == null) {
            return null;
        }

        WritableArray infosArray = Arguments.createArray();
        for (ZIMMessage info : infos) {
            infosArray.pushMap(convertMessage(info));
        }

        return infosArray;
    }

    public static ReadableMap convertTipsMessageChangeInfo(ZIMTipsMessageChangeInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap infoMap = Arguments.createMap();
        infoMap.putInt("type", info.type.value());

        if (info instanceof ZIMTipsMessageGroupChangeInfo) {
            infoMap.putInt("groupDataFlag", ((ZIMTipsMessageGroupChangeInfo) info).groupDataFlag);
            infoMap.putString("groupName", ((ZIMTipsMessageGroupChangeInfo) info).groupName);
            infoMap.putString("groupNotice", ((ZIMTipsMessageGroupChangeInfo) info).groupNotice);
            infoMap.putString("groupAvatarUrl", ((ZIMTipsMessageGroupChangeInfo) info).groupAvatarUrl);
            infoMap.putMap(
                "groupMutedInfo",
                convertGroupMuteInfo(((ZIMTipsMessageGroupChangeInfo) info).groupMutedInfo)
            );
        } else if (info instanceof ZIMTipsMessageGroupMemberChangeInfo) {
            infoMap.putInt("memberRole", ((ZIMTipsMessageGroupMemberChangeInfo) info).memberRole);
            infoMap.putDouble("muteExpiredTime", ((ZIMTipsMessageGroupMemberChangeInfo) info).muteExpiredTime);
        }

        return infoMap;
    }

    public static ZIMTipsMessageChangeInfo convertTipsMessageChangeInfoFromJsToJava(ReadableMap map) {
        if (map == null) {
            return null;
        }

        ZIMTipsMessageChangeInfo info = null;
        ZIMTipsMessageChangeInfoType type = ZIMTipsMessageChangeInfoType.getZIMTipsMessageChangeInfoType(
            map.getInt("type")
        );
        switch (type) {
            case GROUP_DATA_CHANGED:
            case GROUP_NOTICE_CHANGED:
            case GROUP_NAME_CHANGED:
            case GROUP_AVATAR_URL_CHANGED:
            case GROUP_MUTE_CHANGED:
                info = new ZIMTipsMessageGroupChangeInfo();
                break;
            case GROUP_OWNER_TRANSFERRED:
            case GROUP_MEMBER_ROLE_CHANGED:
            case GROUP_MEMBER_MUTE_CHANGED:
                info = new ZIMTipsMessageGroupMemberChangeInfo();
                break;
            default:
                info = new ZIMTipsMessageChangeInfo();
                break;
        }

        return info;
    }

    public static ZIMTipsMessageChangeInfo convertTipsMessageChangeInfoFromJsToJava(HashMap<String, Object> map) {
        if (map == null) {
            return null;
        }

        ZIMTipsMessageChangeInfo info = null;
        ZIMTipsMessageChangeInfoType type = ZIMTipsMessageChangeInfoType.getZIMTipsMessageChangeInfoType(
            Double.valueOf((double) map.get("type")).intValue()
        );
        switch (type) {
            case GROUP_DATA_CHANGED:
            case GROUP_NOTICE_CHANGED:
            case GROUP_NAME_CHANGED:
            case GROUP_AVATAR_URL_CHANGED:
            case GROUP_MUTE_CHANGED:
                info = new ZIMTipsMessageGroupChangeInfo();
                break;
            case GROUP_OWNER_TRANSFERRED:
            case GROUP_MEMBER_ROLE_CHANGED:
            case GROUP_MEMBER_MUTE_CHANGED:
                info = new ZIMTipsMessageGroupMemberChangeInfo();
                break;
            default:
                info = new ZIMTipsMessageChangeInfo();
                break;
        }

        return info;
    }

    public static ReadableMap convertFullRoomInfo(ZIMRoomFullInfo roomFullInfo) {
        if (roomFullInfo == null) {
            return null;
        }

        WritableMap roomInfoMap = Arguments.createMap();
        WritableMap baseInfoMap = Arguments.createMap();
        baseInfoMap.putString("roomID", roomFullInfo.baseInfo.roomID);
        baseInfoMap.putString("roomName", roomFullInfo.baseInfo.roomName);
        roomInfoMap.putMap("baseInfo", baseInfoMap);

        return roomInfoMap;
    }

    public static ReadableMap convertGroupMuteInfo(ZIMGroupMuteInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putInt("mode", info.mode.value());
        map.putDouble("expiredTime", info.expiredTime);

        WritableArray roleArray = Arguments.createArray();
        for (int item : info.roles) {
            roleArray.pushInt(item);
        }
        map.putArray("roles", roleArray);

        return map;
    }

    public static ReadableMap convertGroupVerifyInfo(ZIMGroupVerifyInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putInt("joinMode", info.joinMode.value());
        map.putInt("inviteMode", info.inviteMode.value());
        map.putInt("beInviteMode", info.beInviteMode.value());

        return map;
    }

    public static ReadableMap convertGroupInfo(ZIMGroupInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putString("groupID", info.groupID);
        map.putString("groupName", info.groupName);
        map.putString("groupAvatarUrl", info.groupAvatarUrl);

        return map;
    }

    public static ReadableMap convertGroupFullInfo(ZIMGroupFullInfo groupFullInfo) {
        if (groupFullInfo == null) {
            return null;
        }

        WritableMap groupFullInfoMap = Arguments.createMap();
        groupFullInfoMap.putMap("baseInfo", convertGroupInfo(groupFullInfo.baseInfo));
        groupFullInfoMap.putString("groupAlias", groupFullInfo.groupAlias);
        groupFullInfoMap.putString("groupNotice", groupFullInfo.groupNotice);
        WritableMap groupAttrMap = Arguments.createMap();
        for (String key : groupFullInfo.groupAttributes.keySet()) {
            groupAttrMap.putString(key, groupFullInfo.groupAttributes.get(key));
        }
        groupFullInfoMap.putMap("groupAttributes", groupAttrMap);
        groupFullInfoMap.putInt("notificationStatus", groupFullInfo.notificationStatus.value());
        groupFullInfoMap.putInt("maxMemberCount", groupFullInfo.maxMemberCount);
        groupFullInfoMap.putDouble("createTime", groupFullInfo.createTime);
        groupFullInfoMap.putMap("mutedInfo", convertGroupMuteInfo(groupFullInfo.mutedInfo));
        groupFullInfoMap.putMap("verifyInfo", convertGroupVerifyInfo(groupFullInfo.verifyInfo));

        return groupFullInfoMap;
    }

    public static ReadableMap convertGroupMemberInfo(ZIMGroupMemberInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap infoMap = Arguments.createMap();
        infoMap.putString("userID", info.userID);
        infoMap.putString("userName", info.userName);
        infoMap.putString("userAvatarUrl", info.userAvatarUrl);
        infoMap.putInt("memberRole", info.memberRole);
        infoMap.putString("memberNickname", info.memberNickname);
        infoMap.putDouble("muteExpiredTime", info.muteExpiredTime);
        infoMap.putString("memberAvatarUrl", info.userAvatarUrl);

        if (info.groupEnterInfo != null) {
            WritableMap enterInfoMap = Arguments.createMap();
            enterInfoMap.putDouble("enterTime", info.groupEnterInfo.enterTime);
            enterInfoMap.putInt("enterType", info.groupEnterInfo.enterType.value());
            enterInfoMap.putMap("operatedUser", convertGroupMemberSimpleInfo(info.groupEnterInfo.operatedUser));
            infoMap.putMap("groupEnterInfo", enterInfoMap);
        }

        return infoMap;
    }

    public static ReadableMap convertUserOrGroupMember(ZIMUserInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap infoMap = Arguments.createMap();
        infoMap.putString("userID", info.userID);
        infoMap.putString("userName", info.userName);
        infoMap.putString("userAvatarUrl", info.userAvatarUrl);

        if (info instanceof ZIMGroupMemberSimpleInfo) {
            infoMap.putInt("memberRole", ((ZIMGroupMemberSimpleInfo) info).memberRole);
            infoMap.putString("memberNickname", ((ZIMGroupMemberSimpleInfo) info).memberNickname);
        }

        return infoMap;
    }

    public static ZIMUserInfo convertUserOrGroupMemberFromJsToJava(ReadableMap map) {
        if (map == null) {
            return null;
        }

        ZIMUserInfo info = map.hasKey("memberRole") ? new ZIMGroupMemberSimpleInfo() : new ZIMUserInfo();
        info.userID = map.hasKey("userID") ? map.getString("userID") : "";
        info.userName = map.hasKey("userName") ? map.getString("userName") : "";
        info.userAvatarUrl = map.hasKey("userAvatarUrl") ? map.getString("userAvatarUrl") : "";

        if (info instanceof ZIMGroupMemberSimpleInfo) {
            ((ZIMGroupMemberSimpleInfo) info).memberRole = map.getInt("memberRole");
            ((ZIMGroupMemberSimpleInfo) info).memberNickname =
                map.hasKey("memberNickname") ? map.getString("memberNickname") : "";
        }

        return info;
    }

    public static ZIMUserInfo convertUserOrGroupMemberFromJsToJava(HashMap<String, Object> map) {
        if (map == null) {
            return null;
        }

        ZIMUserInfo info = map.containsKey("memberRole") ? new ZIMGroupMemberSimpleInfo() : new ZIMUserInfo();
        info.userID = map.containsKey("userID") ? (String) map.get("userID") : "";
        info.userName = map.containsKey("userName") ? (String) map.get("userName") : "";
        info.userAvatarUrl = map.containsKey("userAvatarUrl") ? (String) map.get("userAvatarUrl") : "";

        if (info instanceof ZIMGroupMemberSimpleInfo) {
            ((ZIMGroupMemberSimpleInfo) info).memberRole =
                map.containsKey("memberRole") ? Double.valueOf((double) map.get("memberRole")).intValue() : 0;
            ((ZIMGroupMemberSimpleInfo) info).memberNickname =
                map.containsKey("memberNickname") ? (String) map.get("memberNickname") : "";
        }

        return info;
    }

    public static ReadableArray convertUserOrGroupMemberList(ArrayList<ZIMUserInfo> infos) {
        if (infos == null) {
            return null;
        }

        WritableArray array = Arguments.createArray();
        for (ZIMUserInfo info : infos) {
            array.pushMap(convertUserOrGroupMember(info));
        }

        return array;
    }

    public static ReadableMap convertGroupMemberSimpleInfo(ZIMGroupMemberSimpleInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap infoMap = Arguments.createMap();
        infoMap.putString("userID", info.userID);
        infoMap.putString("userName", info.userName);
        infoMap.putString("userAvatarUrl", info.userAvatarUrl);
        infoMap.putInt("memberRole", info.memberRole);
        infoMap.putString("memberNickname", info.memberNickname);

        return infoMap;
    }

    public static ReadableArray convertGroupMemberInfoList(ArrayList<ZIMGroupMemberInfo> infos) {
        if (infos == null) {
            return null;
        }

        WritableArray userArray = Arguments.createArray();
        for (ZIMGroupMemberInfo memberInfo : infos) {
            userArray.pushMap(convertGroupMemberInfo(memberInfo));
        }

        return userArray;
    }

    public static ReadableArray convertGroupList(ArrayList<ZIMGroup> groupList) {
        if (groupList == null) {
            return null;
        }

        WritableArray groupArray = Arguments.createArray();
        for (ZIMGroup group : groupList) {
            WritableMap groupMap = Arguments.createMap();
            groupMap.putMap("baseInfo", convertGroupInfo(group.baseInfo));
            groupMap.putString("groupAlias", group.groupAlias);
            groupMap.putInt("notificationStatus", group.notificationStatus.value());

            groupArray.pushMap(groupMap);
        }

        return groupArray;
    }

    public static ReadableArray convertCallUserList(ArrayList<ZIMCallUserInfo> callUserInfos) {
        if (callUserInfos == null) {
            return null;
        }

        WritableArray callUserArray = Arguments.createArray();
        for (ZIMCallUserInfo userInfo : callUserInfos) {
            WritableMap callUserMap = Arguments.createMap();
            callUserMap.putString("userID", userInfo.userID);
            callUserMap.putInt("state", userInfo.state.value());
            callUserMap.putString("extendedData", userInfo.extendedData);

            callUserArray.pushMap(callUserMap);
        }

        return callUserArray;
    }

    public static ReadableArray convertConversationChangeInfo(ArrayList<ZIMConversationChangeInfo> infoList) {
        if (infoList == null) {
            return null;
        }

        WritableArray convInfoArray = Arguments.createArray();
        for (ZIMConversationChangeInfo info : infoList) {
            WritableMap convInfoMap = Arguments.createMap();
            convInfoMap.putInt("event", info.event.value());
            convInfoMap.putMap("conversation", convertConversation(info.conversation));

            convInfoArray.pushMap(convInfoMap);
        }

        return convInfoArray;
    }

    public static ReadableMap convertGroupOperatedInfo(ZIMGroupOperatedInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap infoMap = Arguments.createMap();
        infoMap.putString("userID", info.userID);
        infoMap.putString("userName", info.userName);
        infoMap.putInt("memberRole", info.memberRole);
        infoMap.putString("memberNickname", info.memberNickname);
        infoMap.putMap("operatedUserInfo", convertGroupMemberInfo(info.operatedUserInfo));

        return infoMap;
    }

    public static ReadableMap convertRoomMemberAttributesInfo(ZIMRoomMemberAttributesInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap infoMap = Arguments.createMap();
        infoMap.putString("userID", info.userID);
        WritableMap attrMap = Arguments.createMap();
        for (String key : info.attributes.keySet()) {
            attrMap.putString(key, info.attributes.get(key));
        }
        infoMap.putMap("attributes", attrMap);

        return infoMap;
    }

    public static ReadableArray convertRoomMemberAttributesInfoList(ArrayList<ZIMRoomMemberAttributesInfo> infoList) {
        if (infoList == null) {
            return null;
        }

        WritableArray infoArray = Arguments.createArray();
        for (ZIMRoomMemberAttributesInfo info : infoList) {
            infoArray.pushMap(convertRoomMemberAttributesInfo(info));
        }

        return infoArray;
    }

    public static ReadableMap convertRoomMemberAttributesOperatedInfo(ZIMRoomMemberAttributesOperatedInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap infoMap = Arguments.createMap();
        infoMap.putMap("attributesInfo", convertRoomMemberAttributesInfo(info.attributesInfo));
        WritableArray errorKeyArray = Arguments.createArray();
        for (String errorKey : info.errorKeys) {
            errorKeyArray.pushString(errorKey);
        }
        infoMap.putArray("errorKeys", errorKeyArray);

        return infoMap;
    }

    public static ReadableArray convertRoomMemberAttributesOperatedInfoList(
        ArrayList<ZIMRoomMemberAttributesOperatedInfo> infoList
    ) {
        if (infoList == null) {
            return null;
        }

        WritableArray infoArray = Arguments.createArray();
        for (ZIMRoomMemberAttributesOperatedInfo info : infoList) {
            infoArray.pushMap(convertRoomMemberAttributesOperatedInfo(info));
        }

        return infoArray;
    }

    public static ReadableMap convertRoomMemberAttributesUpdateInfo(ZIMRoomMemberAttributesUpdateInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap infoMap = Arguments.createMap();
        infoMap.putMap("attributesInfo", convertRoomMemberAttributesInfo(info.attributesInfo));

        return infoMap;
    }

    public static ReadableArray convertRoomMemberAttributesUpdateInfoList(
        ArrayList<ZIMRoomMemberAttributesUpdateInfo> infoList
    ) {
        if (infoList == null) {
            return null;
        }

        WritableArray infoArray = Arguments.createArray();
        for (ZIMRoomMemberAttributesUpdateInfo info : infoList) {
            infoArray.pushMap(convertRoomMemberAttributesUpdateInfo(info));
        }

        return infoArray;
    }

    public static ReadableMap convertRoomOperatedInfo(ZIMRoomOperatedInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap infoMap = Arguments.createMap();
        infoMap.putString("userID", info.userID);

        return infoMap;
    }

    public static ReadableMap convertFriendInfo(ZIMFriendInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putString("userID", info.userID);
        map.putString("userName", info.userName);
        map.putString("userAvatarUrl", info.userAvatarUrl);
        map.putString("wording", info.wording);
        map.putString("friendAlias", info.friendAlias);
        map.putDouble("createTime", info.createTime);

        WritableMap attrMap = Arguments.createMap();
        if (info.friendAttributes != null) {
            for (String key : info.friendAttributes.keySet()) {
                attrMap.putString(key, info.friendAttributes.get(key));
            }
        }
        map.putMap("friendAttributes", attrMap);

        return map;
    }

    public static ReadableArray convertFriendInfoList(ArrayList<ZIMFriendInfo> infos) {
        if (infos == null) {
            return null;
        }

        WritableArray array = Arguments.createArray();
        for (ZIMFriendInfo info : infos) {
            array.pushMap(convertFriendInfo(info));
        }

        return array;
    }

    public static ReadableMap convertFriendApplication(ZIMFriendApplicationInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putInt("type", info.type.value());
        map.putInt("state", info.state.value());
        map.putString("wording", info.wording);
        map.putDouble("createTime", info.createTime);
        map.putDouble("updateTime", info.updateTime);
        map.putMap("applyUser", convertUserInfo(info.applyUser));

        return map;
    }

    public static ReadableMap convertGroupApplication(ZIMGroupApplicationInfo info) {
        if (info == null) {
            return null;
        }

        WritableMap map = Arguments.createMap();
        map.putInt("type", info.type.value());
        map.putInt("state", info.state.value());
        map.putString("wording", info.wording);
        map.putDouble("createTime", info.createTime);
        map.putDouble("updateTime", info.updateTime);
        map.putMap("applyUser", convertUserInfo(info.applyUser));
        map.putMap("groupInfo", convertGroupInfo(info.groupInfo));
        map.putMap("operatedUser", convertGroupMemberSimpleInfo(info.operatedUser));

        return map;
    }

    public static ReadableArray convertFriendApplicationList(ArrayList<ZIMFriendApplicationInfo> infos) {
        if (infos == null) {
            return null;
        }

        WritableArray array = Arguments.createArray();
        for (ZIMFriendApplicationInfo info : infos) {
            array.pushMap(convertFriendApplication(info));
        }

        return array;
    }

    public static ReadableArray convertGroupApplicationList(ArrayList<ZIMGroupApplicationInfo> infos) {
        if (infos == null) {
            return null;
        }

        WritableArray array = Arguments.createArray();
        for (ZIMGroupApplicationInfo info : infos) {
            array.pushMap(convertGroupApplication(info));
        }

        return array;
    }
}
