package app.fedilab.android.mastodon.viewmodel.mastodon;
/* Copyright 2021 Thomas Schneider
 *
 * This file is a part of Fedilab
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 *
 * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with Fedilab; if not,
 * see <http://www.gnu.org/licenses>. */


import android.app.Application;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;

import java.net.IDN;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import app.fedilab.android.R;
import app.fedilab.android.mastodon.client.endpoints.MastodonStatusesService;
import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Accounts;
import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.Card;
import app.fedilab.android.mastodon.client.entities.api.Context;
import app.fedilab.android.mastodon.client.entities.api.Pagination;
import app.fedilab.android.mastodon.client.entities.api.Poll;
import app.fedilab.android.mastodon.client.entities.api.ScheduledStatus;
import app.fedilab.android.mastodon.client.entities.api.ScheduledStatuses;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.api.StatusSource;
import app.fedilab.android.mastodon.client.entities.app.BaseAccount;
import app.fedilab.android.mastodon.client.entities.app.StatusCache;
import app.fedilab.android.mastodon.client.entities.app.Timeline;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.mastodon.helper.TimelineHelper;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;


public class StatusesVM extends AndroidViewModel {


    private MutableLiveData<Status> statusMutableLiveData;
    private MutableLiveData<List<Status>> statusListMutableLiveData;
    private MutableLiveData<StatusSource> statusSourceMutableLiveData;
    private MutableLiveData<ScheduledStatus> scheduledStatusMutableLiveData;
    private MutableLiveData<ScheduledStatuses> scheduledStatusesMutableLiveData;
    private MutableLiveData<Void> voidMutableLiveData;
    private MutableLiveData<Card> cardMutableLiveData;
    private MutableLiveData<Attachment> attachmentMutableLiveData;
    private MutableLiveData<Poll> pollMutableLiveData;
    private MutableLiveData<Context> contextMutableLiveData;
    private MutableLiveData<Accounts> accountsMutableLiveData;


    public StatusesVM(@NonNull Application application) {
        super(application);
    }


    private OkHttpClient getOkHttpClient() {
        return new OkHttpClient.Builder()
                .readTimeout(60, TimeUnit.SECONDS)
                .connectTimeout(60, TimeUnit.SECONDS)
                .callTimeout(60, TimeUnit.SECONDS)
                .proxy(Helper.getProxy(getApplication().getApplicationContext()))
                .build();
    }

