package info.guardianproject.keanuapp.ui;

/*
 * Copyright 2016 OpenMarket Ltd
 * Copyright 2017 Vector Creations Ltd
 * Copyright 2018 New Vector Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.v4.util.LruCache;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewParent;
import android.webkit.WebView;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ImageView;

import org.matrix.androidsdk.MXSession;
import org.matrix.androidsdk.call.MXCallsManager;
import org.matrix.androidsdk.data.Room;
import org.matrix.androidsdk.data.RoomPreviewData;
import org.matrix.androidsdk.db.MXMediaCache;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
import org.matrix.androidsdk.rest.model.MatrixError;
import org.matrix.androidsdk.rest.model.RoomMember;
import org.matrix.androidsdk.rest.model.User;
import org.matrix.androidsdk.rest.model.group.Group;
import org.matrix.androidsdk.rest.model.publicroom.PublicRoom;
import org.matrix.androidsdk.util.ImageUtils;
import org.matrix.androidsdk.util.Log;
import org.matrix.androidsdk.util.ResourceUtils;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;

import info.guardianproject.keanuapp.ImApp;
import info.guardianproject.keanuapp.R;


public class VectorUtils {

    private static final String LOG_TAG = VectorUtils.class.getSimpleName();

    //public static final int REQUEST_FILES = 0;
    public static final int TAKE_IMAGE = 1;

    //==============================================================================================================
    // Rooms methods
    //==============================================================================================================

    /**
     * Returns the public room display name..
     *
     * @param publicRoom the public room.
     * @return the room display name.
     */
    public static String getPublicRoomDisplayName(PublicRoom publicRoom) {
        String displayName = publicRoom.name;

        if (TextUtils.isEmpty(displayName)) {
            if (publicRoom.aliases != null && !publicRoom.aliases.isEmpty()) {
                displayName = publicRoom.aliases.get(0);
            } else {
                displayName = publicRoom.roomId;
            }
        } else if (!displayName.startsWith("#") && publicRoom.aliases != null && !publicRoom.aliases.isEmpty()) {
            displayName = displayName + " (" + publicRoom.aliases.get(0) + ")";
        }

        return displayName;
    }

    /**
     * Provide a display name for a calling room
     *
     * @param context the application context.
     * @param session the room session.
     * @param room    the room.
     * @return the calling room display name.
     */
    @Nullable
    public static void getCallingRoomDisplayName(Context context,
                                                 final MXSession session,
                                                 final Room room,
                                                 final ApiCallback<String> callback) {
        if ((null == context) || (null == session) || (null == room)) {
            callback.onSuccess(null);
        } else if (room.getNumberOfJoinedMembers() == 2) {
            room.getJoinedMembersAsync(new SimpleApiCallback<List<RoomMember>>(callback) {
                @Override
                public void onSuccess(List<RoomMember> members) {
                    if (TextUtils.equals(members.get(0).getUserId(), session.getMyUserId())) {
                        callback.onSuccess(room.getState().getMemberName(members.get(1).getUserId()));
                    } else {
                        callback.onSuccess(room.getState().getMemberName(members.get(0).getUserId()));
                    }
                }
            });
        } else {
            callback.onSuccess(room.getRoomDisplayName(context));
        }
    }

    //==============================================================================================================
    // Avatars generation
    //==============================================================================================================

    // avatars cache
    static final private LruCache<String, Bitmap> mAvatarImageByKeyDict = new LruCache<>(20 * 1024 * 1024);
    // the avatars background color
    static final private List<Integer> mColorList = new ArrayList<>(Arrays.asList(0xff76cfa6, 0xff50e2c2, 0xfff4c371));

    /**
     * Provides the avatar background color from a text.
     *
     * @param text the text.
     * @return the color.
     */
    public static int getAvatarColor(String text) {
        long colorIndex = 0;

        if (!TextUtils.isEmpty(text)) {
            long sum = 0;

            for (int i = 0; i < text.length(); i++) {
                sum += text.charAt(i);
            }

            colorIndex = sum % mColorList.size();
        }

        return mColorList.get((int) colorIndex);
    }

    /**
     * Create a thumbnail avatar.
     *
     * @param context         the context
     * @param backgroundColor the background color
     * @param text            the text to display.
     * @return the generated bitmap
     */
    private static Bitmap createAvatarThumbnail(Context context, int backgroundColor, String text) {
        float densityScale = context.getResources().getDisplayMetrics().density;
        // the avatar size is 42dp, convert it in pixels.
        return createAvatar(backgroundColor, text, (int) (42 * densityScale));
    }

    /**
     * Create an avatar bitmap from a text.
     *
     * @param backgroundColor the background color.
     * @param text            the text to display.
     * @param pixelsSide      the avatar side in pixels
     * @return the generated bitmap
     */
    private static Bitmap createAvatar(int backgroundColor, String text, int pixelsSide) {
        Bitmap.Config bitmapConfig = Bitmap.Config.ARGB_8888;

        Bitmap bitmap = Bitmap.createBitmap(pixelsSide, pixelsSide, bitmapConfig);
        Canvas canvas = new Canvas(bitmap);

        canvas.drawColor(backgroundColor);

        // prepare the text drawing
        Paint textPaint = new Paint();
        textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
        textPaint.setColor(Color.WHITE);
        textPaint.setTextAlign(Paint.Align.CENTER);
        // the text size is proportional to the avatar size.
        // by default, the avatar size is 42dp, the text size is 28 dp (not sp because it has to be fixed).
        textPaint.setTextSize(pixelsSide * 2 / 3);

        // get its size
        Rect textBounds = new Rect();
        textPaint.getTextBounds(text, 0, text.length(), textBounds);

        // draw the text in center
        canvas.drawText(text,
                canvas.getWidth() / 2,
                (canvas.getHeight() - textBounds.top) / 2,
                textPaint);

        // Return the avatar
        return bitmap;
    }

    /**
     * Return the char to display for a name
     *
     * @param name the name
     * @return teh first char
     */
    private static String getInitialLetter(String name) {
        String firstChar = " ";

        if (!TextUtils.isEmpty(name)) {
            int idx = 0;
            char initial = name.charAt(idx);

            if ((initial == '@' || initial == '#' || initial == '+') && (name.length() > 1)) {
                idx++;
            }

            // string.codePointAt(0) would do this, but that isn't supported by
            // some browsers (notably PhantomJS).
            int chars = 1;
            char first = name.charAt(idx);

            // LEFT-TO-RIGHT MARK
            if ((name.length() >= 2) && (0x200e == first)) {
                idx++;
                first = name.charAt(idx);
            }

            // check if it’s the start of a surrogate pair
            if (0xD800 <= first && first <= 0xDBFF && (name.length() > (idx + 1))) {
                char second = name.charAt(idx + 1);
                if (0xDC00 <= second && second <= 0xDFFF) {
                    chars++;
                }
            }

            firstChar = name.substring(idx, idx + chars);
        }

        return firstChar.toUpperCase(Locale.getDefault());
    }

    /**
     * Returns an avatar from a text.
     *
     * @param context the context.
     * @param aText   the text.
     * @param create  create the avatar if it does not exist
     * @return the avatar.
     */
    public static Bitmap getAvatar(Context context, int backgroundColor, String aText, boolean create) {
        String firstChar = getInitialLetter(aText);
        String key = firstChar + "_" + backgroundColor;

        // check if the avatar is already defined
        Bitmap thumbnail = mAvatarImageByKeyDict.get(key);

        if ((null == thumbnail) && create) {
            thumbnail = VectorUtils.createAvatarThumbnail(context, backgroundColor, firstChar);
            mAvatarImageByKeyDict.put(key, thumbnail);
        }

        return thumbnail;
    }

    /**
     * Set the default vector avatar for a member.
     *
     * @param imageView   the imageView to set.
     * @param userId      the member userId.
     * @param displayName the member display name.
     */
    private static void setDefaultMemberAvatar(final ImageView imageView, final String userId, final String displayName) {
        // sanity checks
        if (null != imageView && !TextUtils.isEmpty(userId)) {
            final Bitmap bitmap = VectorUtils.getAvatar(imageView.getContext(),
                    VectorUtils.getAvatarColor(userId), TextUtils.isEmpty(displayName) ? userId : displayName, true);

            if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
                imageView.setImageBitmap(bitmap);
            } else {
                final String tag = userId + " - " + displayName;
                imageView.setTag(tag);

                mUIHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (TextUtils.equals(tag, (String) imageView.getTag())) {
                            imageView.setImageBitmap(bitmap);
                        }
                    }
                });
            }
        }
    }

    /**
     * Set the room avatar in an imageView.
     *
     * @param context   the context
     * @param session   the session
     * @param imageView the image view
     * @param room      the room
     */
    public static void loadRoomAvatar(Context context, MXSession session, ImageView imageView, Room room) {
        if (null != room) {
            VectorUtils.loadUserAvatar(context,
                    session, imageView, room.getAvatarUrl(), room.getRoomId(), room.getRoomDisplayName(context));
        }
    }

    /**
     * Set the room avatar in an imageView by consider the room preview data.
     *
     * @param context         the context
     * @param session         the session
     * @param imageView       the image view
     * @param roomPreviewData the room preview
     */
    public static void loadRoomAvatar(Context context, MXSession session, ImageView imageView, RoomPreviewData roomPreviewData) {
        if (null != roomPreviewData) {
            VectorUtils.loadUserAvatar(context,
                    session, imageView, roomPreviewData.getRoomAvatarUrl(), roomPreviewData.getRoomId(), roomPreviewData.getRoomName());
        }
    }

    /**
     * Set the group avatar in an imageView.
     *
     * @param context   the context
     * @param session   the session
     * @param imageView the image view
     * @param group     the group
     */
    public static void loadGroupAvatar(Context context, MXSession session, ImageView imageView, Group group) {
        if (null != group) {
            VectorUtils.loadUserAvatar(context,
                    session, imageView, group.getAvatarUrl(), group.getGroupId(), group.getDisplayName());
        }
    }

    /**
     * Set the call avatar in an imageView.
     *
     * @param context   the context
     * @param session   the session
     * @param imageView the image view
     * @param room      the room
     */
    public static void loadCallAvatar(Context context, MXSession session, ImageView imageView, Room room) {
        // sanity check
        if ((null != room) && (null != session) && (null != imageView) && session.isAlive()) {
            // reset the imageView tag
            imageView.setTag(null);

            String callAvatarUrl = room.getCallAvatarUrl();
            String roomId = room.getRoomId();
            String displayName = room.getRoomDisplayName(context);
            int pixelsSide = imageView.getLayoutParams().width;

            // when size < 0, it means that the render graph must compute it
            // so, we search the valid parent view with valid size
            if (pixelsSide < 0) {
                ViewParent parent = imageView.getParent();

                while ((pixelsSide < 0) && (null != parent)) {
                    if (parent instanceof View) {
                        View parentAsView = (View) parent;
                        pixelsSide = parentAsView.getLayoutParams().width;
                    }
                    parent = parent.getParent();
                }
            }

            // if the avatar is already cached, use it
            if (session.getMediaCache().isAvatarThumbnailCached(callAvatarUrl, context.getResources().getDimensionPixelSize(R.dimen.profile_avatar_size))) {
                session.getMediaCache().loadAvatarThumbnail(session.getHomeServerConfig(),
                        imageView, callAvatarUrl, context.getResources().getDimensionPixelSize(R.dimen.profile_avatar_size));
            } else {
                Bitmap bitmap = null;

                if (pixelsSide > 0) {
                    // get the avatar bitmap.
                    bitmap = VectorUtils.createAvatar(VectorUtils.getAvatarColor(roomId), getInitialLetter(displayName), pixelsSide);
                }

                // until the dedicated avatar is loaded.
                session.getMediaCache().loadAvatarThumbnail(session.getHomeServerConfig(),
                        imageView, callAvatarUrl, context.getResources().getDimensionPixelSize(R.dimen.profile_avatar_size), bitmap);
            }
        }
    }

    /**
     * Set the room member avatar in an imageView.
     *
     * @param context    the context
     * @param session    the session
     * @param imageView  the image view
     * @param roomMember the room member
     */
    public static void loadRoomMemberAvatar(Context context, MXSession session, ImageView imageView, RoomMember roomMember) {
        if (null != roomMember) {
            VectorUtils.loadUserAvatar(context, session, imageView, roomMember.getAvatarUrl(), roomMember.getUserId(), roomMember.displayname);
        }
    }

    /**
     * Set the user avatar in an imageView.
     *
     * @param context   the context
     * @param session   the session
     * @param imageView the image view
     * @param user      the user
     */
    public static void loadUserAvatar(Context context, MXSession session, ImageView imageView, User user) {
        if (null != user) {
            VectorUtils.loadUserAvatar(context, session, imageView, user.getAvatarUrl(), user.user_id, user.displayname);
        }
    }

    // the background thread
    private static HandlerThread mImagesThread = null;
    private static android.os.Handler mImagesThreadHandler = null;
    private static Handler mUIHandler = null;

    /**
     * Set the user avatar in an imageView.
     *
     * @param context     the context
     * @param session     the session
     * @param imageView   the image view
     * @param avatarUrl   the avatar url
     * @param userId      the user id
     * @param displayName the user display name
     */
    public static void loadUserAvatar(final Context context,
                                      final MXSession session,
                                      final ImageView imageView,
                                      final String avatarUrl,
                                      final String userId,
                                      final String displayName) {
        // sanity check
        if ((null == session) || (null == imageView) || !session.isAlive()) {
            return;
        }

        // reset the imageView tag
        imageView.setTag(null);

        if (session.getMediaCache().isAvatarThumbnailCached(avatarUrl, context.getResources().getDimensionPixelSize(R.dimen.profile_avatar_size))) {
            session.getMediaCache().loadAvatarThumbnail(session.getHomeServerConfig(),
                    imageView, avatarUrl, context.getResources().getDimensionPixelSize(R.dimen.profile_avatar_size));
        } else {
            if (null == mImagesThread) {
                mImagesThread = new HandlerThread("ImagesThread", Thread.MIN_PRIORITY);
                mImagesThread.start();
                mImagesThreadHandler = new android.os.Handler(mImagesThread.getLooper());
                mUIHandler = new Handler(Looper.getMainLooper());
            }

            final Bitmap bitmap = VectorUtils.getAvatar(imageView.getContext(),
                    VectorUtils.getAvatarColor(userId), TextUtils.isEmpty(displayName) ? userId : displayName, false);

            // test if the default avatar has been computed
            if (null != bitmap) {
                imageView.setImageBitmap(bitmap);

                if (!TextUtils.isEmpty(avatarUrl)) {
                    final String tag = avatarUrl + userId + displayName;
                    imageView.setTag(tag);

                    if (!MXMediaCache.isMediaUrlUnreachable(avatarUrl)) {
                        mImagesThreadHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                if (TextUtils.equals(tag, (String) imageView.getTag())) {
                                    session.getMediaCache().loadAvatarThumbnail(session.getHomeServerConfig(),
                                            imageView, avatarUrl, context.getResources().getDimensionPixelSize(R.dimen.profile_avatar_size), bitmap);
                                }
                            }
                        });
                    }
                }
            } else {
                final String tmpTag0 = "00" + avatarUrl + "-" + userId + "--" + displayName;
                imageView.setTag(tmpTag0);

                // create the default avatar in the background thread
                mImagesThreadHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (TextUtils.equals(tmpTag0, (String) imageView.getTag())) {
                            imageView.setTag(null);
                            setDefaultMemberAvatar(imageView, userId, displayName);

                            if (!TextUtils.isEmpty(avatarUrl) && !MXMediaCache.isMediaUrlUnreachable(avatarUrl)) {
                                final String tmpTag1 = "11" + avatarUrl + "-" + userId + "--" + displayName;
                                imageView.setTag(tmpTag1);

                                // wait that it is rendered to load the right one
                                mUIHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        // test if the imageView tag has not been updated
                                        if (TextUtils.equals(tmpTag1, (String) imageView.getTag())) {
                                            final String tmptag2 = "22" + avatarUrl + userId + displayName;
                                            imageView.setTag(tmptag2);

                                            mImagesThreadHandler.post(new Runnable() {
                                                @Override
                                                public void run() {
                                                    // test if the imageView tag has not been updated
                                                    if (TextUtils.equals(tmptag2, (String) imageView.getTag())) {
                                                        final Bitmap bitmap = VectorUtils.getAvatar(imageView.getContext(),
                                                                VectorUtils.getAvatarColor(userId),
                                                                TextUtils.isEmpty(displayName) ? userId : displayName,
                                                                false);
                                                        session.getMediaCache().loadAvatarThumbnail(session.getHomeServerConfig(),
                                                                imageView,
                                                                avatarUrl,
                                                                context.getResources().getDimensionPixelSize(R.dimen.profile_avatar_size),
                                                                bitmap);
                                                    }
                                                }
                                            });
                                        }
                                    }
                                });
                            }
                        }
                    }
                });
            }
        }
    }

    //==============================================================================================================
    // About / terms and conditions
    //==============================================================================================================



    /**
     * Open a web view above the current activity.
     *
     * @param context the application context
     * @param url     the url to open
     */
    private static void displayInWebView(final Context context, String url) {
        WebView wv = new WebView(context);
        wv.loadUrl(url);
        new AlertDialog.Builder(context)
                .setView(wv)
                .setPositiveButton(android.R.string.ok, null)
                .show();
    }




    //==============================================================================================================
    // List uris from intent
    //==============================================================================================================

    /**
     * Return a selected bitmap from an intent.
     *
     * @param intent the intent
     * @return the bitmap uri
     */
    @SuppressLint("NewApi")
    public static Uri getThumbnailUriFromIntent(Context context, final Intent intent, MXMediaCache mediasCache) {
        // sanity check
        if ((null != intent) && (null != context) && (null != mediasCache)) {
            Uri thumbnailUri = null;
            ClipData clipData = null;

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                clipData = intent.getClipData();
            }

            // multiple data
            if (null != clipData) {
                if (clipData.getItemCount() > 0) {
                    thumbnailUri = clipData.getItemAt(0).getUri();
                }
            } else if (null != intent.getData()) {
                thumbnailUri = intent.getData();
            }

            if (null != thumbnailUri) {
                try {
                    ResourceUtils.Resource resource = ResourceUtils.openResource(context, thumbnailUri, null);

                    // sanity check
                    if ((null != resource) && resource.isJpegResource()) {
                        InputStream stream = resource.mContentStream;
                        int rotationAngle = ImageUtils.getRotationAngleForBitmap(context, thumbnailUri);

                        Log.d(LOG_TAG, "## getThumbnailUriFromIntent() :  " + thumbnailUri + " rotationAngle " + rotationAngle);

                        String mediaUrl = ImageUtils.scaleAndRotateImage(context, stream, resource.mMimeType, 1024, rotationAngle, mediasCache);
                        thumbnailUri = Uri.parse(mediaUrl);
                    } else if (null != resource) {
                        Log.d(LOG_TAG, "## getThumbnailUriFromIntent() : cannot manage " + thumbnailUri + " mMimeType " + resource.mMimeType);
                    } else {
                        Log.d(LOG_TAG, "## getThumbnailUriFromIntent() : cannot manage " + thumbnailUri + " --> cannot open the dedicated file");
                    }

                    return thumbnailUri;
                } catch (Exception e) {
                    Log.e(LOG_TAG, "## getThumbnailUriFromIntent failed " + e.getMessage(), e);
                }
            }
        }

        return null;
    }

    //==============================================================================================================
    // User presence
    //==============================================================================================================

    /**
     * Format a time interval in seconds to a string
     *
     * @param context         the context.
     * @param secondsInterval the time interval.
     * @return the formatted string
     */
    /**
    private static String formatSecondsIntervalFloored(Context context, long secondsInterval) {
        String formattedString;

        if (secondsInterval < 0) {
            formattedString = context.getResources().getQuantityString(R.plurals.format_time_s, 0, 0);
        } else {
            if (secondsInterval < 60) {
                formattedString = context.getResources().getQuantityString(R.plurals.format_time_s,
                        (int) secondsInterval,
                        (int) secondsInterval);
            } else if (secondsInterval < 3600) {
                formattedString = context.getResources().getQuantityString(R.plurals.format_time_m,
                        (int) (secondsInterval / 60),
                        (int) (secondsInterval / 60));
            } else if (secondsInterval < 86400) {
                formattedString = context.getResources().getQuantityString(R.plurals.format_time_h,
                        (int) (secondsInterval / 3600),
                        (int) (secondsInterval / 3600));
            } else {
                formattedString = context.getResources().getQuantityString(R.plurals.format_time_d,
                        (int) (secondsInterval / 86400),
                        (int) (secondsInterval / 86400));
            }
        }

        return formattedString;
    }**/

    /**
     * Provide the user online status from his user Id.
     * if refreshCallback is set, try to refresh the user presence if it is not known
     *
     * @param context         the context.
     * @param session         the session.
     * @param userId          the userId.
     * @param refreshCallback the presence callback.
     * @return the online status description.
     */
    /**
    public static String getUserOnlineStatus(final Context context,
                                             final MXSession session,
                                             final String userId,
                                             final ApiCallback<Void> refreshCallback) {
        // sanity checks
        if ((null == session) || (null == userId)) {
            return null;
        }

        final User user = session.getDataHandler().getStore().getUser(userId);

        // refresh the presence with this conditions
        boolean triggerRefresh = (null == user) || user.isPresenceObsolete();

        if ((null != refreshCallback) && triggerRefresh) {
            Log.d(LOG_TAG, "Get the user presence : " + userId);

            final String fPresence = (null != user) ? user.presence : null;

            session.refreshUserPresence(userId, new ApiCallback<Void>() {
                @Override
                public void onSuccess(Void info) {
                    boolean isUpdated = false;
                    User updatedUser = session.getDataHandler().getStore().getUser(userId);

                    // don't find any info for the user
                    if ((null == user) && (null == updatedUser)) {
                        Log.d(LOG_TAG, "Don't find any presence info of " + userId);
                    } else if ((null == user) && (null != updatedUser)) {
                        Log.d(LOG_TAG, "Got the user presence : " + userId);
                        isUpdated = true;
                    } else if (!TextUtils.equals(fPresence, updatedUser.presence)) {
                        isUpdated = true;
                        Log.d(LOG_TAG, "Got some new user presence info : " + userId);
                        Log.d(LOG_TAG, "currently_active : " + updatedUser.currently_active);
                        Log.d(LOG_TAG, "lastActiveAgo : " + updatedUser.lastActiveAgo);
                    }

                    if (isUpdated && (null != refreshCallback)) {
                        try {
                            refreshCallback.onSuccess(null);
                        } catch (Exception e) {
                            Log.e(LOG_TAG, "getUserOnlineStatus refreshCallback failed", e);
                        }
                    }
                }

                @Override
                public void onNetworkError(Exception e) {
                    Log.e(LOG_TAG, "getUserOnlineStatus onNetworkError " + e.getLocalizedMessage(), e);
                }

                @Override
                public void onMatrixError(MatrixError e) {
                    Log.e(LOG_TAG, "getUserOnlineStatus onMatrixError " + e.getLocalizedMessage());
                }

                @Override
                public void onUnexpectedError(Exception e) {
                    Log.e(LOG_TAG, "getUserOnlineStatus onUnexpectedError " + e.getLocalizedMessage(), e);
                }
            });
        }

        // unknown user
        if (null == user) {
            return null;
        }

        String presenceText = null;
        if (TextUtils.equals(user.presence, User.PRESENCE_ONLINE)) {
            presenceText = context.getString(R.string.room_participants_online);
        } else if (TextUtils.equals(user.presence, User.PRESENCE_UNAVAILABLE)) {
            presenceText = context.getString(R.string.room_participants_idle);
        } else if (TextUtils.equals(user.presence, User.PRESENCE_OFFLINE) || (null == user.presence)) {
            presenceText = context.getString(R.string.room_participants_offline);
        }

        if (presenceText != null) {
            if ((null != user.currently_active) && user.currently_active) {
                presenceText = context.getString(R.string.room_participants_now, presenceText);
            } else if ((null != user.lastActiveAgo) && (user.lastActiveAgo > 0)) {
                presenceText = context.getString(R.string.room_participants_ago, presenceText,
                        formatSecondsIntervalFloored(context,
                                user.getAbsoluteLastActiveAgo() / 1000L));
            }
        }

        return presenceText;
    }**/

    //==============================================================================================================
    // Users list
    //==============================================================================================================

    /**
     * List the active users i.e the active rooms users (invited or joined) and the contacts with matrix id emails.
     * This function could require a long time to process so it should be called in background.
     *
     * @param session the session.
     * @return a map indexed by the matrix id.
     */
    /**
    public static Map<String, ParticipantAdapterItem> listKnownParticipants(MXSession session) {
        // check known users
        Collection<User> users = session.getDataHandler().getStore().getUsers();

        // a hash map is a lot faster than a list search
        Map<String, ParticipantAdapterItem> map = new HashMap<>(users.size());

        // we don't need to populate the room members or each room
        // because an user is created for each joined / invited room member event
        for (User user : users) {
            if (!MXCallsManager.isConferenceUserId(user.user_id)) {
                map.put(user.user_id, new ParticipantAdapterItem(user));
            }
        }

        return map;
    }**/

    //==============================================================================================================
    // URL parser
    //==============================================================================================================

    private static final Pattern mUrlPattern = Pattern.compile(
            "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)"
                    + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*"
                    + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)",
            Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

    //==============================================================================================================
    // ExpandableListView tools
    //==============================================================================================================

    /**
     * Provides the visible child views.
     * The map key is the group position.
     * The map values are the visible child views.
     *
     * @param expandableListView the listview
     * @param adapter            the linked adapter
     * @return visible views map
     */
    public static Map<Integer, List<Integer>> getVisibleChildViews(ExpandableListView expandableListView, BaseExpandableListAdapter adapter) {
        Map<Integer, List<Integer>> map = new HashMap<>();

        long firstPackedPosition = expandableListView.getExpandableListPosition(expandableListView.getFirstVisiblePosition());

        int firstGroupPosition = ExpandableListView.getPackedPositionGroup(firstPackedPosition);
        int firstChildPosition = ExpandableListView.getPackedPositionChild(firstPackedPosition);

        long lastPackedPosition = expandableListView.getExpandableListPosition(expandableListView.getLastVisiblePosition());

        int lastGroupPosition = ExpandableListView.getPackedPositionGroup(lastPackedPosition);
        int lastChildPosition = ExpandableListView.getPackedPositionChild(lastPackedPosition);

        for (int groupPos = firstGroupPosition; groupPos <= lastGroupPosition; groupPos++) {
            List<Integer> list = new ArrayList<>();

            int startChildPos = (groupPos == firstGroupPosition) ? firstChildPosition : 0;
            int endChildPos = (groupPos == lastGroupPosition) ? lastChildPosition : adapter.getChildrenCount(groupPos) - 1;

            for (int index = startChildPos; index <= endChildPos; index++) {
                list.add(index);
            }

            map.put(groupPos, list);
        }

        return map;
    }
}