
// readMemos provides functions to read Seekia memos and to derive blockchain addresses to timestamp memos
// Memos are specially formatted messages that are signed with a Seekia identity key
// They can be shared anywhere, and verified using the Seekia application

package readMemos

//TODO: We can relax some restrictions on the memo format.
// We can add translations to some of the memo terms ("Memo", "Author", "Signature")
// This should be able to be done later, and memos created with with the original memo format will still be valid.

import "seekia/internal/cryptocurrency/ethereumAddress"
import "seekia/internal/cryptocurrency/cardanoAddress"

import "seekia/internal/cryptography/blake3"
import "seekia/internal/cryptography/edwardsKeys"
import "seekia/internal/encoding"
import "seekia/internal/identity"

import "strings"
import "errors"


//Outputs:
//	-bool: Memo is valid
//	-[32]byte: Memo hash (32 bytes long blake3 hash of memo string)
//	-[16]byte: Author identity hash
//	-string: Memo Unarmored Contents (The contents of the memo without the header, signature, author identity key, padding, and footer)
//	-error
func ReadMemo(inputMemo string)(bool, [32]byte, [16]byte, string, error){

	memoHashBytes, err := blake3.GetBlake3HashAsBytes(32, []byte(inputMemo))
	if (err != nil) {
		// Invalid memo: Is empty.
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	memoHash := [32]byte(memoHashBytes)

	memoLinesList := strings.Split(inputMemo, "\n")

	if (len(memoLinesList) < 18){
		//Invalid memo: Too short
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	topLine := memoLinesList[0]

	// The decorations on this line (and the footer) can be anything.
	// We just make sure "Seekia Memo" exists in the first line of the memo.
	containsSeekiaMemo := strings.Contains(topLine, "Seekia Memo")
	if (containsSeekiaMemo == false){
		// Invalid memo: Missing Seekia Memo header.
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	if (memoLinesList[1] != "|"){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	if (memoLinesList[2] != "|- Signature:"){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	signaturePart1, hasPrefix := strings.CutPrefix(memoLinesList[3], "|    ")
	if (hasPrefix == false){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}
	signaturePart2, hasPrefix := strings.CutPrefix(memoLinesList[4], "|    ")
	if (hasPrefix == false){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}
	signaturePart3, hasPrefix := strings.CutPrefix(memoLinesList[5], "|    ")
	if (hasPrefix == false){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	signatureString := signaturePart1 + signaturePart2 + signaturePart3

	signatureBytes, err := encoding.DecodeBase64StringToBytes(signatureString)
	if (err != nil){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	if (len(signatureBytes) != 64){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	signatureArray := [64]byte(signatureBytes)

	if (memoLinesList[6] != "|"){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	// We create memoContent, which is the content string that will be hashed and signed by the author
	// We trim the final line
	finalIndex := len(memoLinesList)-1
	memoContentList := memoLinesList[7:finalIndex]
	memoContent := strings.Join(memoContentList, "\n")
	memoContent += "\n"

	memoContentHash, err := blake3.Get32ByteBlake3Hash([]byte(memoContent))
	if (err != nil){ return false, [32]byte{}, [16]byte{}, "", err }

	if (memoLinesList[7] != "|- Identity Key:"){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	identityKeyPart1, hasPrefix := strings.CutPrefix(memoLinesList[8], "|    ")
	if (hasPrefix == false){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}
	identityKeyPart2, hasPrefix := strings.CutPrefix(memoLinesList[9], "|    ")
	if (hasPrefix == false){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	identityKeyHex := identityKeyPart1 + identityKeyPart2

	identityKeyBytes, err := encoding.DecodeHexStringToBytes(identityKeyHex)
	if (err != nil){
		// Memo is malformed
		// Invalid identity key: Not hex.
		return false, [32]byte{}, [16]byte{}, "", nil
	}
	if (len(identityKeyBytes) != 32){
		// Memo is malformed
		// Invalid identity key: Invalid length.
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	identityKeyArray := [32]byte(identityKeyBytes)

	if (memoLinesList[10] != "|"){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}
	if (memoLinesList[11] != "|- Author:"){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	identityHashString, hasPrefix := strings.CutPrefix(memoLinesList[12], "|    ")
	if (hasPrefix == false){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	identityHash, identityType, err := identity.ReadIdentityHashString(identityHashString)
	if (err != nil){
		// Memo is malformed
		// Invalid author identity hash
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	// Now we verify signature and identity hash

	expectedIdentityHash, err := identity.ConvertIdentityKeyToIdentityHash(identityKeyArray, identityType)
	if (err != nil){
		// Memo is malformed
		// Invalid identity key
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	if (identityHash != expectedIdentityHash){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	isValid := edwardsKeys.VerifySignature(identityKeyArray, signatureArray, memoContentHash)
	if (isValid == false){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	// Signature is valid.
	// We make sure rest of memo is well formed.

	if (memoLinesList[13] != "|"){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}
	if (memoLinesList[14] != "|- Memo:"){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}
	if (memoLinesList[15] != "|"){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	penultimateLine := memoLinesList[finalIndex-1]
	if (penultimateLine != "|"){
		// Memo is malformed
		// The second to last line must be a single pipe
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	finalLine := memoLinesList[finalIndex]

	containsEndOfMemo := strings.Contains(finalLine, "End Of Memo")
	if (containsEndOfMemo == false){
		// Memo is malformed
		return false, [32]byte{}, [16]byte{}, "", nil
	}

	// Now we check to make sure each line has the "|" prefix and is well formed
	// We use this to build the unarmored memo contents
	var unarmoredMemoContentsBuilder strings.Builder

	for index, memoLine := range memoLinesList{

		if (index < 16){
			continue
		}
		if (index >= finalIndex-1){
			// We have reached the penultimate line of the memo
			// We don't include this line or the memo footer in the unarmored memo contents
			break
		}

		if (memoLine != "|" && memoLine != "|    "){

			// "|" == The line is a newline without any text content.

			// "|    " == This memo was created before the memo format was changed to not include whitespace before empty lines

			// The line should not be empty
			// We will add the line contents to the unarmoredContent

			lineWithoutPrefix, hasPrefix := strings.CutPrefix(memoLine, "|    ")
			if (hasPrefix == false){
				// Memo is malformed
				// Each line must either be a single pipe, a pipe with 4 whitespace characters,
				//		or a pipe with 4 whitespace characters and content afterwards
				return false, [32]byte{}, [16]byte{}, "", nil
			}

			if (lineWithoutPrefix == ""){
				// Memo is malformed
				// Each line must contain some content
				return false, [32]byte{}, [16]byte{}, "", nil
			}

			unarmoredMemoContentsBuilder.WriteString(lineWithoutPrefix)
		}

		if (index < finalIndex-2){
			unarmoredMemoContentsBuilder.WriteString("\n")
		}
	}

	unarmoredMemoContents := unarmoredMemoContentsBuilder.String()

	return true, memoHash, identityHash, unarmoredMemoContents, nil
}

//Outputs:
//	-string: Blockchain address that can timestamp the memo
//	-error
func GetBlockchainAddressFromMemoHash(cryptocurrencyName string, memoHash [32]byte)(string, error){

	if (cryptocurrencyName != "Ethereum" && cryptocurrencyName != "Cardano"){
		return "", errors.New("GetBlockchainAddressFromMemoHash called with invalid cryptocurrencyName: " + cryptocurrencyName)
	}

	if (cryptocurrencyName == "Ethereum"){

		memoEthereumAddress, err := ethereumAddress.GetMemoEthereumAddressFromMemoHash(memoHash)
		if (err != nil) { return "", err }

		return memoEthereumAddress, nil
	}

	memoCardanoAddress, err := cardanoAddress.GetMemoCardanoAddressFromMemoHash(memoHash)
	if (err != nil) { return "", err }

	return memoCardanoAddress, nil
}



