
// badgerDatabase provides functions to read and write to the Badger database.
// This database is used to store profiles, messages, reviews, reports, and more.

package badgerDatabase

// Below are the kinds of information being stored, and the format in which they are stored.

// Mate Profiles
//	-Key = Prefix + Profile hash
//	-Value = Mate profile bytes

// Host Profiles
//	-Key = Prefix + Profile hash
//	-Value = Host profile bytes

// Moderator Profiles
//	-Key = Prefix + Profile hash
//	-Value = Moderator profile bytes

// Messages
//	-Key = Prefix + Message Hash
//	-Value = Message bytes

// Reviews
//	-Key = Prefix + Review hash
//	-Value = Review bytes

// Reports
//	-Key = Prefix + Report Hash
//	-Value = Report bytes

// Profile Metadata
//	-Key: Prefix + Profile Hash
//	-Value: Messagepack encoded profile metadata. Described in contentMetadata.go

// Message Metadata
//	-Key: Prefix + Message Hash
//	-Value: Messagepack encoded message metadata. Described in contentMetadata.go

// The rest of the described database entries do not store data, but rather store information about the data
// These will make it faster to retrieve profiles, messages, reviews, and reports
// For example, review and report lists make it faster to find reviews/reports of a particular message/identity/profile/attribute

// Attribute Profiles List
//	-Key = Prefix + Attribute Hash
//	-Value = Comma separated list of all profile hashes which contain this attribute

// Mate Identity Profiles List:
//	-Key = Prefix + Identity hash
//	-Value = Comma separated list of the Mate identity's profile hashes

// Host Identity Profiles List:
//	-Key = Prefix + Identity Hash
//	-Value = Comma separated list of the Host identity's profile hashes

// Moderator Identity Profiles List:
//	-Key = Prefix + Identity Hash
//	-Value = Comma separated list of the Moderator identity's profile hashes

// Inbox Messages List:
//	-Key = Prefix + Inbox
//	-Value = Comma separated list of message hashes of messages in a particular inbox

// Reviewer Identity Reviews List
// -Key = Prefix + Reviewer Identity Hash
// -Value = Comma separated list of all review hashes of identity reviews authored by a reviewer (moderator)

// Reviewer Profile Reviews List
// -Key = Prefix + Reviewer Identity Hash
// -Value = Comma separated list of all review hashes of profile reviews authored by a reviewer (moderator)

// Reviewer Attribute Reviews List
// -Key = Prefix + Reviewer Identity Hash
// -Value = Comma separated list of all review hashes of attribute reviews authored by a reviewer (moderator)

// Reviewer Message Reviews List
// -Key = Prefix + Reviewer Identity Hash
// -Value = Comma separated list of all review hashes of message reviews authored by a reviewer (moderator)

// Identity Reviews List
//	-Key = Prefix + Reviewed Identity Hash
//	-Value = Comma separated list of all review hashes for reviews of a particular identity.
// These are the identities of the users who are being reviewed, not the identities of the moderators who created the reviews 

// Profile Reviews List:
//	-Key = Prefix + Reviewed Profile Hash
//	-Value = Comma separated list of all review hashes for reviews of a particular profile hash

// Attribute Reviews List:
//	-Key = Prefix + Reviewed Profile Attribute Hash
//	-Value = Comma separated list of all review hashes for reviews of a particular profile attribute

// Message Reviews List:
//	-Key = Prefix + Reviewed Message hash
//	-Value = Comma separated list of all reviews hashes for reviews of a particular message


// Identity Reports List
//	-Key = Prefix + Reported Identity Hash
//	-Value = Comma separated list of all report hashes for reports of a particular identity.

// Profile Reports List:
//	-Key = Prefix + Reported Profile Hash
//	-Value = Comma separated list of all report hashes for reports of a particular profile

// Attribute Reports List:
//	-Key = Prefix + Reported Profile Attribute Hash
//	-Value = Comma separated list of all report hashes for reports of a particular profile attribute

// Message Reports List:
//	-Key = Prefix + Reported Message Hash
//	-Value = Comma separated list of all report hashes for reports of a particular message

//TODO: Be aware that we cannot prune the attributeProfilesList until all of the attribute's profiles are expired (if mate), or if the attribute's author identity is no longer funded
//		We need to do this so we can keep track of which attribute reviews can be pruned
//		We can only prune an attribute review once the profile is expired (if mate), or the author identity is no longer funded
//		We use the attributeProfilesList to keep track of which profile(s) the attribute belongs to
//		We will delete profiles that are banned, so keeping track of the attribute's profiles is necessary
//		Each profile's author information is stored in contentMetadata

import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/localFilesystem"
import "seekia/internal/moderation/readReports"
import "seekia/internal/moderation/readReviews"
import "seekia/internal/profiles/readProfiles"

import badger "github.com/dgraph-io/badger/v4"

import goFilepath "path/filepath"

import "slices"
import "bytes"
import "sync"
import "errors"

const mateProfilesPrefix byte = 1
const hostProfilesPrefix byte = 2
const moderatorProfilesPrefix byte = 3

const messagesPrefix byte = 4
const reviewsPrefix byte = 5
const reportsPrefix byte = 6

const profileMetadataPrefix byte = 7
const messageMetadataPrefix byte = 8
const attributeProfilesListPrefix byte = 9

const mateIdentityProfilesListPrefix byte = 10
const hostIdentityProfilesListPrefix byte = 11
const moderatorIdentityProfilesListPrefix byte = 12

const inboxMessagesListPrefix byte = 13

const reviewerIdentityReviewsListPrefix byte = 14
const reviewerProfileReviewsListPrefix byte = 15
const reviewerAttributeReviewsListPrefix byte = 16
const reviewerMessageReviewsListPrefix byte = 17

const identityReviewsListPrefix byte = 18
const profileReviewsListPrefix byte = 19
const attributeReviewsListPrefix byte = 20
const messageReviewsListPrefix byte = 21

const identityReportsListPrefix byte = 22
const profileReportsListPrefix byte = 23
const attributeReportsListPrefix byte = 24
const messageReportsListPrefix byte = 25


// We use a lock for for database entries which contain comma separated values
// This is because badger will return a ErrConflict if we do a txn.Get and txn.Set in the same operation
// List entries contain values which need to be read and edited before being committed
// For example, we need to read the exiting value "1,2,3" and append "4", so result is "1,2,3,4"
// Entries such as profiles only need to be set or deleted, so we don't need to use a Mutex

var editingAttributeProfilesListMutex sync.Mutex

var editingMateIdentityProfilesListMutex sync.Mutex
var editingHostIdentityProfilesListMutex sync.Mutex
var editingModeratorIdentityProfilesListMutex sync.Mutex

var editingInboxMessagesListMutex sync.Mutex

var editingReviewerIdentityReviewsListMutex sync.Mutex
var editingReviewerProfileReviewsListMutex sync.Mutex
var editingReviewerAttributeReviewsListMutex sync.Mutex
var editingReviewerMessageReviewsListMutex sync.Mutex

var editingIdentityReviewsListMutex sync.Mutex
var editingProfileReviewsListMutex sync.Mutex
var editingAttributeReviewsListMutex sync.Mutex
var editingMessageReviewsListMutex sync.Mutex

var editingIdentityReportsListMutex sync.Mutex
var editingProfileReportsListMutex sync.Mutex
var editingAttributeReportsListMutex sync.Mutex
var editingMessageReportsListMutex sync.Mutex

var myDatabase *badger.DB

// We use this when establishing and reading from the myDatabase object
var databaseMutex sync.RWMutex

func startDatabase()error{

	databaseMutex.RLock()
	
	if (myDatabase != nil) {
		if (myDatabase.IsClosed() == false){
			databaseMutex.RUnlock()
			return nil
		}
	}
	databaseMutex.RUnlock()

	databaseDirectory, err := localFilesystem.GetAppDatabaseFolderPath()
	if (err != nil) { return err }

	_, err = localFilesystem.CreateFolder(databaseDirectory)
	if (err != nil) { return err }

	// We put the badger database into its own folder
	// We will need to keep multiple databases and migrate data to new versions when they are released
	// This also makes it easier to switch from using Badger to a different database if needed.

	databaseFolderpath := goFilepath.Join(databaseDirectory, "BadgerDatabaseVersion4")

	_, err = localFilesystem.CreateFolder(databaseFolderpath)
	if (err != nil) { return err }
	
	databaseOptions := badger.DefaultOptions(databaseFolderpath)

	newDatabase, err := badger.Open(databaseOptions)
	if (err != nil) { return err }

	databaseMutex.Lock()
	myDatabase = newDatabase
	databaseMutex.Unlock()

	return nil
}


func StopDatabase(){

	if (myDatabase != nil) {
		myDatabase.Close()
	}
}

