package primes

import "math/big"

// Factor represents a factor with a base (Prime) and an exponent (Exponent)
type Factor struct {
	Prime    int64
	Exponent int
}

// Factorize calculates the factors of n and returns it as a slice of Factor's (uses a default cache).
func Factorize(n int64) []Factor {
	return FactorizeCache(primes, n)
}

// FactorizeCache calculates the factors of n and returns it as a slice of Factor's and specifies the cache to use.
func FactorizeCache(c Cache, n int64) []Factor {
	if 1 > n {
		return []Factor{}
	}
	var factors []Factor
	it := NewIteratorFromCache(c)
	for it.Next() {
		p := it.Get()
		var exp int
		for n%p == 0 {
			n /= p
			exp++
		}
		if 0 < exp {
			factors = append(factors, Factor{p, exp})
		}
		if n < p*p {
			break
		}
	}
	if 1 < n {
		factors = append(factors, Factor{n, 1})
	} else if 1 == n && 0 == len(factors) {
		return []Factor{{1, 1}}
	}
	return factors
}

// MustBigIntToInt64 returns the int64 representation of the big int n. If n is too large the method panics.
func MustBigIntToInt64(n *big.Int) int64 {
	if !n.IsInt64() {
		panic("n can't be represented as a int64")
	}
	return n.Int64()
}

// FactorizeBigInt calculates the factors of n and returns it as a slice of Factor's. If the base of a factor is larger than can be represented as a int64 the method will panic.
func FactorizeBigInt(n *big.Int) []Factor {
	n = big.NewInt(0).Set(n)
	var one = big.NewInt(1)
	if one.Cmp(n) > 0 {
		return []Factor{}
	}
	var zero = big.NewInt(0)
	var divModZero = func(p *big.Int) bool {
		var quo, rem = big.NewInt(0), big.NewInt(0)
		quo.QuoRem(n, p, rem)
		if rem.Cmp(zero) == 0 {
			n = quo
			return true
		}
		return false
	}
	var factors []Factor
	it := NewIterator()
	for it.Next() {
		var p = big.NewInt(it.Get())
		var exp int
		for divModZero(p) {
			exp++
		}
		if 0 < exp {
			factors = append(factors, Factor{it.Get(), exp})
		}
		p.Mul(p, p)
		if n.Cmp(p) < 0 {
			break
		}
	}
	if one.Cmp(n) < 0 {
		factors = append(factors, Factor{MustBigIntToInt64(n), 1})
	} else if one.Cmp(n) == 0 && 0 == len(factors) {
		return []Factor{{1, 1}}
	}
	return factors
}