
// myMessageQueue provides functions to add messages we want to send to the message queue
// All messages are added to the queue before being sent
// This is useful for 2 reasons.
//  1. If the payment proof provider server is down/unreachable, we can automatically retry funding the message later
//  2. We can queue messages to send to users whose chat keys we do not yet have (and may not exist on the network at all)

package myMessageQueue

// For users whose keys are missing, we will wait for their chat keys to download in the background
// If we can't download their chat keys after trying for a certain time, we will discard the message

//TODO: If we disable our profile or delete our identity, clear our chat message queue
//TODO: A way to view the message queue through the GUI

import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/messaging/myChatMessages"
import "seekia/internal/messaging/peerChatKeys"
import "seekia/internal/messaging/sendMessages"
import "seekia/internal/myDatastores/myMapList"
import "seekia/internal/myIdentity"
import "seekia/internal/network/myProofFunds"
import "seekia/internal/parameters/getParameters"
import "seekia/internal/unixTime"

import "errors"
import "time"
import "sync"

// This will be locked whenever we are updating the message queue map list
var updatingMessageQueueMapListMutex sync.Mutex

var myMessageQueueMapListDatastore *myMapList.MyMapList

// This function must be called whenever an app user signs in
func InitializeMyMessageQueueDatastore()error{

	updatingMessageQueueMapListMutex.Lock()
	defer updatingMessageQueueMapListMutex.Unlock()

	newMyMessageQueueMapListDatastore, err := myMapList.CreateNewMapList("MyMessageQueue")
	if (err != nil) { return err }

	myMessageQueueMapListDatastore = newMyMessageQueueMapListDatastore

	return nil
}


//Outputs:
//	-bool: Parameters exist
//	-bool: Sufficient funds exist
//	-error
func AddMessageToMyMessageQueue(myIdentityHash [16]byte, recipientIdentityHash [16]byte, messageNetworkType byte, messageDuration int, messageCommunication string)(bool, bool, error){

	// We keep track of our identity to make sure if we don't clear the queue properly between changing user/importing new identity, messages will not be sent by the wrong identity

	myIdentityExists, myIdentityType, err := myIdentity.CheckIfIdentityHashIsMine(myIdentityHash)
	if (err != nil) { return false, false, err }
	if (myIdentityExists == false){
		return false, false, errors.New("AddMessageToMyMessageQueue called with an unknown identity.")
	}

	myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash)
	if (err != nil) {
		myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:])
		return false, false, errors.New("CheckIfIdentityHashIsMine returning invalid identity hash: " + myIdentityHashHex)
	}

	recipientIdentityHashString, recipientIdentityType, err := identity.EncodeIdentityHashBytesToString(recipientIdentityHash)
	if (err != nil) {
		recipientIdentityHashHex := encoding.EncodeBytesToHexString(recipientIdentityHash[:])
		return false, false, errors.New("AddMessageToMyMessageQueue called with invalid recipientIdentityHash: " + recipientIdentityHashHex)
	}

	if (myIdentityType != recipientIdentityType){
		return false, false, errors.New("AddMessageToMyMessageQueue called with mismatched my/recipient identityTypes.")
	}

	isValid := helpers.VerifyNetworkType(messageNetworkType)
	if (isValid == false){
		messageNetworkTypeString := helpers.ConvertByteToString(messageNetworkType)
		return false, false, errors.New("AddMessageToMyMessageQueue called with invalid messageNetworkType: " + messageNetworkTypeString)
	}

	messageNetworkTypeString := helpers.ConvertByteToString(messageNetworkType)

	messageDurationString := helpers.ConvertIntToString(messageDuration)

	messageIdentifierBytes, err := helpers.GetNewRandomBytes(20)
	if (err != nil) { return false, false, err }

	messageIdentifier := [20]byte(messageIdentifierBytes)
	messageIdentifierString := encoding.EncodeBytesToHexString(messageIdentifierBytes)

	// TimeAdded will be used as the messageCreationTime when we send the message
	currentTime := time.Now().Unix()
	currentTimeString := helpers.ConvertInt64ToString(currentTime)

	newMessageMap := map[string]string{
		"MessageIdentifier": messageIdentifierString,
		"MyIdentityHash": myIdentityHashString,
		"RecipientIdentityHash": recipientIdentityHashString,
		"NetworkType": messageNetworkTypeString,
		"MessageCommunication": messageCommunication,
		"MessageDuration": messageDurationString,
		"TimeAdded": currentTimeString,
	}

	estimatedMessageSize, err := sendMessages.GetEstimatedMessageSize(recipientIdentityHash, messageCommunication)
	if (err != nil) { return false, false, err }

	updatingMessageQueueMapListMutex.Lock()
	defer updatingMessageQueueMapListMutex.Unlock()

	parametersExist, sufficientFundsExist, err := myProofFunds.FreezeFundsForMessage(messageIdentifier, messageNetworkType, messageDuration, estimatedMessageSize)
	if (err != nil) { return false, false, err }
	if (parametersExist == false){
		return false, false, nil
	}
	if (sufficientFundsExist == false){
		return true, false, nil
	}

	err = myMessageQueueMapListDatastore.AddMapListItem(newMessageMap)
	if (err != nil) { return false, false, err }

	// Now we add queued message map to myChatMessages so the user will see a queued message in their GUI

	err = myChatMessages.AddMyNewMessageToMyChatMessages("Queued", [26]byte{}, messageIdentifier, myIdentityHash, recipientIdentityHash, messageNetworkType, currentTime, messageCommunication)
	if (err != nil) { return false, false, err }

	return true, true, nil
}

