package emojihash

import (
	"bufio"
	"bytes"
	"errors"
	"math/big"
	"strings"

	"github.com/status-im/status-go/protocol/identity"
	"github.com/status-im/status-go/static"
)

const (
	emojiAlphabetLen = 2757 // 20bytes of data described by 14 emojis requires at least 2757 length alphabet
	emojiHashLen     = 14
)

var emojisAlphabet []string

func GenerateFor(pubkey string) ([]string, error) {
	if len(emojisAlphabet) == 0 {
		alphabet, err := loadAlphabet()
		if err != nil {
			return nil, err
		}
		emojisAlphabet = *alphabet
	}

	compressedKey, err := identity.ToCompressedKey(pubkey)
	if err != nil {
		return nil, err
	}

	slices, err := identity.Slices(compressedKey)
	if err != nil {
		return nil, err
	}

	return toEmojiHash(new(big.Int).SetBytes(slices[1]), emojiHashLen, &emojisAlphabet)
}

func loadAlphabet() (*[]string, error) {
	data, err := static.Asset("emojis.txt")
	if err != nil {
		return nil, err
	}

	alphabet := make([]string, 0, emojiAlphabetLen)

	scanner := bufio.NewScanner(bytes.NewReader(data))
	for scanner.Scan() {
		alphabet = append(alphabet, strings.Replace(scanner.Text(), "\n", "", -1))
	}

	// current alphabet contains more emojis than needed, just in case some emojis needs to be removed
	// make sure only necessary part is loaded
	if len(alphabet) > emojiAlphabetLen {
		alphabet = alphabet[:emojiAlphabetLen]
	}

	return &alphabet, nil
}

func toEmojiHash(value *big.Int, hashLen int, alphabet *[]string) (hash []string, err error) {
	valueBitLen := value.BitLen()
	alphabetLen := new(big.Int).SetInt64(int64(len(*alphabet)))

	indexes := identity.ToBigBase(value, alphabetLen.Uint64())
	if hashLen == 0 {
		hashLen = len(indexes)
	} else if hashLen > len(indexes) {
		prependLen := hashLen - len(indexes)
		for i := 0; i < prependLen; i++ {
			indexes = append([](uint64){0}, indexes...)
		}
	}

	// alphabetLen^hashLen
	possibleCombinations := new(big.Int).Exp(alphabetLen, new(big.Int).SetInt64(int64(hashLen)), nil)

	// 2^valueBitLen
	requiredCombinations := new(big.Int).Exp(new(big.Int).SetInt64(2), new(big.Int).SetInt64(int64(valueBitLen)), nil)

	if possibleCombinations.Cmp(requiredCombinations) == -1 {
		return nil, errors.New("alphabet or hash length is too short to encode given value")
	}

	for _, v := range indexes {
		hash = append(hash, (*alphabet)[v])
	}

	return hash, nil
}