package ch.threema.app.services.messageplayer;

import android.app.Activity;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media3.session.MediaController;

import com.google.common.util.concurrent.ListenableFuture;

import org.slf4j.Logger;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import ch.threema.app.eventbus.GlobalEventBuses;
import ch.threema.app.messagereceiver.MessageReceiver;
import ch.threema.app.services.ConversationCategoryService;
import ch.threema.app.services.FileService;
import ch.threema.app.services.MessageService;
import ch.threema.app.services.NotificationPreferenceService;
import ch.threema.app.preference.service.PreferenceService;
import ch.threema.app.utils.MimeUtil;
import static ch.threema.base.utils.LoggingKt.getThreemaLogger;

import ch.threema.base.SessionScoped;
import ch.threema.domain.protocol.csp.messages.file.FileData;
import ch.threema.storage.models.AbstractMessageModel;
import ch.threema.storage.models.MessageType;

@SessionScoped
public class MessagePlayerServiceImpl implements MessagePlayerService {
    private static final Logger logger = getThreemaLogger("MessagePlayerServiceImpl");

    private final Map<Integer, MessagePlayer> messagePlayers = new HashMap<>();
    private final Context context;
    private final MessageService messageService;
    private final FileService fileService;
    private final PreferenceService preferenceService;
    @NonNull
    private final NotificationPreferenceService notificationPreferenceService;
    @NonNull
    private final ConversationCategoryService conversationCategoryService;
    @NonNull
    private final GlobalEventBuses globalEventBuses;

    public MessagePlayerServiceImpl(
        @NonNull Context context,
        @NonNull MessageService messageService,
        @NonNull FileService fileService,
        @NonNull PreferenceService preferenceService,
        @NonNull NotificationPreferenceService notificationPreferenceService,
        @NonNull ConversationCategoryService conversationCategoryService,
        @NonNull GlobalEventBuses globalEventBuses
    ) {
        this.context = context;
        this.messageService = messageService;
        this.fileService = fileService;
        this.preferenceService = preferenceService;
        this.notificationPreferenceService = notificationPreferenceService;
        this.conversationCategoryService = conversationCategoryService;
        this.globalEventBuses = globalEventBuses;
    }

    @Override
    public MessagePlayer createPlayer(
        AbstractMessageModel messageModel,
        @Nullable WeakReference<Activity> activityWeakReference,
        MessageReceiver<?> messageReceiver,
        @Nullable ListenableFuture<MediaController> mediaControllerFuture
    ) {
        int key = messageModel.getId();
        MessagePlayer o;

        synchronized (this.messagePlayers) {
            o = this.messagePlayers.get(key);

            if (o == null) {
                if (messageModel.getType() == MessageType.FILE) {
                    if (MimeUtil.isAudioFile(messageModel.getFileData().getMimeType())
                        && messageModel.getFileData().getRenderingType() == FileData.RENDERING_MEDIA) {
                        o = new AudioMessagePlayer(
                            this.context,
                            this.messageService,
                            this.fileService,
                            this.preferenceService,
                            this.notificationPreferenceService,
                            this.conversationCategoryService,
                            this.globalEventBuses,
                            messageReceiver,
                            mediaControllerFuture,
                            messageModel
                        );
                    } else if (MimeUtil.isAnimatedImageFormat(messageModel.getFileData().getMimeType())
                        && (messageModel.getFileData().getRenderingType() == FileData.RENDERING_MEDIA
                        || messageModel.getFileData().getRenderingType() == FileData.RENDERING_STICKER)) {
                        o = new AnimatedImageDrawableMessagePlayer(
                            this.context,
                            this.messageService,
                            this.fileService,
                            this.preferenceService,
                            messageReceiver,
                            messageModel
                        );
                    } else {
                        o = new FileMessagePlayer(
                            this.context,
                            this.messageService,
                            this.fileService,
                            messageReceiver,
                            messageModel
                        );
                    }
                }
                logger.debug("creating new player {}", key);
            } else {
                // make sure data model is updated as its status may have changed after the player has been created
                if (messageModel.getType() == MessageType.FILE &&
                    MimeUtil.isAudioFile(messageModel.getFileData().getMimeType()) &&
                    messageModel.getFileData().getRenderingType() == FileData.RENDERING_MEDIA) {
                    o.setData(messageModel.getFileData());
                }
                logger.debug("recycling existing player {}", key);
            }
            if (o != null) {
                if (activityWeakReference != null) {
                    Activity activity = activityWeakReference.get();
                    if (activity != null) {
                        if (o.isReceiverMatch(messageReceiver)) {
                            o.setCurrentActivity(activity, messageReceiver);
                        } else {
                            o.release();
                        }
                    }
                }
                this.messagePlayers.put(key, o);
            }
        }
        if (o != null) {
            o.addListener("service", new MessagePlayer.PlaybackListener() {
                @Override
                public void onPlay(AbstractMessageModel messageModel, boolean autoPlay) {
                    //call stop other players first!
                    logger.debug("onPlay autoPlay = {}", autoPlay);

                    if (!autoPlay) {
                        stopOtherPlayers(messageModel);
                    }
                }

                @Override
                public void onPause(AbstractMessageModel messageModel) {
                }

                @Override
                public void onStatusUpdate(AbstractMessageModel messageModel, int position) {
                }

                @Override
                public void onStop(AbstractMessageModel messageModel) {
                    logger.debug("onStop");
                }
            });
        }
        return o;
    }

