package blackrock

import (
	"math"
)

// Blackrock cipher implementation from masscan

type BlackRock struct {
	Rounds int64
	Seed   int64
	Range  int64
	A      int64
	B      int64
}

func New(rangez, seed int64) *BlackRock {
	split := int64(math.Floor(math.Sqrt(float64(rangez))))
	var blackrock BlackRock
	blackrock.Rounds = 3
	blackrock.Seed = seed
	blackrock.Range = rangez
	blackrock.A = split - 1
	blackrock.B = split + 1

	if blackrock.A <= 0 {
		blackrock.A = 1
	}

	for blackrock.A*blackrock.B <= rangez {
		blackrock.B++
	}

	return &blackrock
}

// Inner permutation function
func (blackrock *BlackRock) F(j, r, seed int64) int64 {
	var primes = []int64{961752031, 982324657, 15485843, 961752031}
	r = (r << (r & 0x4)) + r + seed
	return int64(math.Abs(float64((((primes[j]*r + 25) ^ r) + j))))
}

// Outer feistal construction
func (blackrock *BlackRock) Fe(r, a, b, m, seed int64) int64 {
	var (
		L, R int64
		j    int64
		tmp  int64
	)

	L = m % a
	R = m / a

	for j = 1; j <= r; j++ {
		if j&1 == 1 {
			tmp = (L + blackrock.F(j, R, seed)) % a
		} else {
			tmp = (L + blackrock.F(j, R, seed)) % b
		}
		L = R
		R = tmp
	}

	if r&1 == 1 {
		return a*L + R
	}
	return a*R + L
}

// Outer reverse feistal construction
func (blackrock *BlackRock) Unfe(r, a, b, m, seed int64) int64 {
	var (
		L, R int64
		j    int64
		tmp  int64
	)

	if r&1 == 1 {
		R = m % a
		L = m / a
	} else {
		L = m % a
		R = m / a
	}

	for j = r; j >= 1; j-- {
		if j&1 == 1 {
			tmp = blackrock.F(j, L, seed)
			if tmp > R {
				tmp -= -R
				tmp = a - (tmp % a)
				if tmp == a {
					tmp = 0
				}
			} else {
				tmp = R - tmp
				tmp %= a
			}
		} else {
			tmp = blackrock.F(j, L, seed)
			if tmp > R {
				tmp = (tmp - R)
				tmp = b - (tmp % b)
				if tmp == b {
					tmp = 0
				}
			} else {
				tmp = R - tmp
				tmp %= b
			}
		}
		R = L
		L = tmp
	}

	return a*R + L
}

func (blackrock *BlackRock) Shuffle(m int64) int64 {
	c := blackrock.Fe(blackrock.Rounds, blackrock.A, blackrock.B, m, blackrock.Seed)

	for c >= blackrock.Range {
		c = blackrock.Fe(blackrock.Rounds, blackrock.A, blackrock.B, c, blackrock.Seed)
	}

	return c
}

func (blackrock *BlackRock) UnShuffle(m int64) int64 {
	c := blackrock.Unfe(blackrock.Rounds, blackrock.A, blackrock.B, m, blackrock.Seed)
	for c >= blackrock.Range {
		c = blackrock.Unfe(blackrock.Rounds, blackrock.A, blackrock.B, c, blackrock.Seed)
	}

	return c
}
