package com.chatsdk.dispatch;

import android.util.Log;
import com.chatsdk.common.ExtSdkCallback;
import com.chatsdk.common.ExtSdkMethodType;
import io.agora.CallBack;
import io.agora.ConversationListener;
import io.agora.MessageListener;
import io.agora.ValueCallBack;
import io.agora.chat.ChatClient;
import io.agora.chat.CmdMessageBody;
import io.agora.chat.CombineMessageBody;
import io.agora.chat.Conversation;
import io.agora.chat.ConversationFilter;
import io.agora.chat.CursorResult;
import io.agora.chat.CustomMessageBody;
import io.agora.chat.FetchMessageOption;
import io.agora.chat.FileMessageBody;
import io.agora.chat.GroupReadAck;
import io.agora.chat.ImageMessageBody;
import io.agora.chat.Language;
import io.agora.chat.LocationMessageBody;
import io.agora.chat.ChatMessage;
import io.agora.chat.MessageBody;
import io.agora.chat.MessagePinInfo;
import io.agora.chat.MessageReaction;
import io.agora.chat.MessageReactionChange;
import io.agora.chat.NormalFileMessageBody;
import io.agora.chat.RecallMessageInfo;
import io.agora.chat.TextMessageBody;
import io.agora.chat.VideoMessageBody;
import io.agora.chat.VoiceMessageBody;
import io.agora.exceptions.ChatException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class ExtSdkChatManagerWrapper extends ExtSdkWrapper {

    public static class SingleHolder {
        static ExtSdkChatManagerWrapper instance = new ExtSdkChatManagerWrapper();
    }

    public static ExtSdkChatManagerWrapper getInstance() { return ExtSdkChatManagerWrapper.SingleHolder.instance; }

    ExtSdkChatManagerWrapper() { registerEaseListener(); }

    public void sendMessage(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        final ChatMessage msg = ExtSdkMessageHelper.fromJson(param);
        msg.setMessageStatusCallback(new CallBack() {
            @Override
            public void onSuccess() {
                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(msg));
                map.put("localTime", msg.localTime());
                map.put("callbackType", ExtSdkMethodType.onMessageSuccess);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onProgress(int progress, String status) {

                Map<String, Object> map = new HashMap<>();
                map.put("progress", progress);
                map.put("localTime", msg.localTime());
                map.put("callbackType", ExtSdkMethodType.onMessageProgressUpdate);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onError(int code, String desc) {
                Map<String, Object> data = new HashMap<>();
                data.put("code", code);
                data.put("description", desc);

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(msg));
                map.put("localTime", msg.localTime());
                map.put("error", data);
                map.put("callbackType", ExtSdkMethodType.onMessageError);
                ExtSdkWrapper.onReceive(channelName, map);
            }
        });

        ChatClient.getInstance().chatManager().sendMessage(msg);
        onSuccess(result, channelName, ExtSdkMessageHelper.toJson(msg));
    }

    public void resendMessage(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        ChatMessage tempMsg = ExtSdkMessageHelper.fromJson(param);
        tempMsg.setStatus(ChatMessage.Status.CREATE);
        ChatMessage finalMsg = tempMsg;
        finalMsg.setMessageStatusCallback(new CallBack() {
            @Override
            public void onSuccess() {

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(finalMsg));
                map.put("localTime", finalMsg.localTime());
                map.put("callbackType", ExtSdkMethodType.onMessageSuccess);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onProgress(int progress, String status) {

                Map<String, Object> map = new HashMap<>();
                map.put("progress", progress);
                map.put("localTime", finalMsg.localTime());
                map.put("callbackType", ExtSdkMethodType.onMessageProgressUpdate);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onError(int code, String desc) {
                Map<String, Object> data = new HashMap<>();
                data.put("code", code);
                data.put("description", desc);

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(finalMsg));
                map.put("localTime", finalMsg.localTime());
                map.put("error", data);
                map.put("callbackType", ExtSdkMethodType.onMessageError);
                ExtSdkWrapper.onReceive(channelName, map);
            }
        });
        ChatClient.getInstance().chatManager().sendMessage(finalMsg);
        onSuccess(result, channelName, ExtSdkMessageHelper.toJson(finalMsg));
    }

    public void ackMessageRead(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String msgId = param.getString("msg_id");
        String to = param.getString("to");

        try {
            ChatClient.getInstance().chatManager().ackMessageRead(to, msgId);
            onSuccess(result, channelName, true);
        } catch (ChatException e) {
            onError(result, e, null);
        }
    }

    public void ackGroupMessageRead(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String msgId = param.getString("msg_id");
        String to = param.getString("group_id");
        String content = null;
        if (param.has("content")) {
            content = param.getString("content");
        }
        String finalContent = content;

        try {
            ChatClient.getInstance().chatManager().ackGroupMessageRead(to, msgId, finalContent);
            onSuccess(result, channelName, null);
        } catch (ChatException e) {
            onError(result, e, null);
        }
    }

    public void ackConversationRead(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String conversationId = param.getString("convId");

        try {
            ChatClient.getInstance().chatManager().ackConversationRead(conversationId);
            onSuccess(result, channelName, true);
        } catch (ChatException e) {
            onError(result, e, null);
        }
    }

    public void recallMessage(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String msgId = param.getString("msg_id");
        String ext;
        if (param.has("ext")) {
            ext = param.getString("ext");
        } else {
            ext = null;
        }
        try {
            ChatMessage msg = ChatClient.getInstance().chatManager().getMessage(msgId);
            if (ExtSdkWrapper.checkMessageParams(msg, channelName, result)) {
                return;
            }
            ChatClient.getInstance().chatManager().recallMessage(msg, ext);
            onSuccess(result, channelName, null);
        } catch (ChatException e) {
            onError(result, e, null);
        }
    }

    public void getMessage(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String msgId = param.getString("msg_id");

        ChatMessage msg = ChatClient.getInstance().chatManager().getMessage(msgId);
        if (ExtSdkWrapper.getMessageParams(msg, channelName, result)) {
            return;
        }
        onSuccess(result, channelName, ExtSdkMessageHelper.toJson(msg));
    }

    public void getConversation(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        Conversation conversation = this.getConversation(param);
        onSuccess(result, channelName, ExtSdkConversationHelper.toJson(conversation));
    }

    public void markAllChatMsgAsRead(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        ChatClient.getInstance().chatManager().markAllConversationsAsRead();

        onSuccess(result, channelName, null);
    }

    public void getUnreadMessageCount(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        int count = ChatClient.getInstance().chatManager().getUnreadMessageCount();

        onSuccess(result, channelName, count);
    }

    public void updateChatMessage(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        ChatMessage msg = ExtSdkMessageHelper.fromJson(param.getJSONObject("message"));
        ChatMessage dbMsg =
            ChatClient.getInstance().chatManager().getMessage(param.getJSONObject("message").getString("msgId"));
        if (ExtSdkWrapper.checkMessageParams(dbMsg, channelName, result)) {
            return;
        }
        this.mergeMessage(msg, dbMsg);

        boolean ret = ChatClient.getInstance().chatManager().updateMessage(dbMsg);
        if (ret) {
            onSuccess(result, channelName, ExtSdkMessageHelper.toJson(dbMsg));
        } else {
            onError(result, 1, "Failed to update the message.");
        }
    }

    public void importMessages(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        JSONArray ary = param.getJSONArray("messages");
        List<ChatMessage> messages = new ArrayList<>();
        for (int i = 0; i < ary.length(); i++) {
            JSONObject obj = ary.getJSONObject(i);
            messages.add(ExtSdkMessageHelper.fromJson(obj));
        }

        ChatClient.getInstance().chatManager().importMessages(messages);
        onSuccess(result, channelName, null);
    }

    public void downloadAttachmentInCombine(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        final ChatMessage finalMsg = ExtSdkMessageHelper.fromJson(param.getJSONObject("message"));
        finalMsg.setMessageStatusCallback(new CallBack() {
            @Override
            public void onSuccess() {

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(finalMsg));
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("callbackType", ExtSdkMethodType.onMessageSuccess);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onProgress(int progress, String status) {

                Map<String, Object> map = new HashMap<>();
                map.put("progress", progress);
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("callbackType", ExtSdkMethodType.onMessageProgressUpdate);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onError(int code, String desc) {
                Map<String, Object> data = new HashMap<>();
                data.put("code", code);
                data.put("description", desc);

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(finalMsg));
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("error", data);
                map.put("callbackType", ExtSdkMethodType.onMessageError);
                ExtSdkWrapper.onReceive(channelName, map);
            }
        });

        ChatClient.getInstance().chatManager().downloadAttachment(finalMsg);
        onSuccess(result, channelName, ExtSdkMessageHelper.toJson(finalMsg));
    }

    public void downloadThumbnailInCombine(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        final ChatMessage finalMsg = ExtSdkMessageHelper.fromJson(param.getJSONObject("message"));
        finalMsg.setMessageStatusCallback(new CallBack() {
            @Override
            public void onSuccess() {

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(finalMsg));
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("callbackType", ExtSdkMethodType.onMessageSuccess);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onProgress(int progress, String status) {

                Map<String, Object> map = new HashMap<>();
                map.put("progress", progress);
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("callbackType", ExtSdkMethodType.onMessageProgressUpdate);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onError(int code, String desc) {
                Map<String, Object> data = new HashMap<>();
                data.put("code", code);
                data.put("description", desc);

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(finalMsg));
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("error", data);
                map.put("callbackType", ExtSdkMethodType.onMessageError);
                ExtSdkWrapper.onReceive(channelName, map);
            }
        });

        ChatClient.getInstance().chatManager().downloadThumbnail(finalMsg);
        onSuccess(result, channelName, ExtSdkMessageHelper.toJson(finalMsg));
    }

    public void downloadAttachment(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        ChatMessage tempMsg = ExtSdkMessageHelper.fromJson(param.getJSONObject("message"));
        ChatMessage msg = ChatClient.getInstance().chatManager().getMessage(tempMsg.getMsgId());
        if (ExtSdkWrapper.checkMessageParams(msg, channelName, result)) {
            return;
        }
        ChatMessage finalMsg = msg;
        msg.setMessageStatusCallback(new CallBack() {
            @Override
            public void onSuccess() {

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(finalMsg));
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("callbackType", ExtSdkMethodType.onMessageSuccess);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onProgress(int progress, String status) {

                Map<String, Object> map = new HashMap<>();
                map.put("progress", progress);
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("callbackType", ExtSdkMethodType.onMessageProgressUpdate);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onError(int code, String desc) {
                Map<String, Object> data = new HashMap<>();
                data.put("code", code);
                data.put("description", desc);

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(finalMsg));
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("error", data);
                map.put("callbackType", ExtSdkMethodType.onMessageError);
                ExtSdkWrapper.onReceive(channelName, map);
            }
        });

        ChatClient.getInstance().chatManager().downloadAttachment(msg);
        onSuccess(result, channelName, ExtSdkMessageHelper.toJson(msg));
    }

    public void downloadThumbnail(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        ChatMessage tempMsg = ExtSdkMessageHelper.fromJson(param.getJSONObject("message"));
        ChatMessage msg = ChatClient.getInstance().chatManager().getMessage(tempMsg.getMsgId());
        if (ExtSdkWrapper.checkMessageParams(msg, channelName, result)) {
            return;
        }
        if (null == msg) {
            msg = tempMsg;
        }
        ChatMessage finalMsg = msg;
        msg.setMessageStatusCallback(new CallBack() {
            @Override
            public void onSuccess() {

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(finalMsg));
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("callbackType", ExtSdkMethodType.onMessageSuccess);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onProgress(int progress, String status) {

                Map<String, Object> map = new HashMap<>();
                map.put("progress", progress);
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("callbackType", ExtSdkMethodType.onMessageProgressUpdate);
                ExtSdkWrapper.onReceive(channelName, map);
            }

            @Override
            public void onError(int code, String desc) {
                Map<String, Object> data = new HashMap<>();
                data.put("code", code);
                data.put("description", desc);

                Map<String, Object> map = new HashMap<>();
                map.put("message", ExtSdkMessageHelper.toJson(finalMsg));
                map.put("localTime", finalMsg.localTime());
                map.put("msgId", finalMsg.getMsgId());
                map.put("error", data);
                map.put("callbackType", ExtSdkMethodType.onMessageError);
                ExtSdkWrapper.onReceive(channelName, map);
            }
        });

        ChatClient.getInstance().chatManager().downloadThumbnail(msg);
        onSuccess(result, channelName, ExtSdkMessageHelper.toJson(msg));
    }

    public void loadAllConversations(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {

        List<Conversation> list = ChatClient.getInstance().chatManager().getAllConversationsBySort();
        List<Map> conversations = new ArrayList<>();
        for (Conversation conversation : list) {
            conversations.add(ExtSdkConversationHelper.toJson(conversation));
        }
        onSuccess(result, channelName, conversations);
    }

    public void getConversationsFromServer(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {

        try {
            List<Conversation> list =
                new ArrayList<>(ChatClient.getInstance().chatManager().fetchConversationsFromServer().values());
            Collections.sort(
                list, (o1, o2) -> (o2.getLastMessage().getMsgTime() - o1.getLastMessage().getMsgTime() > 0 ? 1 : -1));
            List<Map> conversations = new ArrayList<>();
            for (Conversation conversation : list) {
                conversations.add(ExtSdkConversationHelper.toJson(conversation));
            }
            onSuccess(result, channelName, conversations);
        } catch (ChatException e) {
            onError(result, e, null);
        }
    }

    public void deleteConversation(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String conId = param.getString("convId");
        boolean isDelete = param.getBoolean("deleteMessages");

        boolean ret = ChatClient.getInstance().chatManager().deleteConversation(conId, isDelete);
        if (ret) {
            onSuccess(result, channelName, null);
        } else {
            onError(result, 1, "remove conversation is failed.");
        }
    }

    public void fetchHistoryMessages(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String conId = param.getString("convId");
        Conversation.ConversationType type = ExtSdkConversationHelper.typeFromInt(param.getInt("convType"));
        int pageSize = param.getInt("pageSize");
        String startMsgId = param.getString("startMsgId");
        int direction = param.getInt("direction");

        try {
            CursorResult<ChatMessage> cursorResult = ChatClient.getInstance().chatManager().fetchHistoryMessages(
                conId, type, pageSize, startMsgId,
                direction == 0 ? Conversation.SearchDirection.UP : Conversation.SearchDirection.DOWN);
            onSuccess(result, channelName, ExtSdkCursorResultHelper.toJson(cursorResult));
        } catch (ChatException e) {
            onError(result, e, null);
        }
    }

    public void fetchHistoryMessagesByOptions(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        String convId = param.getString("convId");
        Conversation.ConversationType type = ExtSdkConversationHelper.typeFromInt(param.getInt("convType"));
        String cursor = "";
        int pageSize = 20;
        FetchMessageOption option = new FetchMessageOption();
        cursor = param.getString("cursor");
        pageSize = param.getInt("pageSize");
        if (param.has("options")) {
            option = ExtSdkFetchMessageOptionHelper.fromJson(param.getJSONObject("options"));
        }

        ChatClient.getInstance().chatManager().asyncFetchHistoryMessages(
            convId, type, pageSize, cursor, option, new ValueCallBack<CursorResult<ChatMessage>>() {
                @Override
                public void onSuccess(CursorResult<ChatMessage> value) {
                    ExtSdkWrapper.onSuccess(result, channelName, ExtSdkCursorResultHelper.toJson(value));
                }

                @Override
                public void onError(int error, String errorMsg) {
                    ExtSdkWrapper.onError(result, error, errorMsg);
                }
            });
    }

    public void searchChatMsgFromDB(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String keywords = param.getString("keywords");
        long timeStamp = param.getLong("timeStamp");
        int count = param.getInt("maxCount");
        String from = param.getString("from");
        Conversation.SearchDirection direction = searchDirectionFromString(param.getString("direction"));
        Conversation.ChatMessageSearchScope scope;
        if (param.has("searchScope")) {
            scope = Conversation.ChatMessageSearchScope.values()[param.getInt("searchScope")];
        } else {
            scope = Conversation.ChatMessageSearchScope.ALL;
        }

        List<ChatMessage> msgList =
            ChatClient.getInstance().chatManager().searchMsgFromDB(keywords, timeStamp, count, from, direction, scope);
        List<Map> messages = new ArrayList<>();
        for (ChatMessage msg : msgList) {
            messages.add(ExtSdkMessageHelper.toJson(msg));
        }
        onSuccess(result, channelName, messages);
    }

    public void asyncFetchGroupAcks(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String msgId = param.getString("msg_id");
        String ackId = param.getString("ack_id");
        int pageSize = param.getInt("pageSize");
        ChatClient.getInstance().chatManager().asyncFetchGroupReadAcks(
            msgId, pageSize, ackId, new ValueCallBack<CursorResult<GroupReadAck>>() {
                @Override
                public void onSuccess(CursorResult<GroupReadAck> value) {
                    ExtSdkWrapper.onSuccess(result, channelName, ExtSdkCursorResultHelper.toJson(value));
                }

                @Override
                public void onError(int error, String errorMsg) {
                    ExtSdkWrapper.onError(result, error, errorMsg);
                }
            });
    }

    public void deleteRemoteConversation(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        String conversationId = param.getString("conversationId");
        Conversation.ConversationType type = typeFromInt(param.getInt("conversationType"));
        boolean isDeleteRemoteMessage = param.getBoolean("isDeleteRemoteMessage");
        ChatClient.getInstance().chatManager().deleteConversationFromServer(
            conversationId, type, isDeleteRemoteMessage, new CallBack() {
                @Override
                public void onSuccess() {
                    ExtSdkWrapper.onSuccess(result, channelName, null);
                }

                @Override
                public void onError(int code, String error) {
                    ExtSdkWrapper.onError(result, error, error);
                }

                @Override
                public void onProgress(int progress, String status) {}
            });
    }

    public void deleteMessagesBeforeTimestamp(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        long timestamp = param.getLong("timestamp");
        ChatClient.getInstance().chatManager().deleteMessagesBeforeTimestamp(timestamp, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, error, error);
            }
        });
    }

    private Conversation.ConversationType typeFromInt(int conversationType) {
        Conversation.ConversationType ret = Conversation.ConversationType.Chat;
        switch (conversationType) {
        case 0:
            ret = Conversation.ConversationType.Chat;
            break;
        case 1:
            ret = Conversation.ConversationType.GroupChat;
            break;
        case 2:
            ret = Conversation.ConversationType.ChatRoom;
            break;
        }
        return ret;
    }

    public void translateMessage(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        ChatMessage msg = ExtSdkMessageHelper.fromJson(param.getJSONObject("message"));
        List<String> list = new ArrayList<String>();
        if (param.has("languages")) {
            JSONArray array = param.getJSONArray("languages");
            for (int i = 0; i < array.length(); i++) {
                list.add(array.getString(i));
            }
        }

        ChatMessage dbMsg = ChatClient.getInstance().chatManager().getMessage(msg.getMsgId());
        if (ExtSdkWrapper.checkMessageParams(dbMsg, channelName, result)) {
            return;
        }
        ChatClient.getInstance().chatManager().translateMessage(dbMsg, list, new ValueCallBack<ChatMessage>() {
            @Override
            public void onSuccess(ChatMessage value) {
                Map<String, Object> data = new HashMap<>();
                data.put("message", ExtSdkMessageHelper.toJson(value));
                ExtSdkWrapper.onSuccess(result, channelName, data);
            }

            @Override
            public void onError(int error, String errorMsg) {
                ExtSdkWrapper.onError(result, error, errorMsg);
            }
        });
    }

    public void fetchSupportedLanguages(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        ChatClient.getInstance().chatManager().fetchSupportLanguages(new ValueCallBack<List<Language>>() {
            @Override
            public void onSuccess(List<Language> value) {
                List<Map> list = new ArrayList<>();
                for (Language language : value) {
                    list.add(ExtSdkLanguageHelper.toJson(language));
                }
                ExtSdkWrapper.onSuccess(result, channelName, list);
            }

            @Override
            public void onError(int error, String errorMsg) {
                ExtSdkWrapper.onError(result, error, errorMsg);
            }
        });
    }

    public void addReaction(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String reaction = param.getString("reaction");
        String msgId = param.getString("msgId");
        ChatClient.getInstance().chatManager().asyncAddReaction(msgId, reaction, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, code, error);
            }
        });
    }

    public void removeReaction(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String reaction = param.getString("reaction");
        String msgId = param.getString("msgId");
        ChatClient.getInstance().chatManager().asyncRemoveReaction(msgId, reaction, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, code, error);
            }
        });
    }

    public void fetchReactionList(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        List<String> msgIds = new ArrayList<>();
        JSONArray ja = param.getJSONArray("msgIds");
        for (int i = 0; i < ja.length(); i++) {
            msgIds.add(ja.getString(i));
        }
        String groupId = null;
        if (param.has("groupId")) {
            groupId = param.getString("groupId");
        }
        ChatMessage.ChatType type = ChatMessage.ChatType.Chat;
        int iType = param.getInt("chatType");
        if (iType == 0) {
            type = ChatMessage.ChatType.Chat;
        } else if (iType == 1) {
            type = ChatMessage.ChatType.GroupChat;
        } else {
            type = ChatMessage.ChatType.ChatRoom;
        }
        ChatClient.getInstance().chatManager().asyncGetReactionList(
            msgIds, type, groupId, new ValueCallBack<Map<String, List<MessageReaction>>>() {
                @Override
                public void onSuccess(Map<String, List<MessageReaction>> value) {
                    HashMap<String, List<Map<String, Object>>> map = new HashMap<>();
                    if (value != null) {
                        for (Map.Entry<String, List<MessageReaction>> entry : value.entrySet()) {
                            List<MessageReaction> list = entry.getValue();
                            ArrayList<Map<String, Object>> ary = new ArrayList<>();
                            for (int i = 0; i < list.size(); i++) {
                                ary.add(ExtSdkMessageReactionHelper.toJson(list.get(i)));
                            }
                            map.put(entry.getKey(), ary);
                        }
                    }
                    ExtSdkWrapper.onSuccess(result, channelName, map);
                }

                @Override
                public void onError(int error, String errorMsg) {
                    ExtSdkWrapper.onError(result, error, errorMsg);
                }
            });
    }

    public void fetchReactionDetail(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String msgId = param.getString("msgId");
        String reaction = param.getString("reaction");
        String cursor = null;
        if (param.has("cursor")) {
            cursor = param.getString("cursor");
        }
        int pageSize = 50;
        if (param.has("pageSize")) {
            pageSize = param.getInt("pageSize");
        }
        ChatClient.getInstance().chatManager().asyncGetReactionDetail(
            msgId, reaction, cursor, pageSize, new ValueCallBack<CursorResult<MessageReaction>>() {
                @Override
                public void onSuccess(CursorResult<MessageReaction> value) {
                    ExtSdkWrapper.onSuccess(result, channelName, ExtSdkCursorResultHelper.toJson(value));
                }

                @Override
                public void onError(int error, String errorMsg) {
                    ExtSdkWrapper.onError(result, error, errorMsg);
                }
            });
    }

    public void reportMessage(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String msgId = param.getString("msgId");
        String tag = param.getString("tag");
        String reason = param.getString("reason");
        ChatClient.getInstance().chatManager().asyncReportMessage(msgId, tag, reason, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, code, error);
            }
        });
    }

    public void fetchConversationsFromServerWithPage(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        int pageNum = param.getInt("pageNum");
        int pageSize = param.getInt("pageSize");
        ChatClient.getInstance().chatManager().asyncFetchConversationsFromServer(
            pageNum, pageSize, new ValueCallBack<Map<String, Conversation>>() {
                @Override
                public void onSuccess(Map<String, Conversation> value) {
                    ArrayList<Conversation> list = new ArrayList<>(value.values());
                    boolean retry = false;
                    List<Map> conversations = new ArrayList<>();
                    do {
                        try {
                            retry = false;
                            Collections.sort(list, new Comparator<Conversation>() {
                                @Override
                                public int compare(Conversation o1, Conversation o2) {
                                    if (o1 == null && o2 == null) {
                                        return 0;
                                    }
                                    if (o1.getLastMessage() == null) {
                                        return 1;
                                    }

                                    if (o2.getLastMessage() == null) {
                                        return -1;
                                    }

                                    if (o1.getLastMessage().getMsgTime() == o2.getLastMessage().getMsgTime()) {
                                        return 0;
                                    }

                                    return o2.getLastMessage().getMsgTime() - o1.getLastMessage().getMsgTime() > 0 ? 1
                                                                                                                   : -1;
                                }
                            });
                            for (Conversation conversation : list) {
                                conversations.add(ExtSdkConversationHelper.toJson(conversation));
                            }

                        } catch (IllegalArgumentException e) {
                            retry = true;
                        }
                    } while (retry);
                    ExtSdkWrapper.onSuccess(result, channelName, conversations);
                }

                @Override
                public void onError(int error, String errorMsg) {
                    ExtSdkWrapper.onError(result, error, errorMsg);
                }
            });
    }

    public void removeMessagesFromServerWithMsgIds(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        Conversation conversation = this.getConversation(param);

        JSONArray jsonArray = param.getJSONArray("msgIds");

        ArrayList<String> msgIds = new ArrayList<>();
        for (int i = 0; i < jsonArray.length(); i++) {
            msgIds.add((String)jsonArray.get(i));
        }

        conversation.removeMessagesFromServer(msgIds, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, code, error);
            }
        });
    }

    public void removeMessagesFromServerWithTs(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        Conversation conversation = this.getConversation(param);
        long timestamp = param.getLong("timestamp");
        conversation.removeMessagesFromServer(timestamp, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, code, error);
            }
        });
    }

    public void getConversationsFromServerWithCursor(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        String cursor = param.optString("cursor");
        int pageSize = param.optInt("pageSize");
        ChatClient.getInstance().chatManager().asyncFetchConversationsFromServer(
            pageSize, cursor, new ValueCallBack<CursorResult<Conversation>>() {
                @Override
                public void onSuccess(CursorResult<Conversation> emConversationCursorResult) {
                    ExtSdkWrapper.onSuccess(result, channelName,
                                            ExtSdkCursorResultHelper.toJson(emConversationCursorResult));
                }

                @Override
                public void onError(int i, String s) {
                    ExtSdkWrapper.onError(result, i, s);
                }
            });
    }

    public void getPinnedConversationsFromServerWithCursor(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        String cursor = param.optString("cursor");
        int pageSize = param.optInt("pageSize");
        ChatClient.getInstance().chatManager().asyncFetchPinnedConversationsFromServer(
            pageSize, cursor, new ValueCallBack<CursorResult<Conversation>>() {
                @Override
                public void onSuccess(CursorResult<Conversation> emConversationCursorResult) {
                    ExtSdkWrapper.onSuccess(result, channelName,
                                            ExtSdkCursorResultHelper.toJson(emConversationCursorResult));
                }

                @Override
                public void onError(int i, String s) {
                    ExtSdkWrapper.onError(result, i, s);
                }
            });
    }

    public void pinConversation(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String convId = param.optString("convId");
        Boolean isPinned = param.optBoolean("isPinned", false);
        ChatClient.getInstance().chatManager().asyncPinConversation(convId, isPinned, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int i, String s) {
                ExtSdkWrapper.onError(result, i, s);
            }
        });
    }

    public void modifyMessage(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String msgId = param.optString("msgId");
        MessageBody body = ExtSdkMessageBodyHelper.textBodyFromJson(param.optJSONObject("body"));
        ChatClient.getInstance().chatManager().asyncModifyMessage(msgId, body, new ValueCallBack<ChatMessage>() {
            @Override
            public void onSuccess(ChatMessage emMessage) {
                ExtSdkWrapper.onSuccess(result, channelName, ExtSdkMessageHelper.toJson(emMessage));
            }

            @Override
            public void onError(int i, String s) {
                ExtSdkWrapper.onError(result, i, s);
            }
        });
    }

    public void downloadAndParseCombineMessage(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        ChatMessage msg = ExtSdkMessageHelper.fromJson(param.optJSONObject("message"));
        ChatClient.getInstance().chatManager().downloadAndParseCombineMessage(
            msg, new ValueCallBack<List<ChatMessage>>() {
                @Override
                public void onSuccess(List<ChatMessage> emMessages) {
                    List<Map> messages = new ArrayList<>();
                    for (ChatMessage msg : emMessages) {
                        messages.add(ExtSdkMessageHelper.toJson(msg));
                    }
                    ExtSdkWrapper.onSuccess(result, channelName, messages);
                }

                @Override
                public void onError(int i, String s) {
                    ExtSdkWrapper.onError(result, i, s);
                }
            });
    }

    public void addRemoteAndLocalConversationsMark(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        JSONArray jsonArray = param.getJSONArray("convIds");
        ArrayList<String> convIds = new ArrayList<>();
        for (int i = 0; i < jsonArray.length(); i++) {
            convIds.add((String)jsonArray.get(i));
        }
        Conversation.EMMarkType mark = Conversation.EMMarkType.values()[param.getInt("mark")];
        ChatClient.getInstance().chatManager().asyncAddConversationMark(convIds, mark, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, code, error);
            }
        });
    }

    public void deleteRemoteAndLocalConversationsMark(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        JSONArray jsonArray = param.getJSONArray("convIds");
        ArrayList<String> convIds = new ArrayList<>();
        for (int i = 0; i < jsonArray.length(); i++) {
            convIds.add((String)jsonArray.get(i));
        }
        Conversation.EMMarkType mark = Conversation.EMMarkType.values()[param.getInt("mark")];
        ChatClient.getInstance().chatManager().asyncRemoveConversationMark(convIds, mark, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, code, error);
            }
        });
    }

    public void fetchConversationsByOptions(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        String cursor = ExtSdkConversationFilterHelper.cursor(param);
        Boolean isPinned = ExtSdkConversationFilterHelper.pinned(param);
        Boolean isMark = ExtSdkConversationFilterHelper.hasMark(param);
        int pageSize = ExtSdkConversationFilterHelper.pageSize(param);
        if (isPinned) {
            ChatClient.getInstance().chatManager().asyncFetchPinnedConversationsFromServer(
                pageSize, cursor, new ValueCallBack<CursorResult<Conversation>>() {
                    @Override
                    public void onSuccess(CursorResult<Conversation> emCursorResult) {
                        ExtSdkWrapper.onSuccess(result, channelName, ExtSdkCursorResultHelper.toJson(emCursorResult));
                    }

                    @Override
                    public void onError(int i, String s) {
                        ExtSdkWrapper.onError(result, i, s);
                    }
                });
            return;
        }

        if (isMark) {
            ConversationFilter filter = ExtSdkConversationFilterHelper.fromJson(param);
            ChatClient.getInstance().chatManager().asyncGetConversationsFromServerWithCursor(
                cursor, filter, new ValueCallBack<CursorResult<Conversation>>() {
                    @Override
                    public void onSuccess(CursorResult<Conversation> value) {
                        ExtSdkWrapper.onSuccess(result, channelName, ExtSdkCursorResultHelper.toJson(value));
                    }

                    @Override
                    public void onError(int error, String errorMsg) {
                        ExtSdkWrapper.onError(result, error, errorMsg);
                    }
                });
            return;
        }

        ChatClient.getInstance().chatManager().asyncFetchConversationsFromServer(
            pageSize, cursor, new ValueCallBack<CursorResult<Conversation>>() {
                @Override
                public void onSuccess(CursorResult<Conversation> emCursorResult) {
                    ExtSdkWrapper.onSuccess(result, channelName, ExtSdkCursorResultHelper.toJson(emCursorResult));
                }

                @Override
                public void onError(int i, String s) {
                    ExtSdkWrapper.onError(result, i, s);
                }
            });
    }

    public void deleteAllMessageAndConversation(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        Boolean clearServerData = param.getBoolean("clearServerData");
        ChatClient.getInstance().chatManager().asyncDeleteAllMsgsAndConversations(clearServerData, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, code, error);
            }
        });
    }

    public void pinMessage(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String msgId = param.getString("msgId");
        ChatClient.getInstance().chatManager().asyncPinMessage(msgId, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, code, error);
            }
        });
    }

    public void unpinMessage(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String msgId = param.getString("msgId");
        ChatClient.getInstance().chatManager().asyncUnPinMessage(msgId, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int code, String error) {
                ExtSdkWrapper.onError(result, code, error);
            }
        });
    }

    public void fetchPinnedMessages(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        String convId = param.getString("convId");
        ChatClient.getInstance().chatManager().asyncGetPinnedMessagesFromServer(
            convId, new ValueCallBack<List<ChatMessage>>() {
                @Override
                public void onSuccess(List<ChatMessage> value) {
                    List<Map> messages = new ArrayList<>();
                    for (ChatMessage msg : value) {
                        messages.add(ExtSdkMessageHelper.toJson(msg));
                    }
                    ExtSdkWrapper.onSuccess(result, channelName, messages);
                }

                @Override
                public void onError(int error, String errorMsg) {
                    ExtSdkWrapper.onError(result, error, errorMsg);
                }
            });
    }

    public void searchMessages(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        JSONArray typesJson = param.getJSONArray("types");
        Set<ChatMessage.Type> types = new HashSet<>();
        for (int i = 0; i < typesJson.length(); i++) {
            String type = typesJson.getString(i);
            types.add(ExtSdkChatMessageTypeHelper.toType(type));
        }
        long timestamp = param.getLong("timestamp");
        int count = param.getInt("count");
        String from = param.getString("from");
        Conversation.SearchDirection direction =
            ExtSdkSearchDirectionHelper.toDirection(param.getString("direction"));
        List<ChatMessage> msgs =
            ChatClient.getInstance().chatManager().searchMsgFromDB(types, timestamp, count, from, direction);
        List<Map<?, ?>> messages = new ArrayList<>();
        for (ChatMessage msg : msgs) {
            messages.add(ExtSdkMessageHelper.toJson(msg));
        }
        ExtSdkWrapper.onSuccess(result, channelName, messages);
    }

    public void removeMessagesWithTimestamp(JSONObject param, String channelName, ExtSdkCallback result)
        throws JSONException {
        Conversation conversation = this.getConversation(param);
        long timestamp = param.getLong("timestamp");
        conversation.removeMessagesFromServer(timestamp, new CallBack() {
            @Override
            public void onSuccess() {
                ExtSdkWrapper.onSuccess(result, channelName, null);
            }

            @Override
            public void onError(int i, String s) {
                ExtSdkWrapper.onError(result, i, s);
            }
        });
    }

    public void getMessageCount(JSONObject param, String channelName, ExtSdkCallback result) throws JSONException {
        ChatClient.getInstance().chatManager().asyncGetMessageCount(new ValueCallBack<Integer>() {
            @Override
            public void onSuccess(Integer value) {
                ExtSdkWrapper.onSuccess(result, channelName, value);
            }

            @Override
            public void onError(int i, String s) {
                ExtSdkWrapper.onError(result, i, s);
            }
        });
    }

    private void registerEaseListener() {
        if (this.messageListener != null) {
            ChatClient.getInstance().chatManager().removeMessageListener(this.messageListener);
        }
        this.messageListener = new MessageListener() {
            @Override
            public void onMessageReceived(List<ChatMessage> messages) {
                ArrayList<Map<String, Object>> msgList = new ArrayList<>();
                for (ChatMessage message : messages) {
                    msgList.add(ExtSdkMessageHelper.toJson(message));
                }
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onMessagesReceived, msgList);
            }

            @Override
            public void onCmdMessageReceived(List<ChatMessage> messages) {

                ArrayList<Map<String, Object>> msgList = new ArrayList<>();
                for (ChatMessage message : messages) {
                    msgList.add(ExtSdkMessageHelper.toJson(message));
                }
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onCmdMessagesReceived, msgList);
            }

            @Override
            public void onMessageRead(List<ChatMessage> messages) {
                ArrayList<Map<String, Object>> msgList = new ArrayList<>();
                for (ChatMessage message : messages) {
                    msgList.add(ExtSdkMessageHelper.toJson(message));
                    ExtSdkWrapper.onReceive(ExtSdkMethodType.onMessageReadAck, ExtSdkMessageHelper.toJson(message));
                }

                ExtSdkWrapper.onReceive(ExtSdkMethodType.onMessagesRead, msgList);
            }

            @Override
            public void onMessageDelivered(List<ChatMessage> messages) {
                ArrayList<Map<String, Object>> msgList = new ArrayList<>();
                for (ChatMessage message : messages) {
                    msgList.add(ExtSdkMessageHelper.toJson(message));
                    ExtSdkWrapper.onReceive(ExtSdkMethodType.onMessageDeliveryAck, ExtSdkMessageHelper.toJson(message));
                }
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onMessagesDelivered, msgList);
            }

            @Override
            public void onMessageRecalledWithExt(List<RecallMessageInfo> recallMessageInfo) {
                ArrayList<Map<String, Object>> msgList = new ArrayList<>();
                for (RecallMessageInfo info : recallMessageInfo) {
                    msgList.add(ExtSdkRecalledMessageInfoHelper.toJson(info));
                }
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onMessagesRecalledInfo, msgList);
            }

            @Override
            public void onGroupMessageRead(List<GroupReadAck> var1) {
                ArrayList<Map<String, Object>> msgList = new ArrayList<>();
                for (GroupReadAck ack : var1) {
                    msgList.add(ExtSdkGroupAckHelper.toJson(ack));
                }
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onGroupMessageRead, msgList);
            }

            @Override
            public void onReadAckForGroupMessageUpdated() {
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onReadAckForGroupMessageUpdated, null);
            }

            @Override
            public void onReactionChanged(List<MessageReactionChange> messageReactionChangeList) {
                ArrayList<Map<String, Object>> list = new ArrayList<>();
                for (MessageReactionChange change : messageReactionChangeList) {
                    list.add(ExtSdkMessageReactionChangeHelper.toJson(change));
                }
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onMessageReactionDidChange, list);
            }

            @Override
            public void onMessageContentChanged(ChatMessage messageModified, String operatorId, long operationTime) {
                Map msgMap = ExtSdkMessageHelper.toJson(messageModified);
                Map ret = new HashMap<>();
                ret.put("message", msgMap);
                ret.put("lastModifyOperatorId", operatorId);
                ret.put("lastModifyTime", operationTime);
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onMessageContentChanged, ret);
            }

            @Override
            public void onMessagePinChanged(String messageId, String conversationId,
                                            MessagePinInfo.PinOperation pinOperation, MessagePinInfo pinInfo) {
                Map map = new HashMap<>();
                map.put("messageId", messageId);
                map.put("conversationId", conversationId);
                map.put("pinOperation", pinOperation.ordinal());
                map.put("pinInfo", ExtSdkMessagePinInfoHelper.toJson(pinInfo));
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onMessagePinChanged, map);
            }
        };
        ChatClient.getInstance().chatManager().addMessageListener(this.messageListener);

        if (this.conversationListener != null) {
            ChatClient.getInstance().chatManager().removeConversationListener(this.conversationListener);
        }
        this.conversationListener = new ConversationListener() {
            @Override
            public void onConversationUpdate() {
                Map<String, Object> data = new HashMap<>();
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onConversationUpdate, data);
            }

            @Override
            public void onConversationRead(String from, String to) {
                Map<String, Object> data = new HashMap<>();
                data.put("from", from);
                data.put("to", to);
                ExtSdkWrapper.onReceive(ExtSdkMethodType.onConversationHasRead, data);
            }
        };
        ChatClient.getInstance().chatManager().addConversationListener(this.conversationListener);
    }

    private Conversation.SearchDirection searchDirectionFromString(String direction) {
        return direction.equals("up") ? Conversation.SearchDirection.UP : Conversation.SearchDirection.DOWN;
    }

    private MessageListener messageListener = null;
    private ConversationListener conversationListener = null;
}