    private void stopOtherPlayers(AbstractMessageModel messageModel) {
        logger.debug("stopOtherPlayers");
        synchronized (this.messagePlayers) {
            for (Map.Entry<Integer, MessagePlayer> entry : messagePlayers.entrySet()) {
                if (!entry.getKey().equals(messageModel.getId())) {
                    logger.debug("maybe stopping player {} if not running ", entry.getKey());
                    entry.getValue().stop();
                }
            }
        }
        logger.debug("otherPlayers stopped");
    }

    @Override
    public void release() {
        logger.debug("release all players");
        synchronized (this.messagePlayers) {
            Iterator<Map.Entry<Integer, MessagePlayer>> iterator = messagePlayers.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<Integer, MessagePlayer> pair = iterator.next();
                MessagePlayer mp = pair.getValue();
                mp.stop();
                if (mp.release()) {
                    iterator.remove();
                    logger.debug("Releasing player {}", pair.getKey());
                } else {
                    // remove ties to activity
                    mp.setCurrentActivity(null, null);
                    mp.removeListeners();
                    logger.debug("Keep downloading player {}", pair.getKey());
                }
            }
        }
    }

    @Override
    public void stopAll() {
        logger.debug("stop all players");
        synchronized (this.messagePlayers) {
            for (Map.Entry<Integer, MessagePlayer> entry : messagePlayers.entrySet()) {
                entry.getValue().stop();
            }
        }
    }

    @Override
    public void pauseAll(int source) {
        logger.debug("pause all players");
        synchronized (this.messagePlayers) {
            for (Map.Entry<Integer, MessagePlayer> entry : messagePlayers.entrySet()) {
                entry.getValue().pause(source);
            }
        }
    }

    @Override
    public void resumeAll(Activity activity, MessageReceiver messageReceiver, int source) {
        logger.debug("resume all players");
        synchronized (this.messagePlayers) {
            for (Map.Entry<Integer, MessagePlayer> entry : messagePlayers.entrySet()) {
                // re-attach message players to current activity
                if (entry.getValue().isReceiverMatch(messageReceiver)) {
                    entry.getValue().setCurrentActivity(activity, messageReceiver);
                    entry.getValue().resume(source);
                } else {
                    entry.getValue().release();
                }
            }
        }
    }

    @Override
    public void setTranscodeProgress(@NonNull AbstractMessageModel messageModel, int progress) {
        synchronized (this.messagePlayers) {
            for (Map.Entry<Integer, MessagePlayer> entry : messagePlayers.entrySet()) {
                if (entry.getKey().equals(messageModel.getId())) {
                    entry.getValue().setTranscodeProgress(progress);
                    return;
                }
            }
        }
    }

    @Override
    public void setTranscodeStart(@NonNull AbstractMessageModel messageModel) {
        synchronized (this.messagePlayers) {
            for (Map.Entry<Integer, MessagePlayer> entry : messagePlayers.entrySet()) {
                if (entry.getKey().equals(messageModel.getId())) {
                    entry.getValue().setTranscodeStart();
                    return;
                }
            }
        }
    }

    @Override
    public void setTranscodeFinished(@NonNull AbstractMessageModel messageModel, boolean success, @Nullable String message) {
        synchronized (this.messagePlayers) {
            for (Map.Entry<Integer, MessagePlayer> entry : messagePlayers.entrySet()) {
                if (entry.getKey().equals(messageModel.getId())) {
                    entry.getValue().setTranscodeFinished(success, message);
                    return;
                }
            }
        }
    }
}
