// This file implements two versions of the Miller-Rabin primality test.
// One of the implementations is deterministic and the other is probabilistic.
// The Miller-Rabin test is one of the simplest and fastest known primality
// tests and is widely used.
//
// Authors:
// [Taj](https://github.com/tjgurwara99)
// [Rak](https://github.com/raklaptudirm)

package prime

import (
	"math/rand"

	"github.com/TheAlgorithms/Go/math/modular"
)

// formatNum accepts a number and returns the
// odd number d such that num = 2^s * d + 1
func formatNum(num int64) (d int64, s int64) {
	d = num - 1
	for num%2 == 0 {
		d /= 2
		s++
	}
	return
}

// isTrivial checks if num's primality is easy to determine.
// If it is, it returns true and num's primality. Otherwise
// it returns false and false.
func isTrivial(num int64) (prime bool, trivial bool) {
	if num <= 4 {
		// 2 and 3 are primes
		prime = num == 2 || num == 3
		trivial = true
	} else {
		prime = false
		// number is trivial prime if
		// it is divisible by 2
		trivial = num%2 == 0
	}

	return
}

// MillerTest tests whether num is a strong probable prime to a witness.
// Formally: a^d ≡ 1 (mod n) or a^(2^r * d) ≡ -1 (mod n), 0 <= r <= s
func MillerTest(num, witness int64) (bool, error) {
	d, _ := formatNum(num)
	res, err := modular.Exponentiation(witness, d, num)

	if err != nil {
		return false, err
	}
	// miller conditions checks
	if res == 1 || res == num-1 {
		return true, nil
	}

	for d != num-1 {
		res = (res * res) % num
		d *= 2
		if res == 1 {
			return false, nil
		}
		if res == num-1 {
			return true, nil
		}
	}
	return false, nil
}

// MillerRandomTest This is the intermediate step that repeats within the
// miller rabin primality test for better probabilitic chances of
// receiving the correct result with random witnesses.
func MillerRandomTest(num int64) (bool, error) {
	random := rand.Int63n(num-2) + 2
	return MillerTest(num, random)
}

// MillerTestMultiple is like MillerTest but runs the test for multiple
// witnesses.
func MillerTestMultiple(num int64, witnesses ...int64) (bool, error) {
	for _, witness := range witnesses {
		prime, err := MillerTest(num, witness)
		if err != nil {
			return false, err
		}

		if !prime {
			return false, nil
		}
	}

	return true, nil
}

// MillerRabinProbabilistic is a probabilistic test for primality
// of an integer based of the algorithm devised by Miller and Rabin.
func MillerRabinProbabilistic(num, rounds int64) (bool, error) {
	if prime, trivial := isTrivial(num); trivial {
		// num is a trivial number
		return prime, nil
	}

	for i := int64(0); i < rounds; i++ {
		val, err := MillerRandomTest(num)
		if err != nil {
			return false, err
		}
		if !val {
			return false, nil
		}
	}
	return true, nil
}

// MillerRabinDeterministic is a Deterministic version of the Miller-Rabin
// test, which returns correct results for all valid int64 numbers.
func MillerRabinDeterministic(num int64) (bool, error) {
	if prime, trivial := isTrivial(num); trivial {
		// num is a trivial number
		return prime, nil
	}

	switch {
	case num < 2047:
		// witness 2 can determine the primality of any number less than 2047
		return MillerTest(num, 2)
	case num < 1_373_653:
		// witnesses 2 and 3 can determine the primality
		// of any number less than 1,373,653
		return MillerTestMultiple(num, 2, 3)
	case num < 9_080_191:
		// witnesses 31 and 73 can determine the primality
		// of any number less than 9,080,191
		return MillerTestMultiple(num, 31, 73)
	case num < 25_326_001:
		// witnesses 2, 3, and 5 can determine the
		// primality of any number less than 25,326,001
		return MillerTestMultiple(num, 2, 3, 5)
	case num < 1_122_004_669_633:
		// witnesses 2, 13, 23, and 1,662,803 can determine the
		// primality of any number less than 1,122,004,669,633
		return MillerTestMultiple(num, 2, 13, 23, 1_662_803)
	case num < 2_152_302_898_747:
		// witnesses 2, 3, 5, 7, and 11 can determine the primality
		// of any number less than 2,152,302,898,747
		return MillerTestMultiple(num, 2, 3, 5, 7, 11)
	case num < 341_550_071_728_321:
		// witnesses 2, 3, 5, 7, 11, 13, and 17 can determine the
		// primality of any number less than 341,550,071,728,321
		return MillerTestMultiple(num, 2, 3, 5, 7, 11, 13, 17)
	default:
		// witnesses 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, and 37 can determine
		// the primality of any number less than 318,665,857,834,031,151,167,461
		// which is well above the max int64 9,223,372,036,854,775,807
		return MillerTestMultiple(num, 2, 3, 5, 7, 11, 13, 17, 19, 23, 31, 37)
	}
}