    private MastodonStatusesService init(String instance) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://" + (instance != null ? IDN.toASCII(instance, IDN.ALLOW_UNASSIGNED) : null) + "/api/v1/")
                .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
                .client(getOkHttpClient())
                .build();
        return retrofit.create(MastodonStatusesService.class);
    }

    /**
     * Post a media
     *
     * @param instance    Instance domain of the active account
     * @param token       Access token of the active account
     * @param file        URI
     * @param thumbnail   URI
     * @param description String
     * @param focus       String
     * @return LiveData<Attachment>
     */
    public LiveData<Attachment> postAttachment(@NonNull String instance,
                                               String token,
                                               @NonNull Uri file,
                                               Uri thumbnail,
                                               String description,
                                               String focus) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        attachmentMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            MultipartBody.Part fileMultipartBody;
            MultipartBody.Part thumbnailMultipartBody;
            fileMultipartBody = Helper.getMultipartBody(getApplication(), "file", file);
            thumbnailMultipartBody = Helper.getMultipartBody(getApplication(), "file", thumbnail);
            RequestBody descriptionBody = null;
            if (description != null && description.trim().length() > 0) {
                descriptionBody = RequestBody.create(MediaType.parse("text/plain"), description);
            }
            RequestBody focusBody = null;
            if (focus != null && focus.trim().length() > 0) {
                focusBody = RequestBody.create(MediaType.parse("text/plain"), focus);
            }
            Call<Attachment> attachmentCall = mastodonStatusesService.postMedia(token, fileMultipartBody, thumbnailMultipartBody, descriptionBody, focusBody);
            Attachment attachment = null;
            if (attachmentCall != null) {
                try {
                    Response<Attachment> attachmentResponse = attachmentCall.execute();
                    if (attachmentResponse.isSuccessful()) {
                        attachment = attachmentResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Attachment finalAttachment = attachment;
            Runnable myRunnable = () -> attachmentMutableLiveData.setValue(finalAttachment);
            mainHandler.post(myRunnable);
        }).start();
        return attachmentMutableLiveData;
    }

    /**
     * Post a message with the authenticated user
     * text can be null if a media or a poll is attached
     * if media are attached, poll need to be null
     * if a poll is attached, media should be null
     *
     * @param instance         Instance domain of the active account
     * @param token            Access token of the active account
     * @param idempotency_Key  String
     * @param text             String
     * @param media_ids        List<String>
     * @param poll_options     String
     * @param poll_expire_in   int
     * @param poll_multiple    boolean
     * @param poll_hide_totals boolean
     * @param in_reply_to_id   String
     * @param sensitive        boolean
     * @param spoiler_text     String
     * @param visibility       String
     * @param language         String
     * @return LiveData<Status>
     */
    public LiveData<Status> postStatus(@NonNull String instance,
                                       String token,
                                       String idempotency_Key,
                                       String text,
                                       List<String> media_ids,
                                       List<String> poll_options,
                                       Integer poll_expire_in,
                                       Boolean poll_multiple,
                                       Boolean poll_hide_totals,
                                       String in_reply_to_id,
                                       Boolean sensitive,
                                       String spoiler_text,
                                       String visibility,
                                       String language,
                                       String quote_id,
                                       String content_type,
                                       Boolean local_only) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<Status> statusCall = mastodonStatusesService.createStatus(idempotency_Key, token, text, media_ids, poll_options, poll_expire_in,
                    poll_multiple, poll_hide_totals, in_reply_to_id, sensitive, spoiler_text, visibility, language, quote_id, content_type, local_only);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            Runnable myRunnable = () -> statusMutableLiveData.setValue(finalStatus);
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * @param instance         Instance domain of the active account
     * @param token            Access token of the active account
     *                         Schedule a message for the authenticated user
     *                         scheduledAt can't be null
     *                         text can be null if a media or a poll is attached
     *                         if media are attached, poll need to be null
     *                         if a poll is attached, media should be null
     * @param idempotency_Key  String
     * @param text             String
     * @param media_ids        List<String>
     * @param poll_options     String
     * @param poll_expire_in   int
     * @param poll_multiple    boolean
     * @param poll_hide_totals boolean
     * @param in_reply_to_id   String
     * @param sensitive        boolean
     * @param spoiler_text     String
     * @param visibility       String
     * @param scheduledAt      Date
     * @param language         String
     * @return LiveData<Status>
     */
    public LiveData<ScheduledStatus> postScheduledStatus(@NonNull String instance,
                                                         String token,
                                                         String idempotency_Key,
                                                         String text,
                                                         List<String> media_ids,
                                                         List<String> poll_options,
                                                         Integer poll_expire_in,
                                                         Boolean poll_multiple,
                                                         Boolean poll_hide_totals,
                                                         String in_reply_to_id,
                                                         Boolean sensitive,
                                                         String spoiler_text,
                                                         String visibility,
                                                         @NonNull String scheduledAt,
                                                         String language) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        scheduledStatusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<ScheduledStatus> scheduledStatusCall = mastodonStatusesService.createScheduledStatus(idempotency_Key, token, text, media_ids, poll_options, poll_expire_in,
                    poll_multiple, poll_hide_totals, in_reply_to_id, sensitive, spoiler_text, visibility, scheduledAt, language);
            ScheduledStatus scheduledStatus = null;
            if (scheduledStatusCall != null) {
                try {
                    Response<ScheduledStatus> scheduledStatusResponse = scheduledStatusCall.execute();
                    if (scheduledStatusResponse.isSuccessful()) {
                        scheduledStatus = scheduledStatusResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            ScheduledStatus finalScheduledStatus = scheduledStatus;
            Runnable myRunnable = () -> scheduledStatusMutableLiveData.setValue(finalScheduledStatus);
            mainHandler.post(myRunnable);
        }).start();
        return scheduledStatusMutableLiveData;
    }

    /**
     * Get a status by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> getStatus(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<Status> statusCall = mastodonStatusesService.getStatus(token, id);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            Runnable myRunnable = () -> statusMutableLiveData.setValue(finalStatus);
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }


    /**
     * Get a status source by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<StatusSource>
     */
    public LiveData<StatusSource> getStatusSource(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusSourceMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<StatusSource> statusSourceCall = mastodonStatusesService.getStatusSource(token, id);
            StatusSource statusSource = null;
            if (statusSourceCall != null) {
                try {
                    Response<StatusSource> statusResponse = statusSourceCall.execute();
                    if (statusResponse.isSuccessful()) {
                        statusSource = statusResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            StatusSource finalStatusSource = statusSource;
            Runnable myRunnable = () -> statusSourceMutableLiveData.setValue(finalStatusSource);
            mainHandler.post(myRunnable);
        }).start();
        return statusSourceMutableLiveData;
    }

    /**
     * Get a history of statuses by id
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<List<Status>> getStatusHistory(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusListMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<List<Status>> statusListCall = mastodonStatusesService.getStatusHistory(token, id);
            List<Status> statusList = null;
            if (statusListCall != null) {
                try {
                    Response<List<Status>> statusResponse = statusListCall.execute();
                    if (statusResponse.isSuccessful()) {
                        statusList = statusResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            List<Status> finalStatusList = statusList;
            Runnable myRunnable = () -> statusListMutableLiveData.setValue(finalStatusList);
            mainHandler.post(myRunnable);
        }).start();
        return statusListMutableLiveData;
    }

    /**
     * Delete a status by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> deleteStatus(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<Status> statusCall = mastodonStatusesService.deleteStatus(token, id);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            //The status must also be deleted in cache
            try {
                BaseAccount account = new app.fedilab.android.mastodon.client.entities.app.Account(getApplication().getApplicationContext()).getAccountByToken(token);
                new StatusCache(getApplication().getApplicationContext()).deleteStatus(account.instance, id);
            } catch (DBException e) {
                e.printStackTrace();
            }

            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            Runnable myRunnable = () -> statusMutableLiveData.setValue(finalStatus);
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * Get context of a status
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<app.fedilab.android.client.mastodon.entities.Context>
     */
    public LiveData<Context> getContext(@NonNull String instance, String token, @NonNull String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        contextMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<Context> contextCall = mastodonStatusesService.getContext(token, id);
            Context context = null;
            if (contextCall != null) {
                try {
                    Response<Context> contextResponse = contextCall.execute();
                    if (contextResponse.isSuccessful()) {
                        context = contextResponse.body();
                        if (context != null) {
                            TimelineHelper.filterStatus(getApplication().getApplicationContext(), context.descendants, Timeline.TimeLineEnum.CONTEXT);
                            TimelineHelper.filterStatus(getApplication().getApplicationContext(), context.ancestors, Timeline.TimeLineEnum.CONTEXT);
                        }

                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Context finalContext = context;
            Runnable myRunnable = () -> contextMutableLiveData.setValue(finalContext);
            mainHandler.post(myRunnable);
        }).start();
        return contextMutableLiveData;
    }

    /**
     * People that reblogged the status by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @param max_id   String
     * @param since_id String
     * @param min_id   String
     * @return LiveData<Accounts>
     */
    public LiveData<Accounts> rebloggedBy(@NonNull String instance,
                                          String token,
                                          @NonNull String id,
                                          String max_id,
                                          String since_id,
                                          String min_id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        accountsMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            int limit = MastodonHelper.accountsPerCall(getApplication().getApplicationContext());
            Call<List<Account>> accountsCall = mastodonStatusesService.getRebloggedBy(token, id, max_id, since_id, min_id, limit);
            List<Account> accounts = null;
            Headers headers = null;
            if (accountsCall != null) {
                try {
                    Response<List<Account>> accountsResponse = accountsCall.execute();
                    if (accountsResponse.isSuccessful()) {
                        accounts = accountsResponse.body();
                    }
                    headers = accountsResponse.headers();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Accounts accountsPagination = new Accounts();
            accountsPagination.accounts = accounts;
            accountsPagination.pagination = MastodonHelper.getPagination(headers);
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Runnable myRunnable = () -> accountsMutableLiveData.setValue(accountsPagination);
            mainHandler.post(myRunnable);
        }).start();
        return accountsMutableLiveData;
    }

    /**
     * People that favourited the status by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @param max_id   String
     * @param since_id String
     * @param min_id   String
     * @return LiveData<Accounts>
     */
    public LiveData<Accounts> favouritedBy(@NonNull String instance,
                                           String token,
                                           @NonNull String id,
                                           String max_id,
                                           String since_id,
                                           String min_id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        accountsMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            int limit = MastodonHelper.accountsPerCall(getApplication().getApplicationContext());
            Call<List<Account>> accountsCall = mastodonStatusesService.getFavourited(token, id, max_id, since_id, min_id, limit);
            List<Account> accounts = null;
            Headers headers = null;
            if (accountsCall != null) {
                try {
                    Response<List<Account>> accountsResponse = accountsCall.execute();
                    if (accountsResponse.isSuccessful()) {
                        accounts = accountsResponse.body();
                    }
                    headers = accountsResponse.headers();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Accounts accountsPagination = new Accounts();
            accountsPagination.accounts = accounts;
            if (headers != null) {
                accountsPagination.pagination = MastodonHelper.getPagination(headers);
            }
            Runnable myRunnable = () -> accountsMutableLiveData.setValue(accountsPagination);
            mainHandler.post(myRunnable);
        }).start();
        return accountsMutableLiveData;
    }

    /**
     * Add a status to favourites by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> favourite(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            String errorMessage = null;
            Call<Status> statusCall = mastodonStatusesService.favourites(token, id);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    } else {
                        if (statusResponse.errorBody() != null) {
                            errorMessage = statusResponse.errorBody().string();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    errorMessage = e.getMessage() != null ? e.getMessage() : getApplication().getString(R.string.toast_error);
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            String finalErrorMessage = errorMessage;
            Runnable myRunnable = () -> {
                statusMutableLiveData.setValue(finalStatus);
                if (finalErrorMessage != null) {
                    Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, finalErrorMessage);
                }
            };
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * remove a status from favourites by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> unFavourite(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            String errorMessage = null;
            Call<Status> statusCall = mastodonStatusesService.unFavourite(token, id);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    } else {
                        if (statusResponse.errorBody() != null) {
                            errorMessage = statusResponse.errorBody().string();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    errorMessage = e.getMessage() != null ? e.getMessage() : getApplication().getString(R.string.toast_error);
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            String finalErrorMessage = errorMessage;
            Runnable myRunnable = () -> {
                statusMutableLiveData.setValue(finalStatus);
                if (finalErrorMessage != null) {
                    Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, finalErrorMessage);
                }
            };
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * Reblog a status by ID
     *
     * @param instance   Instance domain of the active account
     * @param token      Access token of the active account
     * @param id         String - id of the status
     * @param visibility MastodonHelper.visibility - visibility of the reblog (public, unlisted, private)
     * @return LiveData<Status>
     */
    public LiveData<Status> reblog(@NonNull String instance, String token, String id, MastodonHelper.visibility visibility) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();

        new Thread(() -> {
            String errorMessage = null;
            Call<Status> statusCall = mastodonStatusesService.reblog(token, id, visibility != null ? visibility.name().toLowerCase() : null);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    } else {
                        if (statusResponse.errorBody() != null) {
                            errorMessage = statusResponse.errorBody().string();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    errorMessage = e.getMessage() != null ? e.getMessage() : getApplication().getString(R.string.toast_error);
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            String finalErrorMessage = errorMessage;
            Runnable myRunnable = () -> {
                statusMutableLiveData.setValue(finalStatus);
                if (finalErrorMessage != null) {
                    Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, finalErrorMessage);
                }
            };
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * Unreblog a status by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> unReblog(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            String errorMessage = null;
            Call<Status> statusCall = mastodonStatusesService.unReblog(token, id);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    } else {
                        if (statusResponse.errorBody() != null) {
                            errorMessage = statusResponse.errorBody().string();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    errorMessage = e.getMessage() != null ? e.getMessage() : getApplication().getString(R.string.toast_error);
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            String finalErrorMessage = errorMessage;
            Runnable myRunnable = () -> {
                statusMutableLiveData.setValue(finalStatus);
                if (finalErrorMessage != null) {
                    Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, finalErrorMessage);
                }
            };
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * Bookmark a status by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> bookmark(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            String errorMessage = null;
            Call<Status> statusCall = mastodonStatusesService.bookmark(token, id);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    } else {
                        if (statusResponse.errorBody() != null) {
                            errorMessage = statusResponse.errorBody().string();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    errorMessage = e.getMessage() != null ? e.getMessage() : getApplication().getString(R.string.toast_error);
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            String finalErrorMessage = errorMessage;
            Runnable myRunnable = () -> {
                statusMutableLiveData.setValue(finalStatus);
                if (finalErrorMessage != null) {
                    Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, finalErrorMessage);
                }
            };
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * Unbookmark a status by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> unBookmark(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            String errorMessage = null;
            Call<Status> statusCall = mastodonStatusesService.unBookmark(token, id);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    } else {
                        if (statusResponse.errorBody() != null) {
                            errorMessage = statusResponse.errorBody().string();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    errorMessage = e.getMessage() != null ? e.getMessage() : getApplication().getString(R.string.toast_error);
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            String finalErrorMessage = errorMessage;
            Runnable myRunnable = () -> {
                statusMutableLiveData.setValue(finalStatus);
                if (finalErrorMessage != null) {
                    Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, finalErrorMessage);
                }
            };
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * Mute a conversation by status ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> mute(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            String errorMessage = null;
            Call<Status> statusCall = mastodonStatusesService.muteConversation(token, id);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    } else {
                        if (statusResponse.errorBody() != null) {
                            errorMessage = statusResponse.errorBody().string();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    errorMessage = e.getMessage() != null ? e.getMessage() : getApplication().getString(R.string.toast_error);
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            String finalErrorMessage = errorMessage;
            Runnable myRunnable = () -> {
                statusMutableLiveData.setValue(finalStatus);
                if (finalErrorMessage != null) {
                    Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, finalErrorMessage);
                }
            };
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * Unmute a conversation by a status ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> unMute(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            String errorMessage = null;
            Call<Status> statusCall = mastodonStatusesService.unMuteConversation(token, id);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    } else {
                        if (statusResponse.errorBody() != null) {
                            errorMessage = statusResponse.errorBody().string();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    errorMessage = e.getMessage() != null ? e.getMessage() : getApplication().getString(R.string.toast_error);
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            String finalErrorMessage = errorMessage;
            Runnable myRunnable = () -> {
                statusMutableLiveData.setValue(finalStatus);
                if (finalErrorMessage != null) {
                    Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, finalErrorMessage);
                }
            };
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * Pin a status by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> pin(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<Status> statusCall = mastodonStatusesService.pin(token, id);
            String errorMessage = null;
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    } else {
                        if (statusResponse.errorBody() != null) {
                            errorMessage = statusResponse.errorBody().string();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    errorMessage = e.getMessage() != null ? e.getMessage() : getApplication().getString(R.string.toast_error);
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            String finalErrorMessage = errorMessage;
            Runnable myRunnable = () -> {
                statusMutableLiveData.setValue(finalStatus);
                if (finalErrorMessage != null) {
                    Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, finalErrorMessage);
                }
            };
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * Unpin a status by ID
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Status>
     */
    public LiveData<Status> unPin(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        statusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            String errorMessage = null;
            Call<Status> statusCall = mastodonStatusesService.unPin(token, id);
            Status status = null;
            if (statusCall != null) {
                try {
                    Response<Status> statusResponse = statusCall.execute();
                    if (statusResponse.isSuccessful()) {
                        status = statusResponse.body();
                    } else {
                        if (statusResponse.errorBody() != null) {
                            errorMessage = statusResponse.errorBody().string();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    errorMessage = e.getMessage() != null ? e.getMessage() : getApplication().getString(R.string.toast_error);
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Status finalStatus = status;
            String finalErrorMessage = errorMessage;
            Runnable myRunnable = () -> {
                statusMutableLiveData.setValue(finalStatus);
                if (finalErrorMessage != null) {
                    Helper.sendToastMessage(getApplication(), Helper.RECEIVE_TOAST_TYPE_ERROR, finalErrorMessage);
                }
            };
            mainHandler.post(myRunnable);
        }).start();
        return statusMutableLiveData;
    }

    /**
     * Get card
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Card>
     */
    public LiveData<Card> getCard(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        cardMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<Card> cardCall = mastodonStatusesService.getCard(token, id);
            Card card = null;
            if (cardCall != null) {
                try {
                    Response<Card> cardResponse = cardCall.execute();
                    if (cardResponse.isSuccessful()) {
                        card = cardResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Card finalCard = card;
            Runnable myRunnable = () -> cardMutableLiveData.setValue(finalCard);
            mainHandler.post(myRunnable);
        }).start();
        return cardMutableLiveData;
    }

    /**
     * Get attachment
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Card>
     */
    public LiveData<Attachment> getAttachment(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        attachmentMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<Attachment> attachmentCall = mastodonStatusesService.getMedia(token, id);
            Attachment attachment = null;
            if (attachmentCall != null) {
                try {
                    Response<Attachment> attachmentResponse = attachmentCall.execute();
                    if (attachmentResponse.isSuccessful()) {
                        attachment = attachmentResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Attachment finalAttachment = attachment;
            Runnable myRunnable = () -> attachmentMutableLiveData.setValue(finalAttachment);
            mainHandler.post(myRunnable);
        }).start();
        return attachmentMutableLiveData;
    }

    /**
     * Update a media
     *
     * @param instance    Instance domain of the active account
     * @param token       Access token of the active account
     * @param id          String - Id of the media to update
     * @param file        URI
     * @param thumbnail   URI
     * @param description String
     * @param focus       String
     * @return LiveData<Attachment>
     */
    public LiveData<Attachment> updateAttachment(@NonNull String instance,
                                                 String token,
                                                 @NonNull String id,
                                                 @NonNull Uri file,
                                                 Uri thumbnail,
                                                 String description,
                                                 String focus) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        attachmentMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            MultipartBody.Part fileMultipartBody = null;
            MultipartBody.Part thumbnailMultipartBody = null;
            fileMultipartBody = Helper.getMultipartBody(getApplication(), "file", file);
            thumbnailMultipartBody = Helper.getMultipartBody(getApplication(), "file", thumbnail);
            Call<Attachment> attachmentCall = mastodonStatusesService.updateMedia(token, id, fileMultipartBody, thumbnailMultipartBody, description, focus);
            Attachment attachment = null;
            if (attachmentCall != null) {
                try {
                    Response<Attachment> attachmentResponse = attachmentCall.execute();
                    if (attachmentResponse.isSuccessful()) {
                        attachment = attachmentResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Attachment finalAttachment = attachment;
            Runnable myRunnable = () -> attachmentMutableLiveData.setValue(finalAttachment);
            mainHandler.post(myRunnable);
        }).start();
        return attachmentMutableLiveData;
    }

    /**
     * Get Poll
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the status
     * @return LiveData<Poll>
     */
    public LiveData<Poll> getPoll(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        pollMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<Poll> pollCall = mastodonStatusesService.getPoll(token, id);
            Poll poll = null;
            if (pollCall != null) {
                try {
                    Response<Poll> pollResponse = pollCall.execute();
                    if (pollResponse.isSuccessful()) {
                        poll = pollResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Poll finalPoll = poll;
            Runnable myRunnable = () -> pollMutableLiveData.setValue(finalPoll);
            mainHandler.post(myRunnable);
        }).start();
        return pollMutableLiveData;
    }

    /**
     * Vote on a Poll
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the poll
     * @param choices  int[] - array of choices
     * @return LiveData<Poll>
     */
    public LiveData<Poll> votePoll(@NonNull String instance, String token, String id, int[] choices) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        pollMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<Poll> pollCall = mastodonStatusesService.votePoll(token, id, choices);
            Poll poll = null;
            if (pollCall != null) {
                try {
                    Response<Poll> pollResponse = pollCall.execute();
                    if (pollResponse.isSuccessful()) {
                        poll = pollResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Poll finalPoll = poll;
            Runnable myRunnable = () -> pollMutableLiveData.setValue(finalPoll);
            mainHandler.post(myRunnable);
        }).start();
        return pollMutableLiveData;
    }

    /**
     * Get list of scheduled status
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param max_id   String
     * @param since_id String
     * @param min_id   String
     * @param limit    int
     * @return LiveData<ScheduledStatuses>
     */
    public LiveData<ScheduledStatuses> getScheduledStatuses(@NonNull String instance,
                                                            String token,
                                                            String max_id,
                                                            String since_id,
                                                            String min_id,
                                                            int limit) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        scheduledStatusesMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<List<ScheduledStatus>> scheduledStatuseCall = mastodonStatusesService.getScheduledStatuses(token, max_id, since_id, min_id, limit);
            List<ScheduledStatus> scheduledStatusList = null;
            Pagination pagination = null;
            if (scheduledStatuseCall != null) {
                try {
                    Response<List<ScheduledStatus>> scheduledStatusResponse = scheduledStatuseCall.execute();
                    if (scheduledStatusResponse.isSuccessful()) {
                        scheduledStatusList = scheduledStatusResponse.body();
                        pagination = MastodonHelper.getPagination(scheduledStatusResponse.headers());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            ScheduledStatuses scheduledStatuses = new ScheduledStatuses();
            scheduledStatuses.scheduledStatuses = scheduledStatusList;
            scheduledStatuses.pagination = pagination;
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Runnable myRunnable = () -> scheduledStatusesMutableLiveData.setValue(scheduledStatuses);
            mainHandler.post(myRunnable);
        }).start();
        return scheduledStatusesMutableLiveData;
    }

    /**
     * Get a scheduled status
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the scheduled status
     * @return LiveData<ScheduledStatus>
     */
    public LiveData<ScheduledStatus> getScheduledStatus(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        scheduledStatusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<ScheduledStatus> scheduledStatusCall = mastodonStatusesService.getScheduledStatus(token, id);
            ScheduledStatus scheduledStatus = null;
            if (scheduledStatusCall != null) {
                try {
                    Response<ScheduledStatus> scheduledStatusResponse = scheduledStatusCall.execute();
                    if (scheduledStatusResponse.isSuccessful()) {
                        scheduledStatus = scheduledStatusResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            ScheduledStatus finalScheduledStatus = scheduledStatus;
            Runnable myRunnable = () -> scheduledStatusMutableLiveData.setValue(finalScheduledStatus);
            mainHandler.post(myRunnable);
        }).start();
        return scheduledStatusMutableLiveData;
    }

    /**
     * Update a scheduled status
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the scheduled status
     * @return LiveData<ScheduledStatus>
     */
    public LiveData<ScheduledStatus> updateScheduledStatus(@NonNull String instance, String token, String id, Date scheduled_at) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        scheduledStatusMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<ScheduledStatus> scheduledStatusCall = mastodonStatusesService.updateScheduleStatus(token, id, scheduled_at);
            ScheduledStatus scheduledStatus = null;
            if (scheduledStatusCall != null) {
                try {
                    Response<ScheduledStatus> scheduledStatusResponse = scheduledStatusCall.execute();
                    if (scheduledStatusResponse.isSuccessful()) {
                        scheduledStatus = scheduledStatusResponse.body();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            ScheduledStatus finalScheduledStatus = scheduledStatus;
            Runnable myRunnable = () -> scheduledStatusMutableLiveData.setValue(finalScheduledStatus);
            mainHandler.post(myRunnable);
        }).start();
        return scheduledStatusMutableLiveData;
    }

    /**
     * Delete a scheduled status
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       String - id of the scheduled status
     * @return LiveData<ScheduledStatus>
     */
    public LiveData<Void> deleteScheduledStatus(@NonNull String instance, String token, String id) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        voidMutableLiveData = new MutableLiveData<>();
        new Thread(() -> {
            Call<Void> voidCall = mastodonStatusesService.deleteScheduledStatus(token, id);
            if (voidCall != null) {
                try {
                    voidCall.execute();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Handler mainHandler = new Handler(Looper.getMainLooper());
            Runnable myRunnable = () -> voidMutableLiveData.setValue(null);
            mainHandler.post(myRunnable);
        }).start();
        return voidMutableLiveData;
    }

    /**
     * React to a status with an emoji.
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       Local ID of an announcement
     * @param name     Unicode emoji, or shortcode of custom emoji
     */
    public void addReaction(@NonNull String instance, String token, @NonNull String id, @NonNull String name) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        new Thread(() -> {
            Call<Void> addReactionCall = mastodonStatusesService.addReaction(token, id, name);
            if (addReactionCall != null) {
                try {
                    addReactionCall.execute();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     * Undo a react emoji to a status.
     *
     * @param instance Instance domain of the active account
     * @param token    Access token of the active account
     * @param id       Local ID of an announcement
     * @param name     Unicode emoji, or shortcode of custom emoji
     */
    public void removeReaction(@NonNull String instance, String token, @NonNull String id, @NonNull String name) {
        MastodonStatusesService mastodonStatusesService = init(instance);
        new Thread(() -> {
            Call<Void> removeReactionCall = mastodonStatusesService.removeReaction(token, id, name);
            if (removeReactionCall != null) {
                try {
                    removeReactionCall.execute();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
