package langext

import (
	"bytes"
	"errors"
	"math/big"
)

// shamelessly stolen from https://github.com/btcsuite/

type B58Encoding struct {
	bigRadix     [11]*big.Int
	bigRadix10   *big.Int
	alphabet     string
	alphabetIdx0 byte
	b58          [256]byte
}

var Base58DefaultEncoding = newBase58Encoding("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
var Base58FlickrEncoding = newBase58Encoding("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ")
var Base58RippleEncoding = newBase58Encoding("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz")
var Base58BitcoinEncoding = newBase58Encoding("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")

func newBase58Encoding(alphabet string) *B58Encoding {
	bigRadix10 := big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58)
	enc := &B58Encoding{
		alphabet:     alphabet,
		alphabetIdx0: '1',
		bigRadix: [...]*big.Int{
			big.NewInt(0),
			big.NewInt(58),
			big.NewInt(58 * 58),
			big.NewInt(58 * 58 * 58),
			big.NewInt(58 * 58 * 58 * 58),
			big.NewInt(58 * 58 * 58 * 58 * 58),
			big.NewInt(58 * 58 * 58 * 58 * 58 * 58),
			big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58),
			big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
			big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
			bigRadix10,
		},
		bigRadix10: bigRadix10,
	}

	b58 := make([]byte, 0, 256)

	for i := byte(0); i < 32; i++ {
		for j := byte(0); j < 8; j++ {

			b := i*8 + j

			idx := bytes.IndexByte([]byte(alphabet), b)
			if idx == -1 {
				b58 = append(b58, 255)
			} else {
				b58 = append(b58, byte(idx))
			}

		}
	}

	enc.b58 = *((*[256]byte)(b58))

	return enc
}

func (enc *B58Encoding) EncodeString(src string) (string, error) {
	v, err := enc.Encode([]byte(src))
	if err != nil {
		return "", err
	}
	return string(v), nil
}

func (enc *B58Encoding) Encode(src []byte) ([]byte, error) {
	x := new(big.Int)
	x.SetBytes(src)

	// maximum length of output is log58(2^(8*len(b))) == len(b) * 8 / log(58)
	maxlen := int(float64(len(src))*1.365658237309761) + 1
	answer := make([]byte, 0, maxlen)
	mod := new(big.Int)
	for x.Sign() > 0 {
		// Calculating with big.Int is slow for each iteration.
		//    x, mod = x / 58, x % 58
		//
		// Instead we can try to do as much calculations on int64.
		//    x, mod = x / 58^10, x % 58^10
		//
		// Which will give us mod, which is 10 digit base58 number.
		// We'll loop that 10 times to convert to the answer.

		x.DivMod(x, enc.bigRadix10, mod)
		if x.Sign() == 0 {
			// When x = 0, we need to ensure we don't add any extra zeros.
			m := mod.Int64()
			for m > 0 {
				answer = append(answer, enc.alphabet[m%58])
				m /= 58
			}
		} else {
			m := mod.Int64()
			for i := 0; i < 10; i++ {
				answer = append(answer, enc.alphabet[m%58])
				m /= 58
			}
		}
	}

	// leading zero bytes
	for _, i := range src {
		if i != 0 {
			break
		}
		answer = append(answer, enc.alphabetIdx0)
	}

	// reverse
	alen := len(answer)
	for i := 0; i < alen/2; i++ {
		answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i]
	}

	return answer, nil
}

func (enc *B58Encoding) DecodeString(src string) (string, error) {
	v, err := enc.Decode([]byte(src))
	if err != nil {
		return "", err
	}
	return string(v), nil
}

func (enc *B58Encoding) Decode(src []byte) ([]byte, error) {
	answer := big.NewInt(0)
	scratch := new(big.Int)

	for t := src; len(t) > 0; {
		n := len(t)
		if n > 10 {
			n = 10
		}

		total := uint64(0)
		for _, v := range t[:n] {
			if v > 255 {
				return []byte{}, errors.New("invalid char in input")
			}

			tmp := enc.b58[v]
			if tmp == 255 {
				return []byte{}, errors.New("invalid char in input")
			}
			total = total*58 + uint64(tmp)
		}

		answer.Mul(answer, enc.bigRadix[n])
		scratch.SetUint64(total)
		answer.Add(answer, scratch)

		t = t[n:]
	}

	tmpval := answer.Bytes()

	var numZeros int
	for numZeros = 0; numZeros < len(src); numZeros++ {
		if src[numZeros] != enc.alphabetIdx0 {
			break
		}
	}
	flen := numZeros + len(tmpval)
	val := make([]byte, flen)
	copy(val[numZeros:], tmpval)

	return val, nil
}
