package fpe

import (
	"math"
	"math/big"
	"math/bits"
)

type Radix struct {
	r int

	e int // the maximum number such that r^e is a uint64

	rBig *big.Int // radix as big

	reBig *big.Int // radix^e as big.Int, the same as rPowers[-1]

	rPowers []big.Int // [r, r^2, ..., r^e]
}

func NewRadix(r int) *Radix {
	radix := &Radix{}
	radix.Set(r)
	return radix
}

func (r *Radix) Set(v int) {
	if v < 2 || v >= math.MaxUint16 {
		panic("radix overflow")
	}
	r.r = v
	r.e = 64 / bits.Len32(uint32(v))
	r.rPowers = make([]big.Int, r.e)
	r.rPowers[0].SetUint64(uint64(v))
	for i, vi := 1, uint64(v); i < r.e; i++ {
		vi *= uint64(v)
		r.rPowers[i].SetUint64(vi)
	}
	r.rBig = &r.rPowers[0]
	r.reBig = &r.rPowers[r.e-1]
}

func (r *Radix) Radix() int {
	return r.r
}

func (r *Radix) Num(X []Char) *big.Int {
	if false {
		radix := big.NewInt(int64(r.r))
		n := new(big.Int)
		for _, d := range X {
			n.Mul(n, radix)
			n.Add(n, big.NewInt(int64(d)))
		}
		return n
	} else {
		radix := uint64(r.r)
		l := len(X) % r.e

		tail := uint64(0)
		for i := 0; i < l; i++ {
			tail *= uint64(radix)
			tail += uint64(X[i])
		}

		n := new(big.Int).SetUint64(tail)
		for i := l; i < len(X); i += r.e {
			d := uint64(0)
			for j := 0; j < r.e; j++ {
				d = d*uint64(radix) + uint64(X[i+j])
			}

			n.Mul(n, r.reBig)
			n.Add(n, new(big.Int).SetUint64(d))
		}
		return n
	}
}

// x should < radix^(lenX)
func (r *Radix) Str(X []Numeral, x *big.Int) {
	if false {
		n := len(X)
		radix := big.NewInt(int64(r.r))
		d := new(big.Int)
		xx := new(big.Int).Set(x) // make a copy
		for i := 0; i < n; i++ {
			xx, d = xx.DivMod(xx, radix, d)
			X[n-i-1] = Numeral(d.Int64())
		}
		// if xx is not zero, then the input x must too big
		if xx.Sign() != 0 {
			panic("x should less then radix^m")
		}
	} else {
		xx := new(big.Int).Set(x) // make a copy
		lx := len(X)
		radix := uint64(r.r)
		d := new(big.Int)

		// the tail part
		m := lx % r.e
		if m > 0 {
			xx, d = xx.DivMod(xx, &r.rPowers[m-1], d)
			n := d.Uint64()
			for i := lx - 1; i >= lx-m; i-- {
				X[i] = Numeral(n % radix)
				n /= radix
			}
		}

		for i := lx - m; i > 0; i -= r.e {
			xx, d = xx.DivMod(xx, r.reBig, d)
			n := d.Uint64()
			for j := i - 1; j >= i-r.e; j-- {
				X[j] = uint16(n % radix)
				n /= radix
			}
		}
		// if xx is not zero, then the input x must too big
		if xx.Sign() != 0 {
			panic("x should less then radix^m")
		}
	}
}
