/*  _____ _
 * |_   _| |_  _ _ ___ ___ _ __  __ _
 *   | | | ' \| '_/ -_) -_) '  \/ _` |_
 *   |_| |_||_|_| \___\___|_|_|_\__,_(_)
 *
 * Threema for Android
 * Copyright (c) 2013-2023 Threema GmbH
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package ch.threema.app.services;

import android.content.Context;
import android.location.Location;
import android.net.Uri;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import ch.threema.app.messagereceiver.MessageReceiver;
import ch.threema.app.ui.MediaItem;
import ch.threema.app.voip.groupcall.GroupCallDescription;
import ch.threema.base.ProgressListener;
import ch.threema.base.ThreemaException;
import ch.threema.domain.models.MessageId;
import ch.threema.domain.protocol.csp.connection.MessageTooLongException;
import ch.threema.domain.protocol.csp.messages.AbstractGroupMessage;
import ch.threema.domain.protocol.csp.messages.AbstractMessage;
import ch.threema.domain.protocol.csp.messages.DeliveryReceiptMessage;
import ch.threema.domain.protocol.csp.messages.GroupDeliveryReceiptMessage;
import ch.threema.localcrypto.MasterKey;
import ch.threema.storage.models.AbstractMessageModel;
import ch.threema.storage.models.ContactModel;
import ch.threema.storage.models.DistributionListMessageModel;
import ch.threema.storage.models.GroupMessageModel;
import ch.threema.storage.models.MessageModel;
import ch.threema.storage.models.MessageState;
import ch.threema.storage.models.MessageType;
import ch.threema.storage.models.ServerMessageModel;
import ch.threema.storage.models.ballot.BallotModel;
import ch.threema.storage.models.data.MessageContentsType;
import ch.threema.storage.models.data.status.ForwardSecurityStatusDataModel;
import ch.threema.storage.models.data.status.GroupCallStatusDataModel;
import ch.threema.storage.models.data.status.VoipStatusDataModel;

/**
 * Handling methods for messages
 */
public interface MessageService {

	interface CompletionHandler {
		void sendComplete(AbstractMessageModel messageModel);
		void sendQueued(AbstractMessageModel messageModel);
		void sendError(int reason);
	}

	interface MessageFilter {
		/**
		 * Max number of messages that are returned with a response
		 */
		long getPageSize();

		/**
		 * If this returns a non-null value, then only messages with a message id smaller than the
		 * reference id will be returned.
		 */
		Integer getPageReferenceId();

		boolean withStatusMessages();
		boolean withUnsaved();
		boolean onlyUnread();
		boolean onlyDownloaded();
		MessageType[] types();
		@MessageContentsType int[] contentTypes();
	}

	public class MessageString {
		String message;
		String rawMessage;

		public MessageString(String message) {
			this.message = message;
			this.rawMessage = message;
		}

		MessageString(String message, String rawMessage) {
			this.message = message;
			this.rawMessage = rawMessage;
		}

		public String getMessage() {
			return message;
		}

		public String getRawMessage() {
			return rawMessage;
		}
	}

	/**
	 *
	 * @deprecated use createStatusMessage new style
	 */
	@Deprecated
	AbstractMessageModel createStatusMessage(String statusMessage, MessageReceiver receiver);

	AbstractMessageModel createVoipStatus(VoipStatusDataModel data,
										  MessageReceiver receiver,
										  boolean isOutbox,
										  boolean isRead);

	AbstractMessageModel createGroupCallStatus(@NonNull GroupCallStatusDataModel data,
											   @NonNull MessageReceiver receiver,
											   @Nullable ContactModel contactModel,
											   @Nullable GroupCallDescription call,
											   boolean isOutbox,
											   Date postedDate);

	AbstractMessageModel createForwardSecurityStatus(
		@NonNull MessageReceiver receiver,
		@ForwardSecurityStatusDataModel.ForwardSecurityStatusType int type,
		int quantity,
		@Nullable String staticText);

    AbstractMessageModel sendText(String message, MessageReceiver receiver) throws Exception;
	AbstractMessageModel sendLocation(@NonNull Location location, String poiName, MessageReceiver receiver, CompletionHandler completionHandler) throws ThreemaException, IOException;

	String getCorrelationId();

	@AnyThread
	void sendMediaAsync(@NonNull List<MediaItem> mediaItems, @NonNull List<MessageReceiver> messageReceivers);
	@AnyThread
	void sendMediaAsync(@NonNull List<MediaItem> mediaItems, @NonNull List<MessageReceiver> messageReceivers, @Nullable MessageServiceImpl.SendResultListener sendResultListener);

	@AnyThread
	void sendMediaSingleThread(
		@NonNull List<MediaItem> mediaItems,
		@NonNull List<MessageReceiver> messageReceivers
	);

