package com.p3.bewoplaner.page.chat;

import com.p3.bewoplaner.R;
import com.p3.bewoplaner.data.AppPrefsConstants;
import com.p3.bewoplaner.data.api.common.ErrorHandlingResponseConverter;
import com.p3.bewoplaner.data.api.responses.MessageResponse;
import com.p3.bewoplaner.data.api.responses.MessagesResponse;
import com.p3.bewoplaner.data.api.services.ChatService;
import com.p3.bewoplaner.data.events.GroupUpdatedEvent;
import com.p3.bewoplaner.data.prefs.LongPreference;
import com.p3.bewoplaner.data.prefs.StringPreference;
import com.p3.bewoplaner.data.repository.GroupRealmRepository;
import com.p3.bewoplaner.data.repository.MessageRealmRepository;
import com.p3.bewoplaner.data.repository.RealmProvider;
import com.p3.bewoplaner.data.repository.models.MessageRealm;
import com.p3.bewoplaner.data.repository.specifications.GroupByIdSpecification;
import com.p3.bewoplaner.data.repository.specifications.MessageByGroupIdSpecification;
import com.p3.bewoplaner.data.repository.specifications.MessageByIdSpecification;
import com.p3.bewoplaner.di.scopes.ActivityScope;
import com.p3.bewoplaner.model.Group;
import com.p3.bewoplaner.model.Message;
import com.p3.bewoplaner.page.mvp.MVPAbstractPresenter;
import com.p3.bewoplaner.utils.SystemUtils;
import com.p3.bewoplaner.utils.rx.RxUtils;

import org.greenrobot.eventbus.EventBus;

import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.util.Pair;
import android.text.TextUtils;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import javax.inject.Inject;
import javax.inject.Named;

import io.realm.Realm;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import rx.Completable;
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

import static com.p3.bewoplaner.data.AppPrefsConstants.USER_ID;
import static com.p3.bewoplaner.utils.FileUtils.getFileTypeFromFilePath;

/**
 * Created by Rares Teodorescu on 21/02/2017.
 */