func AddUserProfile(profileType string, profileHash [28]byte, profileBytes []byte)error{

	hashIsValid, err := readProfiles.VerifyProfileHash(profileHash, true, profileType, false, false)
	if (err != nil) {
		return errors.New("AddUserProfile called with invalid profileType: " + profileType)
	}
	if (hashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return errors.New("AddUserProfile called with invalid profileHash: " + profileHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	getKeyPrefix := func()(byte, error){

		if (profileType == "Mate"){
			return mateProfilesPrefix, nil

		} else if (profileType == "Host"){
			return hostProfilesPrefix, nil

		} else if (profileType == "Moderator"){
			return moderatorProfilesPrefix, nil
		}

		return 0, errors.New("VerifyProfileHash not verifying profileType: " + profileType)
	}

	keyPrefix, err := getKeyPrefix()
	if (err != nil) { return err }

	key := []byte{keyPrefix}
	key = append(key, profileHash[:]...)

	err = setDatabaseEntry(key, profileBytes)
	if (err != nil) { return err }

	return nil
}

//Outputs
//	-bool: Profile exists
//	-[]byte: User profile bytes
//	-error
func GetUserProfile(profileType string, profileHash [28]byte)(bool, []byte, error){

	hashIsValid, err := readProfiles.VerifyProfileHash(profileHash, true, profileType, false, false)
	if (err != nil) {
		return false, nil, errors.New("GetUserProfile called with invalid profileType: " + profileType)
	}
	if (hashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return false, nil, errors.New("GetUserProfile called with invalid profileHash: " + profileHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	getKeyPrefix := func()(byte, error){

		if (profileType == "Mate"){
			return mateProfilesPrefix, nil

		} else if (profileType == "Host"){
			return hostProfilesPrefix, nil

		} else if (profileType == "Moderator"){
			return moderatorProfilesPrefix, nil
		}

		return 0, errors.New("VerifyProfileHash not verifying profileType: " + profileType)
	}

	keyPrefix, err := getKeyPrefix()
	if (err != nil) { return false, nil, err }

	key := []byte{keyPrefix}
	key = append(key, profileHash[:]...)

	exists, profileData, err := getDatabaseValue(key)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil
	}

	return true, profileData, nil
}


func DeleteUserProfile(profileType string, profileHash [28]byte)error{
	
	hashIsValid, err := readProfiles.VerifyProfileHash(profileHash, true, profileType, false, false)
	if (err != nil) {
		return errors.New("DeleteUserProfile called with invalid profileType: " + profileType)
	}
	if (hashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return errors.New("DeleteUserProfile called with invalid profileHash: " + profileHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	getKeyPrefix := func()(byte, error){

		if (profileType == "Mate"){
			return mateProfilesPrefix, nil

		} else if (profileType == "Host"){
			return hostProfilesPrefix, nil

		} else if (profileType == "Moderator"){
			return moderatorProfilesPrefix, nil
		}

		return 0, errors.New("VerifyProfileHash not verifying profileType: " + profileType)
	}

	keyPrefix, err := getKeyPrefix()
	if (err != nil) { return err }

	key := []byte{keyPrefix}
	key = append(key, profileHash[:]...)

	err = deleteDatabaseEntry(key)
	if (err != nil) { return err }	

	return nil
}

func GetNumberOfUserProfiles()(int64, error){

	err := startDatabase()
	if (err != nil) { return 0, err }

	numberOfMateProfiles, err := GetNumberOfProfiles("Mate")
	if (err != nil) { return 0, err }

	numberOfHostProfiles, err := GetNumberOfProfiles("Host")
	if (err != nil) { return 0, err }
	
	GetNumberOfModeratorProfiles, err := GetNumberOfProfiles("Moderator")
	if (err != nil) { return 0, err }
	
	result := numberOfMateProfiles + numberOfHostProfiles + GetNumberOfModeratorProfiles

	return result, nil
}

func GetNumberOfProfiles(profileType string)(int64, error){

	err := startDatabase()
	if (err != nil) { return 0, err }

	getKeyPrefix := func()(byte, error){

		if (profileType == "Mate"){
			return mateProfilesPrefix, nil

		} else if (profileType == "Host"){
			return hostProfilesPrefix, nil

		} else if (profileType == "Moderator"){
			return moderatorProfilesPrefix, nil
		}

		return 0, errors.New("GetNumberOfProfiles called with invalid profileType: " + profileType)
	}

	keyPrefix, err := getKeyPrefix()
	if (err != nil) { return 0, err }

	numberOfProfiles, err := getNumberOfKeysInDatabaseWithPrefix(keyPrefix)
	if (err != nil) { return 0, err }

	return numberOfProfiles, nil
}

func GetAllProfileHashes(profileType string)([][28]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	getKeyPrefix := func()(byte, error){

		if (profileType == "Mate"){
			return mateProfilesPrefix, nil

		} else if (profileType == "Host"){
			return hostProfilesPrefix, nil

		} else if (profileType == "Moderator"){
			return moderatorProfilesPrefix, nil
		}

		return 0, errors.New("GetAllProfileHashes called with invalid profileType: " + profileType)
	}

	keyPrefix, err := getKeyPrefix()
	if (err != nil) { return nil, err }

	profileHashesBytesList, err := getAllKeysInDatabaseWithPrefix(keyPrefix, true)
	if (err != nil) { return nil, err }

	profileHashesList := make([][28]byte, 0, len(profileHashesBytesList))

	for _, profileHash := range profileHashesBytesList{

		if (len(profileHash) != 28){
			return nil, errors.New("profileHashesBytesList contains invalid length profile hash.")
		}

		profileHashArray := [28]byte(profileHash)

		profileHashesList = append(profileHashesList, profileHashArray)
	}

	return profileHashesList, nil
}


func AddChatMessage(messageHash [26]byte, messageBytes []byte)error{

	err := startDatabase()
	if (err != nil) { return err }

	key := []byte{messagesPrefix}
	key = append(key, messageHash[:]...)

	err = setDatabaseEntry(key, messageBytes)
	if (err != nil) { return err }

	return nil
}


func GetChatMessage(messageHash [26]byte)(bool, []byte, error){

	err := startDatabase()
	if (err != nil) { return false, nil, err }

	key := []byte{messagesPrefix}
	key = append(key, messageHash[:]...)

	exists, messageBytes, err := getDatabaseValue(key)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil
	}

	return true, messageBytes, nil
}

func DeleteChatMessage(messageHash [26]byte)error{

	err := startDatabase()
	if (err != nil) { return err }

	key := []byte{messagesPrefix}
	key = append(key, messageHash[:]...)

	err = deleteDatabaseEntry(key)
	if (err != nil) { return err }	

	return nil
}

func GetNumberOfChatMessages()(int64, error){

	err := startDatabase()
	if (err != nil) { return 0, err }

	numberOfMessages, err := getNumberOfKeysInDatabaseWithPrefix(messagesPrefix)
	if (err != nil) { return 0, err }

	return numberOfMessages, nil
}

func GetAllChatMessageHashes()([][26]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	messageHashesBytesList, err := getAllKeysInDatabaseWithPrefix(messagesPrefix, true)
	if (err != nil) { return nil, err }

	messageHashesList := make([][26]byte, 0, len(messageHashesBytesList))

	for _, messageHashBytes := range messageHashesBytesList{

		if (len(messageHashBytes) != 26){
			return nil, errors.New("messageHashesBytesList contains invalid length messageHash")	
		}

		messageHashArray := [26]byte(messageHashBytes)

		messageHashesList = append(messageHashesList, messageHashArray)
	}

	return messageHashesList, nil
}


func AddReview(reviewHash [29]byte, reviewBytes []byte)error{

	isValid, err := readReviews.VerifyReviewHash(reviewHash, false, "")
	if (err != nil) { return err }
	if (isValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("AddReview called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	key := []byte{reviewsPrefix}
	key = append(key, reviewHash[:]...)

	err = setDatabaseEntry(key, reviewBytes)
	if (err != nil) { return err }

	return nil
}


func GetReview(reviewHash [29]byte)(bool, []byte, error){

	isValid, err := readReviews.VerifyReviewHash(reviewHash, false, "")
	if (err != nil) { return false, nil, err }
	if (isValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return false, nil, errors.New("GetReview called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	key := []byte{reviewsPrefix}
	key = append(key, reviewHash[:]...)

	exists, reviewBytes, err := getDatabaseValue(key)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil
	}

	return true, reviewBytes, nil
}

func DeleteReview(reviewHash [29]byte)error{

	isValid, err := readReviews.VerifyReviewHash(reviewHash, false, "")
	if (err != nil) { return err }
	if (isValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("DeleteReview called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	key := []byte{reviewsPrefix}
	key = append(key, reviewHash[:]...)

	err = deleteDatabaseEntry(key)
	if (err != nil) { return err }	

	return nil
}

func GetNumberOfReviews()(int64, error){

	err := startDatabase()
	if (err != nil) { return 0, err }

	numberOfReviews, err := getNumberOfKeysInDatabaseWithPrefix(reviewsPrefix)
	if (err != nil) { return 0, err }

	return numberOfReviews, nil
}


func AddReport(reportHash [30]byte, reportBytes []byte)error{

	isValid, err := readReports.VerifyReportHash(reportHash, false, "")
	if (err != nil) { return err }
	if (isValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return errors.New("AddReport called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	key := []byte{reportsPrefix}
	key = append(key, reportHash[:]...)

	err = setDatabaseEntry(key, reportBytes)
	if (err != nil) { return err }
	
	return nil
}


func GetReport(reportHash [30]byte)(bool, []byte, error){

	isValid, err := readReports.VerifyReportHash(reportHash, false, "")
	if (err != nil) { return false, nil, err }
	if (isValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return false, nil, errors.New("GetReport called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	key := []byte{reportsPrefix}
	key = append(key, reportHash[:]...)

	exists, reportBytes, err := getDatabaseValue(key)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil
	}

	return true, reportBytes, nil
}

func DeleteReport(reportHash [30]byte)error{

	isValid, err := readReports.VerifyReportHash(reportHash, false, "")
	if (err != nil) { return err }
	if (isValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return errors.New("DeleteReport called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	key := []byte{reportsPrefix}
	key = append(key, reportHash[:]...)

	err = deleteDatabaseEntry(key)
	if (err != nil) { return err }

	return nil
}

func GetNumberOfReports()(int64, error){

	err := startDatabase()
	if (err != nil) { return 0, err }

	numberOfReports, err := getNumberOfKeysInDatabaseWithPrefix(reportsPrefix)
	if (err != nil) { return 0, err }

	return numberOfReports, nil
}


func AddProfileMetadata(profileHash [28]byte, profileMetadata []byte)error{

	isValid, err := readProfiles.VerifyProfileHash(profileHash, false, "", false, false)
	if (err != nil) { return err }
	if (isValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return errors.New("AddProfileMetadata called with invalid profileHash: " + profileHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	entryKey := []byte{profileMetadataPrefix}
	entryKey = append(entryKey, profileHash[:]...)

	err = setDatabaseEntry(entryKey, profileMetadata)
	if (err != nil) { return err }
	
	return nil
}

//Outputs:
//	-bool: Metadata found
//	-[]byte: Profile Metadata
//	-error
func GetProfileMetadata(profileHash [28]byte)(bool, []byte, error){

	isValid, err := readProfiles.VerifyProfileHash(profileHash, false, "", false, false)
	if (err != nil) { return false, nil, err }
	if (isValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return false, nil, errors.New("GetProfileMetadata called with invalid profileHash: " + profileHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{profileMetadataPrefix}
	entryKey = append(entryKey, profileHash[:]...)

	exists, entryValue, err := getDatabaseValue(entryKey)
	if (err != nil) { return false, nil, err }
	if (exists == false){
		return false, nil, nil
	}

	return true, entryValue, nil
}

func DeleteProfileMetadata(profileHash [28]byte)error{

	isValid, err := readProfiles.VerifyProfileHash(profileHash, false, "", false, false)
	if (err != nil) { return err }
	if (isValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return errors.New("DeleteProfileMetadata called with invalid profileHash: " + profileHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	entryKey := []byte{profileMetadataPrefix}
	entryKey = append(entryKey, profileHash[:]...)

	err = deleteDatabaseEntry(entryKey)
	if (err != nil) { return err }

	return nil
}

func GetAllProfileHashesWithMetadata()([][28]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	profileHashesBytesList, err := getAllKeysInDatabaseWithPrefix(profileMetadataPrefix, true)
	if (err != nil) { return nil, err }

	profileHashesList := make([][28]byte, 0, len(profileHashesBytesList))

	for _, profileHashBytes := range profileHashesBytesList{

		if (len(profileHashBytes) != 28){
			profileHashHex := encoding.EncodeBytesToHexString(profileHashBytes)
			return nil, errors.New("profileHashesBytesList contains invalid length profileHash: " + profileHashHex)
		}

		profileHashArray := [28]byte(profileHashBytes)

		profileHashesList = append(profileHashesList, profileHashArray)
	}

	return profileHashesList, nil
}

func AddMessageMetadata(messageHash [26]byte, messageMetadata []byte)error{

	err := startDatabase()
	if (err != nil) { return err }

	entryKey := []byte{messageMetadataPrefix}
	entryKey = append(entryKey, messageHash[:]...)

	err = setDatabaseEntry(entryKey, messageMetadata)
	if (err != nil) { return err }

	return nil
}

//Outputs:
//	-bool: Message metadata exists
//	-[]byte: Message Metadata
//	-error
func GetMessageMetadata(messageHash [26]byte)(bool, []byte, error){

	err := startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{messageMetadataPrefix}
	entryKey = append(entryKey, messageHash[:]...)

	exists, entryValue, err := getDatabaseValue(entryKey)
	if (err != nil) { return false, nil, err }
	if (exists == false){
		return false, nil, nil
	}

	return true, entryValue, nil
}

func DeleteMessageMetadata(messageHash [26]byte)error{

	err := startDatabase()
	if (err != nil) { return err }

	entryKey := []byte{messageMetadataPrefix}
	entryKey = append(entryKey, messageHash[:]...)

	err = deleteDatabaseEntry(entryKey)
	if (err != nil) { return err }

	return nil
}

func GetAllMessageHashesWithMetadata()([][26]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	messageHashesBytesList, err := getAllKeysInDatabaseWithPrefix(messageMetadataPrefix, true)
	if (err != nil) { return nil, err }

	messageHashesList := make([][26]byte, 0, len(messageHashesBytesList))

	for _, messageHashBytes := range messageHashesBytesList{

		if (len(messageHashBytes) != 26){
			messageHashHex := encoding.EncodeBytesToHexString(messageHashBytes)
			return nil, errors.New("messageHashesBytesList contains invalid length messageHash: " + messageHashHex)
		}

		messageHashArray := [26]byte(messageHashBytes)

		messageHashesList = append(messageHashesList, messageHashArray)
	}

	return messageHashesList, nil
}

func AddAttributeProfile(attributeHash [27]byte, profileHash [28]byte)error{
	
	attributeProfileType, _, err := readProfiles.ReadAttributeHashMetadata(attributeHash)
	if (err != nil){
		attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
		return errors.New("AddAttributeProfile called with invalid attributeHash: " + attributeHashHex)
	}
	hashIsValid, err := readProfiles.VerifyProfileHash(profileHash, true, attributeProfileType, false, false)
	if (err != nil) { return err }
	if (hashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return errors.New("AddAttributeProfile called with invalid profileHash: " + profileHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingAttributeProfilesListMutex.Lock()
	defer editingAttributeProfilesListMutex.Unlock()

	entryKey := []byte{attributeProfilesListPrefix}
	entryKey = append(entryKey, attributeHash[:]...)

	err = addItemToEntryList(entryKey, 28, profileHash[:])
	if (err != nil) { return err }

	return nil
}


func GetAttributeProfilesList(attributeHash [27]byte)(bool, [][28]byte, error){

	hashIsValid, err := readProfiles.VerifyAttributeHash(attributeHash, false, "", false, false)
	if (err != nil){ return false, nil, err }
	if (hashIsValid == false){
		attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
		return false, nil, errors.New("GetAttributeProfilesList called with invalid attributeHash: " + attributeHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{attributeProfilesListPrefix}
	entryKey = append(entryKey, attributeHash[:]...)

	exists, profileHashesBytesList, err := getEntryList(entryKey, 28)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil 
	}

	profileHashesList := make([][28]byte, 0, len(profileHashesBytesList))

	for _, profileHashBytes := range profileHashesBytesList{

		if (len(profileHashBytes) != 28){
			return false, nil, errors.New("getEntryList returning invalid bytes length item.")
		}

		profileHashArray := [28]byte(profileHashBytes)

		profileHashesList = append(profileHashesList, profileHashArray)
	}

	return true, profileHashesList, nil
}

func DeleteAttributeProfile(attributeHash [27]byte, profileHash [28]byte)error{

	attributeProfileType, _, err := readProfiles.ReadAttributeHashMetadata(attributeHash)
	if (err != nil) {
		attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
		return errors.New("DeleteAttributeHashProfile called with invalid attributeHash: " + attributeHashHex)
	}

	hashIsValid, err := readProfiles.VerifyProfileHash(profileHash, true, attributeProfileType, false, false)
	if (err != nil) { return err }
	if (hashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return errors.New("DeleteAttributeHashProfile called with invalid profileHash: " + profileHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingAttributeProfilesListMutex.Lock()
	defer editingAttributeProfilesListMutex.Unlock()

	entryKey := []byte{attributeProfilesListPrefix}
	entryKey = append(entryKey, attributeHash[:]...)

	err = deleteItemFromEntryList(entryKey, 28, profileHash[:])
	if (err != nil) { return err }

	return nil
}

func GetAllProfileAttributesHashes()([][27]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	attributeHashesBytesList, err := getAllKeysInDatabaseWithPrefix(attributeProfilesListPrefix, true)
	if (err != nil) { return nil, err }

	attributeHashesList := make([][27]byte, 0, len(attributeHashesBytesList))

	for _, attributeHashBytes := range attributeHashesBytesList{

		if (len(attributeHashBytes) != 27){
			attributeHashHex := encoding.EncodeBytesToHexString(attributeHashBytes)
			return nil, errors.New("attributeHashesBytesList contains invalid length attribute hash: " + attributeHashHex)
		}

		attributeHashArray := [27]byte(attributeHashBytes)

		attributeHashesList = append(attributeHashesList, attributeHashArray)
	}

	return attributeHashesList, nil
}


func AddIdentityProfileHash(identityHash [16]byte, profileHash [28]byte)error{

	identityType, err := identity.GetIdentityTypeFromIdentityHash(identityHash)
	if (err != nil){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		return errors.New("AddIdentityProfileHash called with invalid identity hash: " + identityHashHex)
	}

	hashIsValid, err := readProfiles.VerifyProfileHash(profileHash, true, identityType, false, false)
	if (err != nil) { return err }
	if (hashIsValid == false){

		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])

		return errors.New("AddIdentityProfileHash called with invalid profile hash: " + profileHashHex + ". Identity hash: " + identityHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	if (identityType == "Mate"){

		editingMateIdentityProfilesListMutex.Lock()
		defer editingMateIdentityProfilesListMutex.Unlock()
		
	} else if (identityType == "Host"){

		editingHostIdentityProfilesListMutex.Lock()
		defer editingHostIdentityProfilesListMutex.Unlock()

	} else if (identityType == "Moderator"){

		editingModeratorIdentityProfilesListMutex.Lock()
		defer editingModeratorIdentityProfilesListMutex.Unlock()
	} else {
		return errors.New("GetIdentityTypeFromIdentityHash returning invalid identityType: " + identityType)
	}

	getEntryKeyPrefix := func()byte{

		if (identityType == "Mate"){
			return mateIdentityProfilesListPrefix
		}
		if (identityType == "Host"){
			return hostIdentityProfilesListPrefix
		}
		// identityType == "Moderator"
		return moderatorIdentityProfilesListPrefix
	}

	entryKeyPrefix := getEntryKeyPrefix()

	entryKey := []byte{entryKeyPrefix}
	entryKey = append(entryKey, identityHash[:]...)

	err = addItemToEntryList(entryKey, 28, profileHash[:])
	if (err != nil) { return err }
	
	return nil
}

func GetIdentityProfileHashesList(identityHash [16]byte)(bool, [][28]byte, error){

	identityType, err := identity.GetIdentityTypeFromIdentityHash(identityHash)
	if (err != nil){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		return false, nil, errors.New("GetIdentityProfileHashesList called with invalid identity hash: " + identityHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	getEntryKeyPrefix := func()(byte, error){

		if (identityType == "Mate"){
			return mateIdentityProfilesListPrefix, nil

		} else if (identityType == "Host"){
			return hostIdentityProfilesListPrefix, nil

		} else if (identityType == "Moderator"){
			return moderatorIdentityProfilesListPrefix, nil
		}

		return 0, errors.New("GetIdentityTypeFromIdentityHash returning invalid identityType: " + identityType)
	}

	entryKeyPrefix, err := getEntryKeyPrefix()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{entryKeyPrefix}
	entryKey = append(entryKey, identityHash[:]...)

	exists, profileHashesBytesList, err := getEntryList(entryKey, 28)
	if (err != nil) { return false, nil, err }
	if (exists == false){
		return false, nil, nil
	}

	profileHashesList := make([][28]byte, 0, len(profileHashesBytesList))

	for _, profileHashBytes := range profileHashesBytesList{

		if (len(profileHashBytes) != 28){

			profileHashHex := encoding.EncodeBytesToHexString(profileHashBytes)
			return false, nil, errors.New("getEntryList returning invalid length item in list: " + profileHashHex)
		}

		profileHashArray := [28]byte(profileHashBytes)

		profileHashesList = append(profileHashesList, profileHashArray)
	}

	return true, profileHashesList, nil
}

func DeleteIdentityProfileHash(identityHash [16]byte, profileHash [28]byte)error{

	identityType, err := identity.GetIdentityTypeFromIdentityHash(identityHash)
	if (err != nil){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		return errors.New("DeleteIdentityProfileHash called with invalid identity hash: " + identityHashHex)
	}

	hashIsValid, err := readProfiles.VerifyProfileHash(profileHash, true, identityType, false, false)
	if (err != nil) { return err }
	if (hashIsValid == false){

		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])

		return errors.New("DeleteIdentityProfileHash called with invalid profile hash: " + profileHashHex + ". Identity hash: " + identityHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	if (identityType == "Mate"){

		editingMateIdentityProfilesListMutex.Lock()
		defer editingMateIdentityProfilesListMutex.Unlock()
		
	} else if (identityType == "Host"){

		editingHostIdentityProfilesListMutex.Lock()
		defer editingHostIdentityProfilesListMutex.Unlock()

	} else if (identityType == "Moderator"){

		editingModeratorIdentityProfilesListMutex.Lock()
		defer editingModeratorIdentityProfilesListMutex.Unlock()
	}

	getEntryKeyPrefix := func()(byte, error){

		if (identityType == "Mate"){
			return mateIdentityProfilesListPrefix, nil

		} else if (identityType == "Host"){
			return hostIdentityProfilesListPrefix, nil

		} else if (identityType == "Moderator"){
			return moderatorIdentityProfilesListPrefix, nil
		}

		return 0, errors.New("GetIdentityTypeFromIdentityHash returning invalid identityType: " + identityType)
	}

	entryKeyPrefix, err := getEntryKeyPrefix()
	if (err != nil) { return err }

	entryKey := []byte{entryKeyPrefix}
	entryKey = append(entryKey, identityHash[:]...)

	err = deleteItemFromEntryList(entryKey, 28, profileHash[:])
	if (err != nil) { return err }

	return nil
}


// This only returns identity hashes that have been added to the profile identity lists
// This means these identities have had at least 1 profile added to the database
func GetAllProfileIdentityHashes(identityType string)([][16]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	getEntryKeyPrefix := func()(byte, error){

		if (identityType == "Mate"){
			return mateIdentityProfilesListPrefix, nil

		} else if (identityType == "Host"){
			return hostIdentityProfilesListPrefix, nil

		} else if (identityType == "Moderator"){
			return moderatorIdentityProfilesListPrefix, nil
		}

		return 0, errors.New("GetAllProfileIdentityHashes called with invalid identityType: " + identityType)
	}

	entryKeyPrefix, err := getEntryKeyPrefix()
	if (err != nil) { return nil, err }

	identityHashesBytesList, err := getAllKeysInDatabaseWithPrefix(entryKeyPrefix, true)
	if (err != nil) { return nil, err }

	identityHashesList := make([][16]byte, 0, len(identityHashesBytesList))

	for _, identityHashBytes := range identityHashesBytesList{

		if (len(identityHashBytes) != 16){
			identityHashHex := encoding.EncodeBytesToHexString(identityHashBytes)
			return nil, errors.New("Database corrupt: Identity profiles list entry contains invalid identity hash in key: " + identityHashHex)
		}

		identityHashArray := [16]byte(identityHashBytes)

		identityHashesList = append(identityHashesList, identityHashArray)
	}

	return identityHashesList, nil
}


func AddChatInboxMessage(recipientInbox [10]byte, messageHash [26]byte)error{

	err := startDatabase()
	if (err != nil) { return err }

	editingInboxMessagesListMutex.Lock()
	defer editingInboxMessagesListMutex.Unlock()

	entryKey := []byte{inboxMessagesListPrefix}
	entryKey = append(entryKey, recipientInbox[:]...)

	err = addItemToEntryList(entryKey, 26, messageHash[:])
	if (err != nil) { return err }

	return nil
}

func GetChatInboxMessageHashesList(recipientInbox [10]byte)(bool, [][26]byte, error){

	err := startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{inboxMessagesListPrefix}
	entryKey = append(entryKey, recipientInbox[:]...)

	exists, messageHashesBytesList, err := getEntryList(entryKey, 26)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil 
	}

	messageHashesList := make([][26]byte, 0, len(messageHashesBytesList))

	for _, messageHashBytes := range messageHashesBytesList{

		if (len(messageHashBytes) != 26){
			return false, nil, errors.New("getEntryList returning invalid length message hash.")
		}

		messageHashArray := [26]byte(messageHashBytes)
		
		messageHashesList = append(messageHashesList, messageHashArray)
	}

	return true, messageHashesList, nil
}

func DeleteChatInboxMessage(recipientInbox [10]byte, messageHash [26]byte)error{

	err := startDatabase()
	if (err != nil) { return err }

	editingInboxMessagesListMutex.Lock()
	defer editingInboxMessagesListMutex.Unlock()

	entryKey := []byte{inboxMessagesListPrefix}
	entryKey = append(entryKey, recipientInbox[:]...)

	err = deleteItemFromEntryList(entryKey, 26, messageHash[:])
	if (err != nil) { return err }

	return nil
}


func GetAllMessageInboxes()([][10]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	inboxesBytesList, err := getAllKeysInDatabaseWithPrefix(inboxMessagesListPrefix, true)
	if (err != nil) { return nil, err }

	inboxesList := make([][10]byte, 0, len(inboxesBytesList))

	for _, inboxBytes := range inboxesBytesList{

		if (len(inboxBytes) != 10){
			return nil, errors.New("Database corrupt: Contains inbox entry with invalid inbox: Invalid length.")		
		}

		inboxArray := [10]byte(inboxBytes)

		inboxesList = append(inboxesList, inboxArray)
	}

	return inboxesList, nil
}


func AddReviewerReviewHash(reviewerIdentityHash [16]byte, reviewHash [29]byte)error{

	identityIsValid, err := identity.VerifyIdentityHash(reviewerIdentityHash, true, "Moderator")
	if (err != nil) { return err }
	if (identityIsValid == false){
		reviewerIdentityHashHex := encoding.EncodeBytesToHexString(reviewerIdentityHash[:])
		return errors.New("AddReviewerReviewHash called with invalid reviewerIdentityHash: " + reviewerIdentityHashHex)
	}

	reviewType, err := readReviews.GetReviewTypeFromReviewHash(reviewHash)
	if (err != nil){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("AddReviewerReviewHash called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	if (reviewType == "Identity"){

		editingReviewerIdentityReviewsListMutex.Lock()
		defer editingReviewerIdentityReviewsListMutex.Unlock()

	} else if (reviewType == "Profile"){

		editingReviewerProfileReviewsListMutex.Lock()
		defer editingReviewerProfileReviewsListMutex.Unlock()

	} else if (reviewType == "Attribute"){
	
		editingReviewerAttributeReviewsListMutex.Lock()
		defer editingReviewerAttributeReviewsListMutex.Unlock()

	} else if (reviewType == "Message"){

		editingReviewerMessageReviewsListMutex.Lock()
		defer editingReviewerMessageReviewsListMutex.Unlock()
	} else {

		return errors.New("GetReviewTypeFromReviewHash returning invalid reviewType: " + reviewType)
	}

	getEntryKeyPrefix := func()byte{

		if (reviewType == "Identity"){
			return reviewerIdentityReviewsListPrefix
		}
		if (reviewType == "Profile"){
			return reviewerProfileReviewsListPrefix
		}
		if (reviewType == "Attribute"){
			return reviewerAttributeReviewsListPrefix
		}
		// reviewType == "Message"
		return reviewerMessageReviewsListPrefix
	}

	entryKeyPrefix := getEntryKeyPrefix()

	entryKey := []byte{entryKeyPrefix}
	entryKey = append(entryKey, reviewerIdentityHash[:]...)

	err = addItemToEntryList(entryKey, 29, reviewHash[:])
	if (err != nil) { return err }
	
	return nil
}

// Returns all review hashes created by reviewer
func GetReviewerReviewHashesList(reviewerIdentityHash [16]byte, reviewType string)(bool, [][29]byte, error){

	identityIsValid, err := identity.VerifyIdentityHash(reviewerIdentityHash, true, "Moderator")
	if (err != nil) { return false, nil, err }
	if (identityIsValid == false){
		reviewerIdentityHashHex := encoding.EncodeBytesToHexString(reviewerIdentityHash[:])
		return false, nil, errors.New("GetReviewerReviewHashesList called with invalid reviewerIdentityHash: " + reviewerIdentityHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	getEntryKeyPrefix := func()(byte, error){

		if (reviewType == "Identity"){
			return reviewerIdentityReviewsListPrefix, nil
		}
		if (reviewType == "Profile"){
			return reviewerProfileReviewsListPrefix, nil
		}
		if (reviewType == "Attribute"){
			return reviewerAttributeReviewsListPrefix, nil
		}
		if (reviewType == "Message"){
			return reviewerMessageReviewsListPrefix, nil
		}
		return 0, errors.New("GetReviewerReviewHashesList called with invalid reviewType: " + reviewType)
	}

	entryKeyPrefix, err := getEntryKeyPrefix()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{entryKeyPrefix}
	entryKey = append(entryKey, reviewerIdentityHash[:]...)

	exists, reviewHashesBytesList, err := getEntryList(entryKey, 29)
	if (err != nil) { return false, nil, err }
	if (exists == false){
		return false, nil, nil
	}

	reviewHashesList := make([][29]byte, 0, len(reviewHashesBytesList))

	for _, reviewHashBytes := range reviewHashesBytesList{

		if (len(reviewHashBytes) != 29){
			return false, nil, errors.New("getEntryList returning invalid length review hash.")
		}

		reviewHashArray := [29]byte(reviewHashBytes)

		reviewHashesList = append(reviewHashesList, reviewHashArray)
	}

	return true, reviewHashesList, nil
}

func DeleteReviewerReviewHash(reviewerIdentityHash [16]byte, reviewHash [29]byte)error{

	identityIsValid, err := identity.VerifyIdentityHash(reviewerIdentityHash, true, "Moderator")
	if (err != nil) { return err }
	if (identityIsValid == false){
		reviewerIdentityHashHex := encoding.EncodeBytesToHexString(reviewerIdentityHash[:])
		return errors.New("DeleteReviewerReviewHash called with invalid reviewerIdentityHash: " + reviewerIdentityHashHex)
	}

	reviewType, err := readReviews.GetReviewTypeFromReviewHash(reviewHash)
	if (err != nil){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("DeleteReviewerReviewHash called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	if (reviewType == "Identity"){

		editingReviewerIdentityReviewsListMutex.Lock()
		defer editingReviewerIdentityReviewsListMutex.Unlock()

	} else if (reviewType == "Profile"){

		editingReviewerProfileReviewsListMutex.Lock()
		defer editingReviewerProfileReviewsListMutex.Unlock()

	} else if (reviewType == "Attribute"){
	
		editingReviewerAttributeReviewsListMutex.Lock()
		defer editingReviewerAttributeReviewsListMutex.Unlock()

	} else if (reviewType == "Message"){

		editingReviewerMessageReviewsListMutex.Lock()
		defer editingReviewerMessageReviewsListMutex.Unlock()
	} else {

		return errors.New("GetReviewTypeFromReviewHash returning invalid reviewType: " + reviewType)
	}

	getEntryKeyPrefix := func()byte{

		if (reviewType == "Identity"){
			return reviewerIdentityReviewsListPrefix
		}
		if (reviewType == "Profile"){
			return reviewerProfileReviewsListPrefix
		}
		if (reviewType == "Attribute"){
			return reviewerAttributeReviewsListPrefix
		}
		// reviewType == "Message"
		return reviewerMessageReviewsListPrefix
	}

	entryKeyPrefix := getEntryKeyPrefix()

	entryKey := []byte{entryKeyPrefix}
	entryKey = append(entryKey, reviewerIdentityHash[:]...)

	err = deleteItemFromEntryList(entryKey, 29, reviewHash[:])
	if (err != nil) { return err }

	return nil
}

func AddIdentityReviewToList(identityHash [16]byte, reviewHash [29]byte)error{

	identityIsValid, err := identity.VerifyIdentityHash(identityHash, false, "")
	if (err != nil) { return err }
	if (identityIsValid == false){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		return errors.New("AddIdentityReviewToList called with invalid identityHash: " + identityHashHex)
	}

	hashIsValid, err := readReviews.VerifyReviewHash(reviewHash, true, "Identity")
	if (err != nil) { return err }
	if (hashIsValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("AddIdentityReviewToList called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingIdentityReviewsListMutex.Lock()
	defer editingIdentityReviewsListMutex.Unlock()

	entryKey := []byte{identityReviewsListPrefix}
	entryKey = append(entryKey, identityHash[:]...)

	err = addItemToEntryList(entryKey, 29, reviewHash[:])
	if (err != nil) { return err }

	return nil
}

func GetIdentityReviewsList(identityHash [16]byte)(bool, [][29]byte, error){

	identityIsValid, err := identity.VerifyIdentityHash(identityHash, false, "")
	if (err != nil) { return false, nil, err }
	if (identityIsValid == false){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		return false, nil, errors.New("GetIdentityReviewsList called with invalid identityHash: " + identityHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{identityReviewsListPrefix}
	entryKey = append(entryKey, identityHash[:]...)

	exists, reviewHashesBytesList, err := getEntryList(entryKey, 29)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil 
	}

	reviewHashesList := make([][29]byte, 0, len(reviewHashesBytesList))

	for _, reviewHashBytes := range reviewHashesBytesList{

		if (len(reviewHashBytes) != 29){
			return false, nil, errors.New("getEntryList returning invalid length review hash.")
		}

		reviewHashArray := [29]byte(reviewHashBytes)

		reviewHashesList = append(reviewHashesList, reviewHashArray)
	}

	return true, reviewHashesList, nil
}

func DeleteIdentityReviewFromList(identityHash [16]byte, reviewHash [29]byte)error{

	identityIsValid, err := identity.VerifyIdentityHash(identityHash, false, "")
	if (err != nil) { return err }
	if (identityIsValid == false){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		return errors.New("DeleteIdentityReviewFromList called with invalid identityHash: " + identityHashHex)
	}

	hashIsValid, err := readReviews.VerifyReviewHash(reviewHash, true, "Identity")
	if (err != nil) { return err }
	if (hashIsValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("DeleteIdentityReviewFromList called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingIdentityReviewsListMutex.Lock()
	defer editingIdentityReviewsListMutex.Unlock()

	entryKey := []byte{identityReviewsListPrefix}
	entryKey = append(entryKey, identityHash[:]...)

	err = deleteItemFromEntryList(entryKey, 29, reviewHash[:])
	if (err != nil) { return err }

	return nil
}

// Returns all identity hashes that have been reviewed, both profiles and their identity
func GetAllReviewedIdentityHashes()([][16]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	identityHashesBytesList, err := getAllKeysInDatabaseWithPrefix(identityReviewsListPrefix, true)
	if (err != nil) { return nil, err }

	identityHashesList := make([][16]byte, 0, len(identityHashesBytesList))

	for _, identityHashBytes := range identityHashesBytesList{

		if (len(identityHashBytes) != 16){
			return nil, errors.New("Database corrupt: Identtity reviews list entry contains invalid identity hash.")
		}

		identityHashArray := [16]byte(identityHashBytes)

		identityHashesList = append(identityHashesList, identityHashArray)
	}

	return identityHashesList, nil
}


func AddIdentityReportToList(identityHash [16]byte, reportHash [30]byte)error{

	identityIsValid, err := identity.VerifyIdentityHash(identityHash, false, "")
	if (err != nil) { return err }
	if (identityIsValid == false){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		return errors.New("AddIdentityReportToList called with invalid identityHash: " + identityHashHex)
	}

	hashIsValid, err := readReports.VerifyReportHash(reportHash, true, "Identity")
	if (err != nil) { return err }
	if (hashIsValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return errors.New("AddIdentityReportToList called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingIdentityReportsListMutex.Lock()
	defer editingIdentityReportsListMutex.Unlock()

	entryKey := []byte{identityReportsListPrefix}
	entryKey = append(entryKey, identityHash[:]...)

	err = addItemToEntryList(entryKey, 30, reportHash[:])
	if (err != nil) { return err }

	return nil
}

func GetIdentityReportsList(identityHash [16]byte)(bool, [][30]byte, error){

	identityIsValid, err := identity.VerifyIdentityHash(identityHash, false, "")
	if (err != nil) { return false, nil, err }
	if (identityIsValid == false){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		return false, nil, errors.New("GetIdentityReportsList called with invalid identityHash: " + identityHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{identityReportsListPrefix}
	entryKey = append(entryKey, identityHash[:]...)

	exists, reportHashesBytesList, err := getEntryList(entryKey, 30)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil 
	}

	reportHashesList := make([][30]byte, 0, len(reportHashesBytesList))

	for _, reportHashBytes := range reportHashesBytesList{

		if (len(reportHashBytes) != 30){
			return false, nil, errors.New("getEntryList returning invalid length reportHash.")
		}

		reportHashArray := [30]byte(reportHashBytes)

		reportHashesList = append(reportHashesList, reportHashArray)
	}

	return true, reportHashesList, nil
}

func DeleteIdentityReportFromList(identityHash [16]byte, reportHash [30]byte)error{

	identityIsValid, err := identity.VerifyIdentityHash(identityHash, false, "")
	if (err != nil) { return err }
	if (identityIsValid == false){
		identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
		return errors.New("DeleteIdentityReportFromList called with invalid identityHash: " + identityHashHex)
	}

	hashIsValid, err := readReports.VerifyReportHash(reportHash, true, "Identity")
	if (err != nil) { return err }
	if (hashIsValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return errors.New("DeleteIdentityReportFromList called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingIdentityReportsListMutex.Lock()
	defer editingIdentityReportsListMutex.Unlock()

	entryKey := []byte{identityReportsListPrefix}
	entryKey = append(entryKey, identityHash[:]...)

	err = deleteItemFromEntryList(entryKey, 30, reportHash[:])
	if (err != nil) { return err }

	return nil
}

func GetAllReportedIdentityHashes()([][16]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	identityHashesBytesList, err := getAllKeysInDatabaseWithPrefix(identityReportsListPrefix, true)
	if (err != nil) { return nil, err }

	identityHashesList := make([][16]byte, 0, len(identityHashesBytesList))

	for _, identityHashBytes := range identityHashesBytesList{

		if (len(identityHashBytes) != 16){
			return nil, errors.New("Database corrupt: identity reports list entry key contains invalid length identity hash.")
		}

		identityHashArray := [16]byte(identityHashBytes)

		identityHashesList = append(identityHashesList, identityHashArray)
	}

	return identityHashesList, nil
}

func AddProfileReviewToList(profileHash [28]byte, reviewHash [29]byte)error{

	profileHashIsValid, err := readProfiles.VerifyProfileHash(profileHash, false, "", false, false)
	if (err != nil) { return err }
	if (profileHashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return errors.New("AddProfileReviewToList called with invalid profileHash: " + profileHashHex)
	}

	reviewHashIsValid, err := readReviews.VerifyReviewHash(reviewHash, true, "Profile")
	if (err != nil) { return err }
	if (reviewHashIsValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("AddProfileReviewToList called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingProfileReviewsListMutex.Lock()
	defer editingProfileReviewsListMutex.Unlock()

	entryKey := []byte{profileReviewsListPrefix}
	entryKey = append(entryKey, profileHash[:]...)

	err = addItemToEntryList(entryKey, 29, reviewHash[:])
	if (err != nil) { return err }

	return nil
}

func GetProfileReviewsList(profileHash [28]byte)(bool, [][29]byte, error){

	profileHashIsValid, err := readProfiles.VerifyProfileHash(profileHash, false, "", false, false)
	if (err != nil) { return false, nil, err }
	if (profileHashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return false, nil, errors.New("GetProfileReviewsList called with invalid profileHash: " + profileHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{profileReviewsListPrefix}
	entryKey = append(entryKey, profileHash[:]...)

	exists, reviewHashesBytesList, err := getEntryList(entryKey, 29)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil
	}

	reviewHashesList := make([][29]byte, 0, len(reviewHashesBytesList))

	for _, reviewHashBytes := range reviewHashesBytesList{

		if (len(reviewHashBytes) != 29){
			return false, nil, errors.New("getEntryList returning invalid review hash.")
		}

		reviewHashArray := [29]byte(reviewHashBytes)

		reviewHashesList = append(reviewHashesList, reviewHashArray)
	}

	return true, reviewHashesList, nil
}

func DeleteProfileReviewFromList(profileHash [28]byte, reviewHash [29]byte)error{

	profileHashIsValid, err := readProfiles.VerifyProfileHash(profileHash, false, "", false, false)
	if (err != nil) { return err }
	if (profileHashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return errors.New("DeleteProfileReviewFromList called with invalid profileHash: " + profileHashHex)
	}

	reviewHashIsValid, err := readReviews.VerifyReviewHash(reviewHash, true, "Profile")
	if (err != nil) { return err }
	if (reviewHashIsValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("DeleteProfileReviewFromList called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingProfileReviewsListMutex.Lock()
	defer editingProfileReviewsListMutex.Unlock()

	entryKey := []byte{profileReviewsListPrefix}
	entryKey = append(entryKey, profileHash[:]...)

	err = deleteItemFromEntryList(entryKey, 29, reviewHash[:])
	if (err != nil) { return err }

	return nil
}

// Returns all Profile hashes that have been reviewed
func GetAllReviewedProfileHashes()([][28]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	profileHashesBytesList, err := getAllKeysInDatabaseWithPrefix(profileReviewsListPrefix, true)
	if (err != nil) { return nil, err }

	profileHashesList := make([][28]byte, 0, len(profileHashesBytesList))

	for _, profileHashBytes := range profileHashesBytesList{

		if (len(profileHashBytes) != 28){
			return nil, errors.New("Database corrupt: Profile reviews list entry key contains invalid profile hash.")
		}

		profileHashArray := [28]byte(profileHashBytes)

		profileHashesList = append(profileHashesList, profileHashArray)
	}

	return profileHashesList, nil
}


func AddProfileReportToList(profileHash [28]byte, reportHash [30]byte)error{

	profileHashIsValid, err := readProfiles.VerifyProfileHash(profileHash, false, "", false, false)
	if (err != nil) { return err }
	if (profileHashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return errors.New("AddProfileReportToList called with invalid profileHash: " + profileHashHex)
	}

	reportHashIsValid, err := readReports.VerifyReportHash(reportHash, true, "Profile")
	if (err != nil) { return err }
	if (reportHashIsValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return errors.New("AddProfileReportToList called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingProfileReportsListMutex.Lock()
	defer editingProfileReportsListMutex.Unlock()

	entryKey := []byte{profileReportsListPrefix}
	entryKey = append(entryKey, profileHash[:]...)

	err = addItemToEntryList(entryKey, 30, reportHash[:])
	if (err != nil) { return err }

	return nil
}

func GetProfileReportsList(profileHash [28]byte)(bool, [][30]byte, error){

	profileHashIsValid, err := readProfiles.VerifyProfileHash(profileHash, false, "", false, false)
	if (err != nil) { return false, nil, err }
	if (profileHashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return false, nil, errors.New("GetProfileReportsList called with invalid profileHash: " + profileHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{profileReportsListPrefix}
	entryKey = append(entryKey, profileHash[:]...)

	exists, reportHashesBytesList, err := getEntryList(entryKey, 30)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil
	}

	reportHashesList := make([][30]byte, 0, len(reportHashesBytesList))

	for _, reportHashBytes := range reportHashesBytesList{

		if (len(reportHashBytes) != 30){
			return false, nil, errors.New("getEntryList returning invalid length reportHash.")
		}

		reportHashArray := [30]byte(reportHashBytes)

		reportHashesList = append(reportHashesList, reportHashArray)
	}

	return true, reportHashesList, nil
}


func DeleteProfileReportFromList(profileHash [28]byte, reportHash [30]byte)error{

	profileHashIsValid, err := readProfiles.VerifyProfileHash(profileHash, false, "", false, false)
	if (err != nil) { return err }
	if (profileHashIsValid == false){
		profileHashHex := encoding.EncodeBytesToHexString(profileHash[:])
		return errors.New("DeleteProfileReportFromList called with invalid profileHash: " + profileHashHex)
	}

	reportHashIsValid, err := readReports.VerifyReportHash(reportHash, true, "Profile")
	if (err != nil) { return err }
	if (reportHashIsValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return errors.New("DeleteProfileReportFromList called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingProfileReportsListMutex.Lock()
	defer editingProfileReportsListMutex.Unlock()

	entryKey := []byte{profileReportsListPrefix}
	entryKey = append(entryKey, profileHash[:]...)

	err = deleteItemFromEntryList(entryKey, 30, reportHash[:])
	if (err != nil) { return err }

	return nil
}

// Returns all Profile hashes that have been reported
func GetAllReportedProfileHashes()([][28]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	profileHashesBytesList, err := getAllKeysInDatabaseWithPrefix(profileReportsListPrefix, true)
	if (err != nil) { return nil, err }

	profileHashesList := make([][28]byte, 0, len(profileHashesBytesList))

	for _, profileHashBytes := range profileHashesBytesList{

		if (len(profileHashBytes) != 28){
			return nil, errors.New("Database corrupt: Profile reports list entry contains invalid profileHash.")
		}

		profileHashArray := [28]byte(profileHashBytes)

		profileHashesList = append(profileHashesList, profileHashArray)
	}

	return profileHashesList, nil
}


func AddProfileAttributeReviewToList(attributeHash [27]byte, reviewHash [29]byte)error{

	attributeHashIsValid, err := readProfiles.VerifyAttributeHash(attributeHash, false, "", false, false)
	if (err != nil) { return err }
	if (attributeHashIsValid == false){
		attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
		return errors.New("AddProfileAttributeReviewToList called with invalid attributeHash: " + attributeHashHex)
	}
	reviewHashIsValid, err := readReviews.VerifyReviewHash(reviewHash, true, "Attribute")
	if (err != nil) { return err }
	if (reviewHashIsValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("AddProfileAttributeReviewToList called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingAttributeReviewsListMutex.Lock()
	defer editingAttributeReviewsListMutex.Unlock()

	entryKey := []byte{attributeReviewsListPrefix}
	entryKey = append(entryKey, attributeHash[:]...)

	err = addItemToEntryList(entryKey, 29, reviewHash[:])
	if (err != nil) { return err }

	return nil
}

func GetProfileAttributeReviewsList(attributeHash [27]byte)(bool, [][29]byte, error){

	attributeHashIsValid, err := readProfiles.VerifyAttributeHash(attributeHash, false, "", false, false)
	if (err != nil) { return false, nil, err }
	if (attributeHashIsValid == false){
		attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
		return false, nil, errors.New("GetProfileAttributeReviewsList called with invalid attributeHash: " + attributeHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{attributeReviewsListPrefix}
	entryKey = append(entryKey, attributeHash[:]...)

	exists, reviewHashesBytesList, err := getEntryList(entryKey, 29)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil
	}

	reviewHashesList := make([][29]byte, 0, len(reviewHashesBytesList))

	for _, reviewHashBytes := range reviewHashesBytesList{

		if (len(reviewHashBytes) != 29){
			return false, nil, errors.New("getEntryList returning invalid length review hash.")
		}

		reviewHashArray := [29]byte(reviewHashBytes)

		reviewHashesList = append(reviewHashesList, reviewHashArray)
	}

	return true, reviewHashesList, nil
}

func DeleteProfileAttributeReviewFromList(attributeHash [27]byte, reviewHash [29]byte)error{

	attributeHashIsValid, err := readProfiles.VerifyAttributeHash(attributeHash, false, "", false, false)
	if (err != nil) { return err }
	if (attributeHashIsValid == false){
		attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
		return errors.New("DeleteProfileAttributeReviewFromList called with invalid attributeHash: " + attributeHashHex)
	}

	reviewHashIsValid, err := readReviews.VerifyReviewHash(reviewHash, true, "Attribute")
	if (err != nil) { return err }
	if (reviewHashIsValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("DeleteProfileAttributeReviewFromList called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingAttributeReviewsListMutex.Lock()
	defer editingAttributeReviewsListMutex.Unlock()

	entryKey := []byte{attributeReviewsListPrefix}
	entryKey = append(entryKey, attributeHash[:]...)

	err = deleteItemFromEntryList(entryKey, 29, reviewHash[:])
	if (err != nil) { return err }

	return nil
}

// Returns all attribute hashes that have been reviewed
func GetAllReviewedProfileAttributeHashes()([][27]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	attributeHashesBytesList, err := getAllKeysInDatabaseWithPrefix(attributeReviewsListPrefix, true)
	if (err != nil) { return nil, err }

	attributeHashesList := make([][27]byte, 0, len(attributeHashesBytesList))

	for _, attributeHashBytes := range attributeHashesBytesList{

		if (len(attributeHashBytes) != 27){
			return nil, errors.New("Database corrupt: Attribute reviews list entry key contains invalid length attribute hash.")
		}

		attributeHashArray := [27]byte(attributeHashBytes)

		attributeHashesList = append(attributeHashesList, attributeHashArray)
	}

	return attributeHashesList, nil
}

func AddProfileAttributeReportToList(attributeHash [27]byte, reportHash [30]byte)error{

	attributeHashIsValid, err := readProfiles.VerifyAttributeHash(attributeHash, false, "", false, false)
	if (err != nil) { return err }
	if (attributeHashIsValid == false){
		attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
		return errors.New("AddProfileAttributeReportToList called with invalid attributeHash: " + attributeHashHex)
	}
	reportHashIsValid, err := readReports.VerifyReportHash(reportHash, true, "Attribute")
	if (err != nil) { return err }
	if (reportHashIsValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return errors.New("AddProfileAttributeReportToList called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingAttributeReportsListMutex.Lock()
	defer editingAttributeReportsListMutex.Unlock()

	entryKey := []byte{attributeReportsListPrefix}
	entryKey = append(entryKey, attributeHash[:]...)

	err = addItemToEntryList(entryKey, 30, reportHash[:])
	if (err != nil) { return err }

	return nil
}


func GetProfileAttributeReportsList(attributeHash [27]byte)(bool, [][30]byte, error){

	attributeHashIsValid, err := readProfiles.VerifyAttributeHash(attributeHash, false, "", false, false)
	if (err != nil) { return false, nil, err }
	if (attributeHashIsValid == false){
		attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
		return false, nil, errors.New("GetProfileAttributeReportsList called with invalid attributeHash: " + attributeHashHex)
	}

	err = startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{attributeReportsListPrefix}
	entryKey = append(entryKey, attributeHash[:]...)

	exists, reportHashesBytesList, err := getEntryList(entryKey, 30)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil
	}

	reportHashesList := make([][30]byte, 0, len(reportHashesBytesList))

	for _, reportHashBytes := range reportHashesBytesList{

		if (len(reportHashBytes) != 30){
			return false, nil, errors.New("getEntryList returning invalid length report hash.")
		}

		reportHashArray := [30]byte(reportHashBytes)

		reportHashesList = append(reportHashesList, reportHashArray)
	}

	return true, reportHashesList, nil
}

func DeleteProfileAttributeReportFromList(attributeHash [27]byte, reportHash [30]byte)error{

	attributeHashIsValid, err := readProfiles.VerifyAttributeHash(attributeHash, false, "", false, false)
	if (err != nil){ return err }
	if (attributeHashIsValid == false){
		attributeHashHex := encoding.EncodeBytesToHexString(attributeHash[:])
		return errors.New("DeleteProfileAttributeReportFromList called with invalid attributeHash: " + attributeHashHex)
	}
	reportHashIsValid, err := readReports.VerifyReportHash(reportHash, true, "Attribute")
	if (err != nil){ return err }
	if (reportHashIsValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return errors.New("DeleteProfileAttributeReportFromList called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingAttributeReportsListMutex.Lock()
	defer editingAttributeReportsListMutex.Unlock()

	entryKey := []byte{attributeReportsListPrefix}
	entryKey = append(entryKey, attributeHash[:]...)

	err = deleteItemFromEntryList(entryKey, 30, reportHash[:])
	if (err != nil) { return err }

	return nil
}


// Returns all profile attribute hashes that have been reported
func GetAllReportedProfileAttributeHashes()([][27]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	attributeHashesBytesList, err := getAllKeysInDatabaseWithPrefix(attributeReportsListPrefix, true)
	if (err != nil) { return nil, err }

	attributeHashesList := make([][27]byte, 0, len(attributeHashesBytesList))

	for _, attributeHashBytes := range attributeHashesBytesList{

		if (len(attributeHashBytes) != 27){
			return nil, errors.New("Database corrupt: Attribute reports list entry key contains invalid attribute hash")
		}

		attributeHashArray := [27]byte(attributeHashBytes)

		attributeHashesList = append(attributeHashesList, attributeHashArray)
	}

	return attributeHashesList, nil
}


func AddMessageReviewToList(messageHash [26]byte, reviewHash [29]byte)error{

	reviewHashIsValid, err := readReviews.VerifyReviewHash(reviewHash, true, "Message")
	if (err != nil) { return err }
	if (reviewHashIsValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("AddMessageReviewToList called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingMessageReviewsListMutex.Lock()
	defer editingMessageReviewsListMutex.Unlock()

	entryKey := []byte{messageReviewsListPrefix}
	entryKey = append(entryKey, messageHash[:]...)

	err = addItemToEntryList(entryKey, 29, reviewHash[:])
	if (err != nil) { return err }

	return nil
}

func GetMessageReviewsList(messageHash [26]byte)(bool, [][29]byte, error){

	err := startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{messageReviewsListPrefix}
	entryKey = append(entryKey, messageHash[:]...)

	exists, reviewHashesBytesList, err := getEntryList(entryKey, 29)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil 
	}

	reviewHashesList := make([][29]byte, 0, len(reviewHashesBytesList))

	for _, reviewHashBytes := range reviewHashesBytesList{

		if (len(reviewHashBytes) != 29){
			return false, nil, errors.New("getEntryList returning invalid length reviewHash.")
		}

		reviewHashArray := [29]byte(reviewHashBytes)

		reviewHashesList = append(reviewHashesList, reviewHashArray)
	}

	return true, reviewHashesList, nil
}


func DeleteMessageReviewFromList(messageHash [26]byte, reviewHash [29]byte)error{

	reviewHashIsValid, err := readReviews.VerifyReviewHash(reviewHash, true, "Message")
	if (err != nil){ return err }
	if (reviewHashIsValid == false){
		reviewHashHex := encoding.EncodeBytesToHexString(reviewHash[:])
		return errors.New("DeleteMessageReviewFromList called with invalid reviewHash: " + reviewHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingMessageReviewsListMutex.Lock()
	defer editingMessageReviewsListMutex.Unlock()

	entryKey := []byte{messageReviewsListPrefix}
	entryKey = append(entryKey, messageHash[:]...)

	err = deleteItemFromEntryList(entryKey, 29, reviewHash[:])
	if (err != nil) { return err }

	return nil
}

// Returns all message hashes that have been reviewed
func GetAllReviewedMessageHashes()([][26]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	messageHashesBytesList, err := getAllKeysInDatabaseWithPrefix(messageReviewsListPrefix, true)
	if (err != nil) { return nil, err }

	messageHashesList := make([][26]byte, 0, len(messageHashesBytesList))

	for _, messageHashBytes := range messageHashesBytesList{

		if (len(messageHashBytes) != 26){
			return nil, errors.New("Database corrupt: Contains invalid message reviews list entry key message hash.")
		}

		messageHashArray := [26]byte(messageHashBytes)

		messageHashesList = append(messageHashesList, messageHashArray)
	}

	return messageHashesList, nil
}


func AddMessageReportToList(messageHash [26]byte, reportHash [30]byte)error{

	reportHashIsValid, err := readReports.VerifyReportHash(reportHash, true, "Message")
	if (err != nil) { return err }
	if (reportHashIsValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return errors.New("AddMessageReportToList called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingMessageReportsListMutex.Lock()
	defer editingMessageReportsListMutex.Unlock()

	entryKey := []byte{messageReportsListPrefix}
	entryKey = append(entryKey, messageHash[:]...)

	err = addItemToEntryList(entryKey, 30, reportHash[:])
	if (err != nil) { return err }

	return nil
}

func GetMessageReportsList(messageHash [26]byte)(bool, [][30]byte, error){

	err := startDatabase()
	if (err != nil) { return false, nil, err }

	entryKey := []byte{messageReportsListPrefix}
	entryKey = append(entryKey, messageHash[:]...)

	exists, reportHashesBytesList, err := getEntryList(entryKey, 30)
	if (err != nil) { return false, nil, err }
	if (exists == false) {
		return false, nil, nil 
	}

	reportHashesList := make([][30]byte, 0, len(reportHashesBytesList))

	for _, reportHashBytes := range reportHashesBytesList{

		if (len(reportHashBytes) != 30){
			return false, nil, errors.New("getEntryList returning invalid length report hash.")
		}
		
		reportHashArray := [30]byte(reportHashBytes)

		reportHashesList = append(reportHashesList, reportHashArray)
	}

	return true, reportHashesList, nil
}

func DeleteMessageReportFromList(messageHash [26]byte, reportHash [30]byte)error{

	reportHashIsValid, err := readReports.VerifyReportHash(reportHash, true, "Message")
	if (err != nil) { return err }
	if (reportHashIsValid == false){
		reportHashHex := encoding.EncodeBytesToHexString(reportHash[:])
		return errors.New("DeleteMessageReportFromList called with invalid reportHash: " + reportHashHex)
	}

	err = startDatabase()
	if (err != nil) { return err }

	editingMessageReportsListMutex.Lock()
	defer editingMessageReportsListMutex.Unlock()

	entryKey := []byte{messageReportsListPrefix}
	entryKey = append(entryKey, messageHash[:]...)

	err = deleteItemFromEntryList(entryKey, 30, reportHash[:])
	if (err != nil) { return err }

	return nil
}

// Returns all message hashes that have been reported
func GetAllReportedMessageHashes()([][26]byte, error){

	err := startDatabase()
	if (err != nil) { return nil, err }

	messageHashesBytesList, err := getAllKeysInDatabaseWithPrefix(messageReportsListPrefix, true)
	if (err != nil) { return nil, err }

	messageHashesList := make([][26]byte, 0, len(messageHashesBytesList))

	for _, messageHashBytes := range messageHashesBytesList{

		if (len(messageHashBytes) != 26){
			return nil, errors.New("Database corrupt: Contains message reports list entry with invalid message hash.")
		}

		messageHashArray := [26]byte(messageHashBytes)

		messageHashesList = append(messageHashesList, messageHashArray)
	}
	
	return messageHashesList, nil
}

//Ouputs:
//	-bool: Entry exists
//	-[][]byte: Items list
//	-error
func getEntryList(entryKey []byte, lengthOfEachItem int)(bool, [][]byte, error){

	exists, entryValue, err := getDatabaseValue(entryKey)
	if (err != nil) { return false, nil, err }
	if (exists == false){
		return false, nil, nil
	}

	itemsList, err := splitByteSliceIntoEqualParts(entryValue, lengthOfEachItem)
	if (err != nil){
		entryKeyHex := encoding.EncodeBytesToHexString(entryKey)
		return false, nil, errors.New("Database entry list is invalid for entry key: " + entryKeyHex)
	}

	return true, itemsList, nil
}

// This adds an item to an entry whose value is a list of equal-length byte slices
// Duplicates items will be avoided
func addItemToEntryList(entryKey []byte, lengthOfEachItem int, itemToAdd []byte)error{

	if (len(itemToAdd) != lengthOfEachItem){
		lengthString := helpers.ConvertIntToString(lengthOfEachItem)
		return errors.New("addItemToEntryList called with invalid length itemToAdd: Not " + lengthString)
	}

	//Outputs:
	//	-bool: Update needed
	//	-bool: New value exists (false = delete entry value)
	//	-[]byte: New value
	//	-error
	updateValueFunction := func(existingValueExists bool, existingValue []byte)(bool, bool, []byte, error){

		if (existingValueExists == false){
			return true, true, itemToAdd, nil
		}

		//TODO: Do we need to copy existingValue? because we use it as an input to slices.Concat()

		currentList, err := splitByteSliceIntoEqualParts(existingValue, lengthOfEachItem)
		if (err != nil) { return false, false, nil, err }

		for _, element := range currentList{

			areEqual := bytes.Equal(itemToAdd, element)
			if (areEqual == true){
				// The item to add already exists.
				// No update is needed.
				return false, false, nil, nil
			}
		}

		newValue := slices.Concat(existingValue, itemToAdd)

		return true, true, newValue, nil
	}
	
	err := updateDatabaseEntry(entryKey, updateValueFunction)
	if (err != nil) { return err }

	return nil
}

// This deletes a value from an entry whose value is a list of equal-length byte slices
// All byte slices are concatenated
func deleteItemFromEntryList(entryKey []byte, lengthOfEachItem int, itemToDelete []byte)error{

	//Outputs:
	//	-bool: Update needed
	//	-bool: New value exists (false == delete entry)
	//	-[]byte: New value
	//	-error
	updateValueFunction := func(existingValueExists bool, existingValue []byte)(bool, bool, []byte, error){

		if (existingValueExists == false){
			// Nothing to delete
			return false, false, nil, nil
		}

		currentList, err := splitByteSliceIntoEqualParts(existingValue, lengthOfEachItem)
		if (err != nil) { return false, false, nil, err }

		newValue := make([]byte, 0)

		deletedAny := false

		for _, element := range currentList{

			areEqual := bytes.Equal(element, itemToDelete)
			if (areEqual == true){
				// We are deleting this element
				deletedAny = true
				continue
			}

			newValue = append(newValue, element...)
		}

		if (deletedAny == false){
			// Nothing to delete
			return false, false, nil, nil
		}

		if (len(newValue) == 0){
			// New value is empty
			return true, false, nil, nil
		}

		return true, true, newValue, nil
	}

	err := updateDatabaseEntry(entryKey, updateValueFunction)
	if (err != nil) { return err }

	return nil
}

func setDatabaseEntry(key []byte, value []byte)error{

	if (myDatabase == nil) {
		return errors.New("setDatabaseEntry called when database not started.")
	}

	//TODO: Do we need to copy value?
	// Copy is never needed for key because it will always be passed in as an array

	valueCopy := slices.Clone(value)

	updateValueFunction := func(txn *badger.Txn) error {
		err := txn.Set(key, valueCopy)
		return err
	}

	databaseMutex.RLock()
	defer databaseMutex.RUnlock()

	err := myDatabase.Update(updateValueFunction)
	if (err != nil) { return err }
	
	return nil
}

func getDatabaseValue(key []byte)(bool, []byte, error){

	if (myDatabase == nil) {
		return false, nil, errors.New("getDatabaseValue called when database is not started.")
	}

	valueFound := false
	var valueBytes []byte

	getEntryValueFunction := func(txn *badger.Txn)error{

		item, err := txn.Get(key)
		if (err != nil) {
			if (err == badger.ErrKeyNotFound){
				return nil
			}
			return err
		}

		valueFound = true
		valueBytes, err = item.ValueCopy(nil)
		if (err != nil) { return err }

		return nil
	}

	databaseMutex.RLock()
	defer databaseMutex.RUnlock()

	err := myDatabase.View(getEntryValueFunction)
	if (err != nil){ return false, nil, err }

	if (valueFound == false){
		return false, nil, nil
	}
	
	return true, valueBytes, nil
}


func deleteDatabaseEntry(key []byte)error{
	
	if (myDatabase == nil) {
		return errors.New("deleteDatabaseEntry called when database is not started.")
	}

	deleteFunction := func(txn *badger.Txn) error {
		err := txn.Delete(key)
		if (err != nil) { return err }

		return nil
	}

	databaseMutex.RLock()
	defer databaseMutex.RUnlock()

	err := myDatabase.Update(deleteFunction)
	if (err != nil) { return err }

	return nil
}

// This function will get the database entry and replace the value, conditional on what the existing value is
//
//Inputs:
//	-[]byte: The key we are editing
//	-func(currentValueExists bool, currentValue []byte)(bool, bool, []byte, error)
//		-Outputs:
//			-bool: Change to key value is needed
//			-bool: New value exists (false = delete key value)
//			-[]byte: New value for key entry
//			-error
// Outputs:
//	-error
func updateDatabaseEntry(key []byte, updateEntryValueFunction func(bool, []byte)(bool, bool, []byte, error))error{

	updateFunction := func(txn *badger.Txn)error{

		//Outputs:
		//	-bool: Existing value exists
		//	-string: Existing value
		//	-error
		getExistingValue := func()(bool, []byte, error){

			item, err := txn.Get(key)
			if (err != nil) {
				if (err == badger.ErrKeyNotFound){
					return false, nil, nil
				}
				return false, nil, err
			}

			existingValue, err := item.ValueCopy(nil)
			if (err != nil) { return false, nil, err }

			return true, existingValue, nil
		}

		existingValueExists, existingValue, err := getExistingValue()
		if (err != nil){ return err }

		needsUpdate, newValueExists, newValue, err := updateEntryValueFunction(existingValueExists, existingValue)
		if (err != nil){ return err }
		if (needsUpdate == false){
			return nil
		}
		if (newValueExists == false){
			err := txn.Delete(key)
			if (err != nil) { return err }

			return nil
		}

		err = txn.Set(key, newValue)
		if (err != nil) { return err }

		return nil
	}

	databaseMutex.RLock()
	defer databaseMutex.RUnlock()

	err := myDatabase.Update(updateFunction)
	if (err != nil){ return err }
		
	return nil
}


func getNumberOfKeysInDatabaseWithPrefix(prefixToCheck byte)(int64, error){

	if (myDatabase == nil) {
		return 0, errors.New("getNumberOfKeysInDatabaseWithPrefix called when database is not started.")
	}

	counter := int64(0)

	getNumberOfKeysFunction := func(txn *badger.Txn) error {

		options := badger.DefaultIteratorOptions
		options.PrefetchValues = false
		iterator := txn.NewIterator(options)
		
		for iterator.Rewind(); iterator.Valid(); iterator.Next() {

			item := iterator.Item()
			entryKey := item.KeyCopy(nil)

			if (len(entryKey) == 0){
				return errors.New("Database corrupt: Contains empty entry key.")
			}

			if (entryKey[0] == prefixToCheck){
				counter += 1
			}
		}

		iterator.Close()
		return nil
	}

	databaseMutex.RLock()
	defer databaseMutex.RUnlock()

	err := myDatabase.View(getNumberOfKeysFunction)
	if (err != nil) { return 0, err }

	return counter, nil
}


func getAllKeysInDatabaseWithPrefix(prefixToCheck byte, trimPrefix bool)([][]byte, error){

	if (myDatabase == nil) {
		return nil, errors.New("getAllKeysInDatabaseWithPrefix called when database is not started.")
	}

	keysList := make([][]byte, 0)

	getAllKeysFunction := func(txn *badger.Txn) error {
		options := badger.DefaultIteratorOptions
		options.PrefetchValues = false
		iterator := txn.NewIterator(options)

		for iterator.Rewind(); iterator.Valid(); iterator.Next() {
			item := iterator.Item()
		
			entryKey := item.KeyCopy(nil)

			if (len(entryKey) < 2){
				return errors.New("Database corrupt: Contains entry key less than 2 bytes in length.")
			}

			if (entryKey[0] == prefixToCheck){

				if (trimPrefix == false){
					keysList = append(keysList, entryKey)

				} else {

					keyWithoutPrefix := entryKey[1:]
					keysList = append(keysList, keyWithoutPrefix)
				}
			}
		}
		
		iterator.Close()
		return nil
	}

	databaseMutex.RLock()
	defer databaseMutex.RUnlock()
	
	err := myDatabase.View(getAllKeysFunction)
	if (err != nil) { return nil, err }

	return keysList, nil
}


func splitByteSliceIntoEqualParts(inputSlice []byte, lengthOfEachSubslice int)([][]byte, error){

	inputLength := len(inputSlice)

	if (inputLength % lengthOfEachSubslice != 0){
		// The input slice should be a list of uniform-length byte arrays appended together
		inputSliceHex := encoding.EncodeBytesToHexString(inputSlice)
		return nil, errors.New("splitByteSliceIntoEqualParts called with invalid inputSlice: " + inputSliceHex)
	}

	numberOfSubslices := inputLength/lengthOfEachSubslice

	subslicesList := make([][]byte, 0)

	for i := 0; i < numberOfSubslices; i++{

		index := i * lengthOfEachSubslice

		subsliceBytes := inputSlice[index:index+lengthOfEachSubslice]

		subslicesList = append(subslicesList, subsliceBytes)
	}

	return subslicesList, nil
}