	@WorkerThread
	AbstractMessageModel sendMedia(@NonNull List<MediaItem> mediaItems, @NonNull List<MessageReceiver> messageReceivers, @Nullable MessageServiceImpl.SendResultListener sendResultListener);

	@WorkerThread
	boolean sendUserAcknowledgement(@NonNull AbstractMessageModel messageModel, boolean markAsRead);

	boolean sendProfilePicture(MessageReceiver[] messageReceivers);

	void resendMessage(AbstractMessageModel messageModel, MessageReceiver receiver, CompletionHandler completionHandler) throws Exception;

	AbstractMessageModel sendBallotMessage(BallotModel ballotModel) throws MessageTooLongException;

	@WorkerThread
	boolean sendUserDecline(@NonNull AbstractMessageModel messageModel, boolean markAsRead);

	void updateMessageState(@NonNull final MessageId apiMessageId, MessageState state, @NonNull DeliveryReceiptMessage stateMessage);
	void updateGroupMessageState(@NonNull final MessageId apiMessageId, @NonNull MessageState state, @NonNull GroupDeliveryReceiptMessage stateMessage);
	void updateMessageStateForOutgoingMessage(final MessageId apiMessageId, MessageState state, Date stateDate);
	boolean markAsRead(AbstractMessageModel message, boolean silent) throws ThreemaException;

	@WorkerThread
	boolean markAsConsumed(AbstractMessageModel message) throws ThreemaException;

	void remove(AbstractMessageModel messageModel);

	/**
	 * if silent is true, no event will be fired on delete
	 */
	void remove(AbstractMessageModel messageModel, boolean silent);

	boolean processIncomingContactMessage(AbstractMessage message) throws Exception;
	boolean processIncomingGroupMessage(AbstractGroupMessage message) throws Exception;

	@WorkerThread
	List<AbstractMessageModel> getMessagesForReceiver(@NonNull MessageReceiver receiver, MessageFilter messageFilter, boolean appendUnreadMessage);
	@WorkerThread
	List<AbstractMessageModel> getMessagesForReceiver(@NonNull MessageReceiver receiver, MessageFilter messageFilter);
	@WorkerThread
	List<AbstractMessageModel> getMessagesForReceiver(@NonNull MessageReceiver receiver);
	List<AbstractMessageModel> getMessageForBallot(BallotModel ballotModel);
	List<AbstractMessageModel> getContactMessagesForText(String query, boolean includeArchived);
	List<AbstractMessageModel> getGroupMessagesForText(String query, boolean includeArchived);

	MessageModel getContactMessageModel(final Integer id, boolean lazy);
	GroupMessageModel getGroupMessageModel(final Integer id, boolean lazy);
	DistributionListMessageModel getDistributionListMessageModel(final Integer id, boolean lazy);

	MessageString getMessageString(AbstractMessageModel messageModel, int maxLength);
	MessageString getMessageString(AbstractMessageModel messageModel, int maxLength, boolean withPrefix);

	void saveIncomingServerMessage(ServerMessageModel msg);

	boolean downloadMediaMessage(AbstractMessageModel mediaMessageModel, ProgressListener progressListener) throws Exception;
	boolean cancelMessageDownload(AbstractMessageModel messageModel);
	void cancelMessageUpload(AbstractMessageModel messageModel);

	void saveMessageQueueAsync();
	void saveMessageQueue(@NonNull MasterKey masterKey);

	void removeAll() throws SQLException, IOException, ThreemaException;
	void save(AbstractMessageModel messageModel);

	void markConversationAsRead(MessageReceiver messageReceiver, NotificationService notificationService);
	void markMessageAsRead(AbstractMessageModel abstractMessageModel, NotificationService notificationService);

	/**
	 * count all message records (normal, group and distribution lists)
	 */
	long getTotalMessageCount();

	boolean shareMediaMessages(Context context, ArrayList<AbstractMessageModel> models, ArrayList<Uri> shareFileUris, String caption);
	boolean viewMediaMessage(Context context, AbstractMessageModel model, Uri uri);
	boolean shareTextMessage(Context context, AbstractMessageModel model);
	AbstractMessageModel getMessageModelFromId(int id, String type);
	@Nullable AbstractMessageModel getMessageModelByApiMessageId(String id, @MessageReceiver.MessageReceiverType int type);

	void cancelVideoTranscoding(AbstractMessageModel messageModel);

	/**
	 * Create a message receiver for the specified message model
	 * @param messageModel AbstractMessageModel to create a receiver for
	 * @return MessageReceiver
	 * @throws ThreemaException in case no MessageReceiver could be created or the AbstractMessageModel is none of the three possible message types
	 */
	MessageReceiver getMessageReceiver(AbstractMessageModel messageModel) throws ThreemaException;
}