@ActivityScope
public class ChatPresenter extends MVPAbstractPresenter<ChatView>
        implements ChatView.Callback {

    private static final int FIRST_PAGE = 1;
    private static final int GROUP_ID_NOT_SET = -999;
    private static final int LIMIT = 25;

    @Inject
    ChatService mChatService;

    @Inject
    MessageRealmRepository mMessageRealmRepository;

    @Inject
    GroupRealmRepository mGroupRealmRepository;

    @Inject
    SystemUtils mSystemUtils;

    @Inject
    @Named(USER_ID)
    LongPreference mUserIdPreference;

    @Inject
    @Named(AppPrefsConstants.AVATAR)
    StringPreference mAvatarPreference;

    @Inject
    @Named(AppPrefsConstants.NAME)
    StringPreference mProfileNamePreference;

    @Inject
    RealmProvider mRealmProvider;

    @Inject
    ChatPresenter() {
    }

    private long mGroupId = GROUP_ID_NOT_SET;
    private boolean mLoading = false;
    private int mPage = FIRST_PAGE;
    private int mOffset;
    private boolean mFetchMore;
    private ArrayList<Message> mMessages = new ArrayList<>();
    private ArrayList<Pair<Message, String>> mMessageToUpdate = new ArrayList<>();

    /**
     * Get all messages from the server with a specific groupId.
     */
    @Override
    public void loadMessagesFromServerByGroupId(long groupId) {
        if (!mFetchMore) {
            getView().hideMessagesLoading();
            return;
        }
        mGroupId = groupId;
        Subscription subscription = mChatService.getMessages(groupId, mPage)
                .compose(RxUtils.provideDefaultTransformer())
                .flatMap(new ErrorHandlingResponseConverter<>())
                .flatMap(new Func1<MessagesResponse, Observable<ArrayList<Message>>>() {
                    @Override
                    public Observable<ArrayList<Message>> call(MessagesResponse messageResponse) {
                        ArrayList<Message> messages = messageResponse.response.messages;
                        for (int i = 0, size = messages.size(); i < size; i++) {
                            messages.get(i).status = Message.Status.DELIVERED;
                            messages.get(i).isRead = true;
                            messages.get(i).userId = mUserIdPreference.get();
                        }
                        mFetchMore = messageResponse.response.hasMorePages;
                        return Observable.just(messageResponse.response.messages);
                    }
                })
                .subscribe(this::messagesSuccess, this::messagesError);
        addSubscription(subscription);
    }

    /**
     * Get messages from page 0.
     */
    @Override
    public void fetchNewMessages(long groupId) {
        mGroupId = groupId;
        Subscription subscription = mChatService.getMessages(mGroupId, 0)
                .compose(RxUtils.provideDefaultTransformer())
                .flatMap(new ErrorHandlingResponseConverter<>())
                .flatMap(new Func1<MessagesResponse, Observable<List<Message>>>() {
                    @Override
                    public Observable<List<Message>> call(MessagesResponse messageResponse) {
                        if (messageResponse.response.messages == null || messageResponse.response.messages.isEmpty()) {
                            return Observable.just(null);
                        }
                        Message lastMessage = messageResponse.response.messages.get(messageResponse.response.messages.size() - 1);
                        lastMessage.status = Message.Status.DELIVERED;
                        lastMessage.isRead = true;
                        if (!mMessages.contains(lastMessage)) {
                            long groupId = mGroupId;
                            resetMessages();
                            loadMessagesFromServerByGroupId(groupId);
                            return Observable.just(null);
                        }
                        for (int i = 0; i < mMessageToUpdate.size(); i++) {
                            Pair<Message, String> messageToUpdate = mMessageToUpdate.get(i);
                            if (isAttached()) {
                                getView().lastMessageUpdated(messageToUpdate.first, messageToUpdate.second);
                            }
                        }
                        mMessageToUpdate.clear();
                        ArrayList<Message> messages = new ArrayList<>();
                        for (int i = messageResponse.response.messages.size() - 1; i >= 0; i--) {
                            Message message = messageResponse.response.messages.get(i);
                            message.status = Message.Status.DELIVERED;
                            message.isRead = true;
                            message.userId = mUserIdPreference.get();
                            if (mMessages.contains(message)) {
                                continue;
                            }
                            messages.add(message);
                        }
                        return mMessageRealmRepository.updateMessages(messages, new MessageByIdSpecification(mUserIdPreference.get()));
                    }
                })
                .subscribe(this::fetchNewMessagesSuccess, this::fetchNewMessagesError);
        addSubscription(subscription);
    }

    /**
     * Send messages to view.
     */
    private void fetchNewMessagesSuccess(List<Message> messages) {
        if (messages == null) {
            return;
        }
        for (int i = 0; i < messages.size(); i++) {
            if (!isAttached()) {
                break;
            }
            Message message = messages.get(i);
            mMessages.add(message);
            getView().addOneMessage(message);
        }
    }
    /**
     * Send error messages to view.
     */
    private void fetchNewMessagesError(Throwable throwable) {
        Log.e("fetchNewMessages", "onError", throwable);
        if (!isAttached()) {
            return;
        }
        if (mSystemUtils.isNetworkUnavailable()) {
            getView().showError(R.string.error_no_internet_connection);
            return;
        }
        getView().showError(R.string.error_something_went_wrong);
    }

    @Override
    public void resetMessages() {
        mGroupId = GROUP_ID_NOT_SET;
        mLoading = false;
        mPage = FIRST_PAGE;
        mOffset = 0;
        mFetchMore = true;
    }

    /**
     * Save messages to local database.
     */
    private void messagesSuccess(ArrayList<Message> messages) {
        mMessageRealmRepository.update(messages, new MessageByIdSpecification(mUserIdPreference.get()))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Object>() {
                    @Override
                    public void onCompleted() {
                        loadMessagesFromDBByGroupId(mGroupId, true);
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e(ChatPresenter.class.getSimpleName(), e.getMessage(), e);
                    }

                    @Override
                    public void onNext(Object o) {
                        // nop here
                    }
                });
    }

    private void messagesError(Throwable throwable) {
        Log.e(ChatPresenter.class.getSimpleName(), throwable.getMessage(), throwable);
        getView().hideLoading();
        getView().hideMessagesLoading();
        if (mSystemUtils.isNetworkUnavailable()) {
            getView().showError(R.string.error_no_internet_connection);
            return;
        }
        getView().showError(R.string.error_something_went_wrong);
    }

    @Override
    public void loadMessagesFromDBByGroupId(long groupId, boolean fetchWhenFinished) {
        mGroupId = groupId;
        loadMessagesFromDBByGroupId();
    }

    /**
     * Get messages from database using a specific groupId.
     */
    private void loadMessagesFromDBByGroupId() {
        MessageByGroupIdSpecification messageByGroupIdSpecification = new MessageByGroupIdSpecification(mUserIdPreference.get());
        messageByGroupIdSpecification.setGroupId(mGroupId);
        mMessageRealmRepository.queryForAll(messageByGroupIdSpecification)
                .map(ArrayList::new)
                .compose(RxUtils.provideDefaultTransformer())
                .subscribe(this::loadMessagesFromDBSuccessful, this::loadMessagesError);
    }

    /**
     * Send messages to view.
     */
    private void loadMessagesFromDBSuccessful(ArrayList<Message> messages) {
        if (!isAttached()) {
            return;
        }
        mMessages.clear();
        mMessages.addAll(messages);
        mLoading = false;
        getView().hideLoading();
        getView().hideMessagesLoading();
        if (!mMessages.isEmpty()) {
            int endOffset = mOffset + LIMIT > mMessages.size() ? mMessages.size() : mOffset + LIMIT;
            if (mOffset > endOffset) {
                return;
            }
            boolean isFirstPage = mPage == FIRST_PAGE;
            getView().messagesLoaded(new ArrayList<>(mMessages.subList(mOffset, endOffset)), isFirstPage);
            if (isFirstPage && mSystemUtils.isNetworkUnavailable()) {
                Message firstMessage = mMessages.get(0);
                Message message = new Message(firstMessage.id,
                        firstMessage.groupId,
                        firstMessage.createdAt / 1000,
                        firstMessage.message,
                        firstMessage.filePath,
                        firstMessage.smallerImage,
                        firstMessage.originalFilename,
                        firstMessage.uri,
                        firstMessage.senderId,
                        firstMessage.senderName,
                        firstMessage.senderAvatar,
                        firstMessage.status,
                        true,
                        firstMessage.userId,
                        firstMessage.fileDownloaded);
                storeGroupLocally(mGroupId, message);
            }
            mPage++;
            mOffset += LIMIT;
            return;
        }
        getView().showEmptyMessage();
    }

    private void loadMessagesError(Throwable throwable) {
        if (!isAttached()) {
            return;
        }
        mLoading = false;
        getView().hideLoading();
        getView().hideMessagesLoading();
    }

    /**
     * If mMessages size is bigger then 25 and network is available, get more messages from the server.
     */
    @Override
    public void fetchMoreMessagesByGroupId() {
        if (mMessages.size() < 25) {
            getView().hideMessagesLoading();
            return;
        }
        mLoading = true;
        if (mPage == FIRST_PAGE) {
            getView().showLoading();
        } else {
            getView().showMessagesLoading();
        }
        if (mSystemUtils.isNetworkUnavailable()) {
            loadMessagesFromDBByGroupId();
            getView().showError(R.string.error_no_internet_connection);
            return;
        }
        loadMessagesFromServerByGroupId(mGroupId);
    }

    @Override
    public boolean isLoading() {
        return mLoading;
    }

    /**
     * Create message with a specific file path, save it to the local database.
     * Send the file to the server.
     */
    @Override
    public void sendFile(Uri fileUri) {
        File file = getView().getFile(fileUri);
        if (file == null || !file.exists()) {
            getView().showToastMessage(R.string.error_file_not_found);
            return;
        }
        String[] split = file.getAbsolutePath().split("/");

        Message message = new Message(findUniqueRandomId(mRealmProvider, mUserIdPreference.get()),
                mGroupId,
                System.currentTimeMillis() / 1000,
                null,
                file.getPath(),
                file.getPath(),
                split[split.length - 1],
                fileUri.toString(),
                mUserIdPreference.get(),
                mProfileNamePreference.get(),
                mAvatarPreference.get(),
                Message.Status.PENDING,
                true,
                mUserIdPreference.get(),
                false);

        storeMessageLocally(message)
                .subscribe(new Subscriber<Object>() {
                    @Override
                    public void onCompleted() {
                        if (mSystemUtils.isNetworkUnavailable()) {
                            getView().showError(R.string.error_no_internet_connection);
                            message.status = Message.Status.FAILED;
                            message.isRead = true;
                            storeMessageLocally(message, message.id, true);
                            storeGroupLocally(mGroupId, message);
                            return;
                        }
                        checkForEmptyMessageAndHideIt();
                        message.createdAt *= 1000;
                        getView().addOneMessage(message);

                        // create RequestBody instance from file
                        RequestBody requestFile = RequestBody.create(getView().getMediaType(fileUri), file);

                        // MultipartBody.Part is used to send also the actual file name
                        MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);

                        // add another part within the multipart request
                        String groupIdString = String.valueOf(mGroupId);
                        RequestBody groupIdBody = RequestBody.create(okhttp3.MultipartBody.FORM, groupIdString);

                        mChatService.sendFile(groupIdBody, body)
                                .compose(RxUtils.provideDefaultTransformer())
                                .flatMap(new ErrorHandlingResponseConverter<>())
                                .subscribe(new Subscriber<MessageResponse>() {
                                    @Override
                                    public void onCompleted() {
                                    }

                                    @Override
                                    public void onError(Throwable e) {
                                        Log.e("sendFile", "onError", e);
                                        @SuppressWarnings("UnnecessaryLocalVariable") Message newMessage = message;
                                        newMessage.createdAt /= 1000;
                                        newMessage.status = Message.Status.FAILED;
                                        newMessage.isRead = true;
                                        storeMessageLocally(newMessage, message.id, false);
                                        storeGroupLocally(mGroupId, message);
                                        if (!isAttached()) {
                                            return;
                                        }
                                        if (mSystemUtils.isNetworkUnavailable()) {
                                            getView().showError(R.string.error_no_internet_connection);
                                            return;
                                        }
                                        getView().showError(R.string.error_something_went_wrong);
                                    }

                                    @Override
                                    public void onNext(MessageResponse baseResponse) {
                                        deleteLocalMessage(message);
                                        baseResponse.response.message.status = Message.Status.DELIVERED;
                                        baseResponse.response.message.isRead = true;
                                        storeMessageLocally(baseResponse.response.message, message.id, false);
                                        storeGroupLocally(mGroupId, baseResponse.response.message);
                                    }
                                });
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("sendFile", "onError", e);
                    }

                    @Override
                    public void onNext(Object o) {

                    }
                });
    }

    /**
     * Download file and notify the activity.
     */
    @Override
    public void downloadFile(Message message, String filePath, int itemPosition) {
        if (message.fileDownloaded) {
            getView().stopItemProgress(itemPosition);
            getView().fileDownloadedSuccessfully(filePath, getFileTypeFromFilePath(filePath));
            return;
        }
        downloadFileFromUrl(message.filePath, filePath, itemPosition)
                .compose(RxUtils.provideDefaultTransformer())
                .subscribe(new Subscriber<String>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("ChatPresenter", "downloadFile", e);
                        if (!isAttached()) {
                            return;
                        }
                        getView().stopItemProgress(itemPosition);
                        if (mSystemUtils.isNetworkUnavailable()) {
                            getView().showError(R.string.error_no_internet_connection);
                            return;
                        }
                        getView().showError(R.string.error_something_went_wrong);
                    }

                    @Override
                    public void onNext(String fileType) {
                        if (!isAttached()) {
                            return;
                        }
                        getView().stopItemProgress(itemPosition);
                        getView().fileDownloadedSuccessfully(filePath, fileType);
                        message.fileDownloaded = true;
                        message.createdAt /= 1000;
                        storeMessageLocally(message).subscribe();
                    }
                });
    }

    @Override
    public void resendMessage(Message message) {
        if (mSystemUtils.isNetworkUnavailable()) {
            getView().enableMessageRetry(message.id);
            getView().showError(R.string.error_no_internet_connection);
            return;
        }
        if (TextUtils.isEmpty(message.message)) {
            Uri fileUri = Uri.parse(message.uri);
            File file = getView().getFile(fileUri);

            if (file == null || !file.exists()) {
                getView().showError(R.string.error_file_does_not_exist_anymore);
                return;
            }

            // create RequestBody instance from file
            RequestBody requestFile = RequestBody.create(getView().getMediaType(fileUri), file);

            // MultipartBody.Part is used to send also the actual file name
            MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);

            // add another part within the multipart request
            String groupIdString = String.valueOf(mGroupId);
            RequestBody groupIdBody = RequestBody.create(okhttp3.MultipartBody.FORM, groupIdString);

            mChatService.sendFile(groupIdBody, body)
                    .compose(RxUtils.provideDefaultTransformer())
                    .flatMap(new ErrorHandlingResponseConverter<>())
                    .subscribe(new Subscriber<MessageResponse>() {
                        @Override
                        public void onCompleted() {
                        }

                        @Override
                        public void onError(Throwable e) {
                            Log.e("resendMessage", "onError", e);
                            if (!isAttached()) {
                                return;
                            }
                            getView().enableMessageRetry(message.id);
                            if (mSystemUtils.isNetworkUnavailable()) {
                                getView().showError(R.string.error_no_internet_connection);
                                return;
                            }
                            getView().showError(R.string.error_retry_failed);
                        }

                        @Override
                        public void onNext(MessageResponse baseResponse) {
                            deleteLocalMessage(message);
                            baseResponse.response.message.status = Message.Status.DELIVERED;
                            baseResponse.response.message.isRead = true;
                            storeMessageLocally(baseResponse.response.message, message.id, true);
                            storeGroupLocally(mGroupId, baseResponse.response.message);
                        }
                    });
            return;
        }
        mChatService.sendMessage(message.groupId, message.message)
                .compose(RxUtils.provideDefaultTransformer())
                .flatMap(new ErrorHandlingResponseConverter<>())
                .subscribe(new Subscriber<MessageResponse>() {
                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("resendMessage", "onError", e);
                        if (!isAttached()) {
                            return;
                        }
                        getView().enableMessageRetry(message.id);
                        if (mSystemUtils.isNetworkUnavailable()) {
                            getView().showError(R.string.error_no_internet_connection);
                            return;
                        }
                        getView().showError(R.string.error_retry_failed);
                    }

                    @Override
                    public void onNext(MessageResponse baseResponse) {
                        deleteLocalMessage(message);
                        baseResponse.response.message.status = Message.Status.DELIVERED;
                        storeMessageLocally(baseResponse.response.message, message.id, true);
                        storeGroupLocally(mGroupId, baseResponse.response.message);
                    }
                });
    }

    @Override
    public void sendMessage(String text) {
        Message message = new Message(findUniqueRandomId(mRealmProvider, mUserIdPreference.get()),
                mGroupId,
                System.currentTimeMillis() / 1000,
                text,
                null,
                null,
                null,
                null,
                mUserIdPreference.get(),
                mProfileNamePreference.get(),
                mAvatarPreference.get(),
                Message.Status.PENDING,
                true,
                mUserIdPreference.get(),
                false);
        storeMessageLocally(message)
                .subscribe(new Subscriber<Object>() {
                    @Override
                    public void onCompleted() {
                        if (mSystemUtils.isNetworkUnavailable()) {
                            getView().showError(R.string.error_no_internet_connection);
                            message.status = Message.Status.FAILED;
                            message.isRead = true;
                            storeMessageLocally(message, message.id, true);
                            storeGroupLocally(mGroupId, message);
                            return;
                        }
                        checkForEmptyMessageAndHideIt();
                        message.createdAt *= 1000;
                        getView().addOneMessage(message);

                        mChatService.sendMessage(mGroupId, text)
                                .compose(RxUtils.provideDefaultTransformer())
                                .flatMap(new ErrorHandlingResponseConverter<>())
                                .subscribe(new Subscriber<MessageResponse>() {
                                    @Override
                                    public void onCompleted() {
                                    }

                                    @Override
                                    public void onError(Throwable e) {
                                        Log.e("sendMessage", "onError", e);
                                        @SuppressWarnings("UnnecessaryLocalVariable") Message newMessage = message;
                                        newMessage.status = Message.Status.FAILED;
                                        newMessage.createdAt /= 1000;
                                        newMessage.isRead = true;
                                        storeMessageLocally(newMessage, message.id, false);
                                        storeGroupLocally(mGroupId, message);
                                        if (!isAttached()) {
                                            return;
                                        }
                                        if (mSystemUtils.isNetworkUnavailable()) {
                                            getView().showError(R.string.error_no_internet_connection);
                                            return;
                                        }
                                        getView().showError(R.string.error_something_went_wrong);
                                    }

                                    @Override
                                    public void onNext(MessageResponse baseResponse) {
                                        deleteLocalMessage(message);
                                        baseResponse.response.message.status = Message.Status.DELIVERED;
                                        baseResponse.response.message.isRead = true;
                                        storeMessageLocally(baseResponse.response.message, message.id, false);
                                        storeGroupLocally(mGroupId, baseResponse.response.message);
                                    }
                                });
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("sendMessage", "onError", e);
                    }

                    @Override
                    public void onNext(Object object) {
                    }
                });
    }

    private void storeMessageLocally(Message newMessage, String oldMessageId, boolean queryLocalDBWhenFinished) {
        newMessage.userId = mUserIdPreference.get();
        MessageByIdSpecification messageByIdSpecification = new MessageByIdSpecification(mUserIdPreference.get());
        messageByIdSpecification.setMessageId(newMessage.id);
        mMessageRealmRepository.update(Collections.singletonList(newMessage), messageByIdSpecification)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Object>() {
                    @Override
                    public void onCompleted() {
                        newMessage.createdAt *= 1000;
                        if (!queryLocalDBWhenFinished) {
                            Message messageToDelete = new Message();
                            messageToDelete.id = oldMessageId;
                            messageToDelete.removeFromList(mMessages);
                            mMessages.add(newMessage);
                            if (!isAttached()) {
                                mMessageToUpdate.add(new Pair<>(newMessage, oldMessageId));
                                return;
                            }
                            getView().lastMessageUpdated(newMessage, oldMessageId);
                            return;
                        }
                        mPage = FIRST_PAGE;
                        mOffset = 0;
                        loadMessagesFromDBByGroupId();
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("addMessage", "onError", e);
                    }

                    @Override
                    public void onNext(Object o) {
                    }
                });
    }

    private void deleteLocalMessage(Message message) {
        MessageByIdSpecification messageByIdSpecification = new MessageByIdSpecification(mUserIdPreference.get());
        messageByIdSpecification.setMessageId(message.id);
        mMessageRealmRepository.remove(messageByIdSpecification)
                .subscribeOn(Schedulers.io())
                .subscribe(new Subscriber<Object>() {
                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("deleteLocalMessage", "onError", e);
                    }

                    @Override
                    public void onNext(Object o) {

                    }
                });
    }

    @Override
    public void saveMessage(Message message) {
        storeMessageLocally(message)
                .subscribe(() -> {
                    if (!isAttached()) {
                        return;
                    }
                    checkForEmptyMessageAndHideIt();
                    message.createdAt *= 1000;
                    mMessages.add(message);
                    getView().addOneMessage(message);
                });
        storeGroupLocally(mGroupId, message);
    }

    private void checkForEmptyMessageAndHideIt() {
        if (mMessages.isEmpty()) {
            getView().hideLoading();
        }
    }

    private Completable storeMessageLocally(Message message) {
        MessageByIdSpecification messageByIdSpecification = new MessageByIdSpecification(mUserIdPreference.get());
        messageByIdSpecification.setMessageId(message.id);
        return mMessageRealmRepository.update(message, messageByIdSpecification)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

    /**
     * Save last message to local database.
     */
    private void storeGroupLocally(long groupId, Message message) {
        Group group = new Group();
        group.id = groupId;
        group.lastMessage = message;
        mGroupRealmRepository.updateGroupMessage(group, new GroupByIdSpecification(mUserIdPreference.get()))
                .subscribe(() -> {
                    if (!isAttached()) {
                        EventBus.getDefault().post(new GroupUpdatedEvent());
                    }
                });
    }

    @Override
    public void findGroupById() {
        Subscription subscription = mGroupRealmRepository.queryForOneById(String.valueOf(mGroupId), new GroupByIdSpecification(mUserIdPreference.get()))
                .compose(RxUtils.provideDefaultTransformer())
                .subscribe(new Subscriber<Group>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(Group group) {
                        if (group != null) {
                            getView().setGroup(group);
                            return;
                        }
                        loadGroups();
                    }
                });
        addSubscription(subscription);
    }

    private void loadGroups() {
        Subscription subscription = mChatService.getGroups()
                .compose(RxUtils.provideDefaultTransformer())
                .flatMap(new ErrorHandlingResponseConverter<>())
                .flatMap(chatResponse -> getGroupById(chatResponse.response.groups))
                .subscribe(this::groupsSuccess, this::groupsError);
        addSubscription(subscription);
    }

    private Observable<Group> getGroupById(ArrayList<Group> groups) {
        Group group = null;
        for (Group group1 : groups) {
            if (group1.id == mGroupId) {
                group = group1;
                break;
            }
        }
        return Observable.just(group);
    }

    private void groupsError(Throwable throwable) {
        Log.e(ChatPresenter.class.getSimpleName(), throwable.getMessage(), throwable);
        if (mSystemUtils.isNetworkUnavailable()) {
            getView().showError(R.string.error_no_internet_connection);
            return;
        }
        getView().showError(R.string.error_something_went_wrong);
    }

    private void groupsSuccess(Group group) {
        getView().setGroup(group);
        mGroupRealmRepository.update(group, new GroupByIdSpecification(mUserIdPreference.get())).subscribe();
    }

    @Override
    public long getGroupId() {
        return mGroupId;
    }

    @Override
    protected void afterAttachView(@NonNull ChatView view) {
        super.afterAttachView(view);
        view.setCallback(this);
        mFetchMore = true;
    }

    @Override
    protected void beforeDetachView(@NonNull ChatView view) {
        view.setCallback(null);
        super.beforeDetachView(view);
    }

    private Observable<String> downloadFileFromUrl(@NonNull String fileUrl, @NonNull String filePath, int itemPosition) {
        return Observable.create(subscriber -> {
            try {
                //set the download URL, a url that points to a file on the internet
                //this is the file to be downloaded
                URL url = new URL(fileUrl);
                //create the new connection
                HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
                //set up some things on the connection
                urlConnection.setRequestMethod("GET");
                urlConnection.setDoOutput(true);

                FileOutputStream fileOutput;
                InputStream inputStream;
                File file = new File(filePath);

                if (file.length() > 0) {
                    urlConnection.setRequestProperty("Range", "bytes=" + file.length() + "-");
                    //this will be used to append the downloaded data into the file already existing
                    fileOutput = new FileOutputStream(file, true);
                } else {
                    //this will be used to write the downloaded data into the file we created
                    fileOutput = new FileOutputStream(file);
                }

                //and connect!
                urlConnection.connect();

                //this is the total size of the file
                int totalSize = urlConnection.getContentLength();

                if (file.length() == totalSize || totalSize == -1) {
                    subscriber.onNext(getFileTypeFromFilePath(filePath));
                    subscriber.onCompleted();
                    return;
                }

                //this will be used in reading the data from the internet
                inputStream = urlConnection.getInputStream();

                //variable to store total downloaded bytes
                long downloadedSize = file.length();
                totalSize += downloadedSize;

                //create a buffer...
                byte[] buffer = new byte[1024];
                int bufferLength = 0;
                int lastPercentage = 0;
                int percentage = 0;

                //now, read through the input buffer and write the contents to the file
                while ((bufferLength = inputStream.read(buffer, 0, 1024)) >= 0) {
                    //add the data in the buffer to the file in the file output stream (the file on the sd card
                    fileOutput.write(buffer, 0, bufferLength);
                    //add up the size so we know how much is downloaded
                    downloadedSize += bufferLength;
                    percentage = (int) (downloadedSize * 100 / totalSize);
                    if (percentage - lastPercentage > 1) {
                        lastPercentage = percentage;
                        if (isAttached()) {
                            getView().showItemProgress(itemPosition, percentage);
                        }
                    }
                }
                //close the output stream when done
                fileOutput.close();

                subscriber.onNext(getFileTypeFromFilePath(filePath));
                subscriber.onCompleted();

            } catch (IOException e) {
                e.printStackTrace();
                //noinspection ResultOfMethodCallIgnored
                subscriber.onError(e);
                subscriber.onCompleted();
            }
        });
    }

    public static String findUniqueRandomId(RealmProvider realmProvider, long userId) {
        String randomId = generateRandomId();
        MessageByIdSpecification messageByIdSpecification = new MessageByIdSpecification(userId);
        messageByIdSpecification.setMessageId(randomId);
        Realm realm = realmProvider.provide();
        realm.beginTransaction();
        boolean goOn = true;
        while (goOn) {
            MessageRealm messageRealm = messageByIdSpecification.toRealmQuery(realm).findFirst();
            if (messageRealm == null) {
                goOn = false;
            } else {
                randomId = generateRandomId();
                messageByIdSpecification.setMessageId(randomId);
            }
        }
        realm.commitTransaction();
        realm.close();
        return randomId;
    }

    private static String generateRandomId() {
        return "_" + new Random().nextInt();
    }

}