func PruneMessageQueue()error{

	//TODO: Delete messages that have already been sent

	// Use sendMessages.CheckIfMessageIsSent()

	return nil
}

// This function should be run automatically on a continual period
// It will attempt to send a random selection of messages from the queue
// It will contact 1 host per message
func AttemptToSendMessagesInQueue(identityType string, networkType byte, maximumMessagesToSend int)error{

	if (identityType != "Mate" && identityType != "Moderator"){
		return errors.New("AttemptToSendMessagesInQueue called with invalid identityType: " + identityType)
	}

	isValid := helpers.VerifyNetworkType(networkType)
	if (isValid == false){
		networkTypeString := helpers.ConvertByteToString(networkType)
		return errors.New("AttemptToSendMessagesInQueue called with invalid networkType: " + networkTypeString)
	}

	myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(identityType)
	if (err != nil) { return err }
	if (myIdentityExists == false){
		return nil
	}

	parametersExist, err := getParameters.CheckIfChatParametersExist(networkType)
	if (err != nil) { return err }
	if (parametersExist == false){
		// We need these parameters to send messages. We wait for them to download.
		return nil
	}

	updatingMessageQueueMapListMutex.Lock()
	defer updatingMessageQueueMapListMutex.Unlock()

	currentMessageQueueMapList, err := myMessageQueueMapListDatastore.GetMapList()
	if (err != nil) { return err }

	// We randomize the map list order, so we are sending a random subset of the messages

	helpers.RandomizeListOrder(currentMessageQueueMapList)

	// We use this to keep track of how many message sends we have succesfully started
	sentMessagesCount := 0

	messageMapsToDeleteList := make([]map[string]string, 0)

	for _, messageQueueMessageMap := range currentMessageQueueMapList{

		messageMyIdentityHashString, exists := messageQueueMessageMap["MyIdentityHash"]
		if (exists == false) {
			return errors.New("MessageQueue mapList malformed: Item missing MyIdentityHash")
		}

		messageMyIdentityHash, _, err := identity.ReadIdentityHashString(messageMyIdentityHashString)
		if (err != nil){
			return errors.New("MessageQueue mapList malformed: Item contains invalid MyIdentityHash: " + messageMyIdentityHashString)
		}

		if (messageMyIdentityHash != myIdentityHash){
			// Must be identity for different identityType
			continue
		}

		messageNetworkTypeString, exists := messageQueueMessageMap["NetworkType"]
		if (exists == false){
			return errors.New("MessageQueue mapList malformed: Item missing NetworkType.")
		}

		messageNetworkType, err := helpers.ConvertNetworkTypeStringToByte(messageNetworkTypeString)
		if (err != nil){
			return errors.New("MessageQueue mapList malformed: Item contains invalid NetworkType: " + messageNetworkTypeString)
		}
		if (messageNetworkType != networkType){
			// Message belongs to a different networkType.
			continue
		}

		messageIdentifierString, exists := messageQueueMessageMap["MessageIdentifier"]
		if (exists == false) {
			return errors.New("MessageQueue mapList malformed: Item missing MessageIdentifier")
		}

		messageIdentifierBytes, err := encoding.DecodeHexStringToBytes(messageIdentifierString)
		if (err != nil){
			return errors.New("MessageQueue mapList malformed: Item contains invalid messageIdentifier: Not Hex: " + messageIdentifierString)
		}

		if (len(messageIdentifierBytes) != 20){
			return errors.New("MessageQueue mapList malformed: Item contains invalid messageIdentifier: Invalid length: " + messageIdentifierString)
		}

		messageIdentifier := [20]byte(messageIdentifierBytes)

		messageRecipientString, exists := messageQueueMessageMap["RecipientIdentityHash"]
		if (exists == false) {
			return errors.New("MessageQueue mapList malformed: item missing RecipientIdentityHash")
		}

		messageRecipient, _, err := identity.ReadIdentityHashString(messageRecipientString)
		if (err != nil){
			return errors.New("MessageQueue mapList malformed: Item contains invalid RecipientIdentityHash: " + messageRecipientString)
		}

		messageTimeAdded, exists := messageQueueMessageMap["TimeAdded"]
		if (exists == false) {
			return errors.New("MessageQueue mapList malformed: Item missing TimeAdded")
		}

		messageTimeAddedInt64, err := helpers.ConvertCreationTimeStringToInt64(messageTimeAdded)
		if (err != nil) {
			return errors.New("MessageQueue mapList malformed: Item contains invalid TimeAdded: " + messageTimeAdded)
		}

		messageCommunication, exists := messageQueueMessageMap["MessageCommunication"]
		if (exists == false) {
			return errors.New("MessageQueue mapList malformed: item missing MessageCommunication")
		}

		// Now we check if recipient keys exist

		peerIsDisabled, peerChatKeysExist, _, _, _, err := peerChatKeys.GetPeerNewestActiveChatKeys(messageRecipient, networkType)
		if (err != nil) { return err }
		if (peerIsDisabled == true || peerChatKeysExist == false){

			// We see if message entry is more than 1 week old
			// If it is, we will delete it

			currentTime := time.Now().Unix()

			elapsedTime := currentTime - messageTimeAddedInt64

			maximumTime := unixTime.GetWeekUnix()

			if (elapsedTime > maximumTime){

				fundsFound, err := myProofFunds.ReleaseFrozenFundsForMessage(messageIdentifier)
				if (err != nil){ return err }
				if (fundsFound == false){
					// This should not happen
					// If the user manually released funds, the queued message that the funds were held for should have been deleted from queue
					return errors.New("MessageQueue message frozen funds not found.")
				}

				// We update the existing message status inside myChatMessages to "Failed"
				err = myChatMessages.AddMyNewMessageToMyChatMessages("Failed", [26]byte{}, messageIdentifier, messageMyIdentityHash, messageRecipient, messageNetworkType, messageTimeAddedInt64, messageCommunication)
				if (err != nil) { return err }

				messageMapsToDeleteList = append(messageMapsToDeleteList, messageQueueMessageMap)
			}

			// Peer chat keys were not found.
			// We will keep trying to download them. We skip this message for now.

			continue
		}

		// We initiate message send

		messageDurationString, exists := messageQueueMessageMap["MessageDuration"]
		if (exists == false) {
			return errors.New("MessageQueue map list malformed: item missing MessageDuration")
		}

		messageDurationToFund, err := helpers.ConvertStringToInt(messageDurationString)
		if (err != nil) {
			return errors.New("MessageQueue mapList malformed: Item contains invalid MessageDuration: " + messageDurationString)
		}

		messageIsPendingOrSent, successfullyStartedSend, err := sendMessages.StartSendingMessage(messageIdentifier, myIdentityHash, messageRecipient, messageNetworkType, messageTimeAddedInt64, messageCommunication, messageDurationToFund)
		if (err != nil) { return err }
		if (messageIsPendingOrSent == true){
			// We are already trying to send the message, or the message has already been sent.
			continue
		}
		if (successfullyStartedSend == false){

			// We don't have the required data 
			// We are probably missing parameters
			// We continue looking for messages to send

			continue
		}

		// We successfully started sending the message.
		// It will be deleted from the message queue automatically if the send is successful

		sentMessagesCount += 1

		if (sentMessagesCount >= maximumMessagesToSend){
			// We have sent the number of messages we wanted to
			break
		}
	}

	for _, messageMapToDelete := range messageMapsToDeleteList{

		err := myMessageQueueMapListDatastore.DeleteMapListItems(messageMapToDelete)
		if (err != nil) { return err }
	}

	return nil
}




