

#******************************************************************************
#
#  rpnPrimeUtils.py
#
#  rpnChilada prime number utilies
#  copyright (c) 2020, Rick Gutleber (rickg@his.com)
#
#  License: GNU GPL 3.0 (see <http://www.gnu.org/licenses/gpl.html> for more
#  information).
#
#******************************************************************************

import os
import sys

from bisect import bisect_left
from pathlib import Path

from mpmath import arange, fadd, fmod, fmul, fprod, fsub

import gmpy2

from rpn.rpnDebug import debugPrint
from rpn.rpnGenerator import RPNGenerator
from rpn.rpnPrimes import primes
from rpn.rpnPersistence import cachedFunction, openPrimeCache
from rpn.rpnUtils import getUserDataPath, oneArgFunctionEvaluator, twoArgFunctionEvaluator, \
                         validateRealInt

import rpn.rpnGlobals as g


#******************************************************************************
#
#  isPrime
#
#******************************************************************************

def isPrime( n ):
    if g.zhangConjecturesAllowed and n < 1543267864443420616877677640751301:
        return isPrimeMillerRabin( int( n ) )
    elif n < 3317044064679887385961981:
        return isPrimeMillerRabin( int( n ) )
    else:
        debugPrint( 'primality testing of ' + str( int( n ) ) )
        return 1 if gmpy2.is_bpsw_prp( int( n ) ) else 0

@oneArgFunctionEvaluator( )
def isComposite( n ):
    return 0 if n == 1 or isPrime( n ) else 1

@oneArgFunctionEvaluator( )
def isPrimeOperator( n ):
    return 1 if isPrime( n ) else 0


#******************************************************************************
#
#  isPrimeNumberSimple
#
#  Use this for numbers smaller than 10,000,000,000.
#
#******************************************************************************

def isPrimeNumberSimple( n ):
    # if prime is already in the list, just pick it
    if n <= max( primes ):
        i = bisect_left( primes, n )
        return i != len( primes ) and primes[ i ] == n

    # Divide by each known prime
    limit = int( n ** .5 )

    for p in primes:
        if p > limit:
            return True

        if n % p == 0:
            return False

    return True


#******************************************************************************
#
#  getNextPrimeCandidateForAny
#
#  p is the next prime candidate.  Starting with ( p - 10 ) mod 210, we only
#  need to check 43 values out of the next 210, which eliminates all multiples
#  of 2, 3, 5, and 7 and saves us a lot of unnecessary checking.
#
#  We only need to check for a prime if ( p - 10 ) mod 210 is one of the
#  following:
#
#  1, 3, 7, 9, 13, 19, 21, 27, 31, 33, 37, 43, 49, 51, 57, 61, 63, 69, 73,
#  79, 87, 91, 93, 97, 99, 103, 111, 117, 121, 127, 129, 133, 139, 141, 147,
#  153, 157, 159, 163, 169, 171, 177, 181, 183, 187, 189, 199, 201
#
#  This list can be generated by:
#
#  rpn 10 4 primorial 10 + range lambda x 5 prime is_rough filter 10 -
#
#******************************************************************************

nextPrimeModuloTable = [
    # 0
    1,
    # 1
    2, 1,
    # 3
    4, 3, 2, 1,
    # 7
    2, 1,
    # 9
    4, 3, 2, 1,
    # 13
    6, 5, 4, 3, 2, 1,
    # 19
    2, 1,
    # 21
    6, 5, 4, 3, 2, 1,
    # 27
    4, 3, 2, 1,
    # 31
    2, 1,
    # 33
    4, 3, 2, 1,
    # 37
    6, 5, 4, 3, 2, 1,
    # 43
    6, 5, 4, 3, 2, 1,
    # 49
    2, 1,
    # 51
    6, 5, 4, 3, 2, 1,
    # 57
    4, 3, 2, 1,
    # 61
    2, 1,
    # 63
    6, 5, 4, 3, 2, 1,
    # 69
    4, 3, 2, 1,
    # 73
    6, 5, 4, 3, 2, 1,
    # 79
    8, 7, 6, 5, 4, 3, 2, 1,
    # 87
    4, 3, 2, 1,
    # 91
    2, 1,
    # 93
    4, 3, 2, 1,
    # 97
    2, 1,
    # 99
    4, 3, 2, 1,
    # 103
    8, 7, 6, 5, 4, 3, 2, 1,
    # 111
    6, 5, 4, 3, 2, 1,
    # 117
    4, 3, 2, 1,
    # 121
    6, 5, 4, 3, 2, 1,
    # 127
    2, 1,
    # 129
    4, 3, 2, 1,
    # 133
    6, 5, 4, 3, 2, 1,
    # 139
    2, 1,
    # 141
    6, 5, 4, 3, 2, 1,
    # 147
    6, 5, 4, 3, 2, 1,
    # 153
    4, 3, 2, 1,
    # 157
    2, 1,
    # 159
    4, 3, 2, 1,
    # 163
    6, 5, 4, 3, 2, 1,
    # 169
    2, 1,
    # 171
    6, 5, 4, 3, 2, 1,
    # 177
    4, 3, 2, 1,
    # 181
    2, 1,
    # 183
    4, 3, 2, 1,
    # 187
    2, 1,
    # 189
    10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
    # 199
    2, 1,
    # 201
    10, 9, 8, 7, 6, 5, 4, 3, 2
]

def getNextPrimeCandidateForAny( p ):
    f = int( ( p - 10 ) % 210 )
    return p + nextPrimeModuloTable[ f ]


#******************************************************************************
#
#  getPreviousPrimeCandidateForAny
#
#  p is the next prime candidate.  Starting with ( p - 10 ) mod 210, we only
#  need to check 43 values out of the next 210, which eliminates all multiples
#  of 2, 3, 5, and 7 and saves us a lot of unnecessary checking.
#
#  We only need to check for a prime if ( p - 10 ) mod 210 is one of the
#  following:
#
#  0, 2, 4, 8, 10, 14, 20, 22, 28, 32, 34, 38, 44, 50, 52, 58, 62, 64, 70, 74
#  80, 88, 92, 94, 98, 100, 104, 112, 118, 122, 128, 130, 134, 140, 142, 148
#  154, 158, 160, 164, 170, 172, 178, 182, 184, 188, 190, 200, 202,
#
#  This list can be generated by:
#
#  rpn [ 0 ] 10 4 primorial 10 + range lambda x 5 prime is_rough filter 10 - 1 + append
#
#******************************************************************************

previousPrimeModuloTable = [
    # 0
    9, 10,
    # 2
    1, 2,
    # 4
    1, 2, 3, 4,
    # 8
    1, 2,
    # 10
    1, 2, 3, 4,
    # 14
    1, 2, 3, 4, 5, 6,
    # 20
    1, 2,
    # 22
    1, 2, 3, 4, 5, 6,
    # 28
    1, 2, 3, 4,
    # 32
    1, 2,
    # 34
    1, 2, 3, 4,
    # 38
    1, 2, 3, 4, 5, 6,
    # 44
    1, 2, 3, 4, 5, 6,
    # 50
    1, 2,
    # 52
    1, 2, 3, 4, 5, 6,
    # 58
    1, 2, 3, 4,
    # 62
    1, 2,
    # 64
    1, 2, 3, 4, 5, 6,
    # 70
    1, 2, 3, 4,
    # 74
    1, 2, 3, 4, 5, 6,
    # 80
    1, 2, 3, 4, 5, 6, 7, 8,
    # 88
    1, 2, 3, 4,
    # 92
    1, 2,
    # 94
    1, 2, 3, 4,
    # 98
    1, 2,
    # 100
    1, 2, 3, 4,
    # 104
    1, 2, 3, 4, 5, 6, 7, 8,
    # 112
    1, 2, 3, 4, 5, 6,
    # 118
    1, 2, 3, 4,
    # 122
    1, 2, 3, 4, 5, 6,
    # 128
    1, 2,
    # 130
    1, 2, 3, 4,
    # 134
    1, 2, 3, 4, 5, 6,
    # 140
    1, 2,
    # 142
    1, 2, 3, 4, 5, 6,
    # 148
    1, 2, 3, 4, 5, 6,
    # 154
    1, 2, 3, 4,
    # 158
    1, 2,
    # 160
    1, 2, 3, 4,
    # 164
    1, 2, 3, 4, 5, 6,
    # 170
    1, 2,
    # 172
    1, 2, 3, 4, 5, 6,
    # 178
    1, 2, 3, 4,
    # 182
    1, 2,
    # 184
    1, 2, 3, 4,
    # 188
    1, 2,
    # 190
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
    # 200
    1, 2,
    # 202
    1, 2, 3, 4, 5, 6, 7, 8,
]


#******************************************************************************
#
#  getPreviousPrimeCandidateForAny
#
#******************************************************************************

def getPreviousPrimeCandidateForAny( p ):
    f = int( ( p - 10 ) % 210 )
    return p - previousPrimeModuloTable[ f ]


#******************************************************************************
#
#  getNextPrime
#
#******************************************************************************

def getNextPrime( p, func=getNextPrimeCandidateForAny ):
    if p < 2:
        return 2

    if p == 2:
        return 3

    if p < 5:
        return 5

    if p < 7:
        return 7

    p = func( p )

    debugPrint( 'testing', p )

    while not isPrime( p ):
        p = func( p )

    return p

@oneArgFunctionEvaluator( )
@cachedFunction( 'next_prime' )
def getNextPrimeOperator( n ):
    return getNextPrime( n, func=getNextPrimeCandidateForAny )


#******************************************************************************
#
#  getPreviousPrime
#
#******************************************************************************

def getPreviousPrime( p, func=getPreviousPrimeCandidateForAny ):
    if p < 12:
        if p < 3:
            raise ValueError( 'There is no previous prime to 2.' )

        if p == 3:
            return 2

        if p <= 5:
            return 3

        if p <= 7:
            return 5

        if p <= 11:
            return 7

    p = func( p )

    while not isPrime( p ):
        p = func( p )

    return p

@oneArgFunctionEvaluator( )
@cachedFunction( 'previous_prime' )
def getPreviousPrimeOperator( n ):
    return getPreviousPrime( n, func=getPreviousPrimeCandidateForAny )


#******************************************************************************
#
#  getNextPrimes
#
#******************************************************************************

def getNextPrimes( p, k, func=getNextPrimeCandidateForAny ):
    result = [ ]

    for _ in arange( 0, k ):
        p = getNextPrime( p, func )
        result.append( p )

    return result

@twoArgFunctionEvaluator( )
def getNextPrimesOperator( n, k ):
    return getNextPrimes( n, k, func=getNextPrimeCandidateForAny )


#******************************************************************************
#
#  getPreviousPrimes
#
#******************************************************************************

def getPreviousPrimes( p, k, func = getPreviousPrimeCandidateForAny ):
    if p < 1000000000 and getNthPrime( p ) < k:
        raise ValueError( 'There is no previous prime to 2.' )

    result = [ ]

    for _ in arange( 0, k ):
        p = getPreviousPrime( p, func )
        result.append( p )

    return result

@twoArgFunctionEvaluator( )
def getPreviousPrimesOperator( n, k ):
    return getPreviousPrimes( n, k, func=getPreviousPrimeCandidateForAny )


#******************************************************************************
#
#  getNthPrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthPrime( arg ):
    n = int( arg )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 2

    if n == 2:
        return 3

    if n == 3:
        return 5

    if g.primeDataAvailable and n >= 1000000000:
        openPrimeCache( 'huge_primes' )

        maxIndex = g.cursors[ 'huge_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        currentIndex, p = g.cursors[ 'huge_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    elif g.primeDataAvailable and n >= 1000000:
        openPrimeCache( 'large_primes' )

        currentIndex, p = g.cursors[ 'large_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    elif g.primeDataAvailable and n >= 100:
        openPrimeCache( 'small_primes' )

        currentIndex, p = g.cursors[ 'small_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        currentIndex = 4
        p = 7

    while n > currentIndex:
        p = getNextPrime( p )
        currentIndex += 1

    return p


#******************************************************************************
#
#  findPrime
#
#******************************************************************************

def findPrime( arg ):
    target = int( arg )

    if target < 3:
        return 1, 2
    elif target == 3:
        return 2, 3
    elif target <= 5:
        return 3, 5
    elif target <= 7:
        return 4, 7
    elif target < 541:          # 100th prime
        currentIndex = 4
        p = 7
    elif g.primeDataAvailable and target <= 15485863:     # 1,000,000th prime
        openPrimeCache( 'small_primes' )

        currentIndex, p = g.cursors[ 'small_primes' ].execute(
            '''SELECT id, max( value ) FROM cache WHERE value <= ?''', ( target, ) ).fetchone( )
    elif g.primeDataAvailable and target <= 22801763489:  # 1,000,000,000th prime
        openPrimeCache( 'large_primes' )

        currentIndex, p = g.cursors[ 'large_primes' ].execute(
            '''SELECT id, max( value ) FROM cache WHERE value <= ?''', ( target, ) ).fetchone( )
    elif g.primeDataAvailable:
        openPrimeCache( 'huge_primes' )

        currentIndex, p = g.cursors[ 'huge_primes' ].execute(
            '''SELECT id, max( value ) FROM cache WHERE value <= ?''', ( target, ) ).fetchone( )
    else:
        currentIndex = 4
        p = 7

    while True:
        oldP = p
        p = getNextPrime( p )
        currentIndex += 1

        if p > target:
            return currentIndex - 1, oldP

@oneArgFunctionEvaluator( )
def findPrimeOperator( n ):
    return findPrime( n )[ 0 ]


#******************************************************************************
#
#  findQuadrupletPrimes
#
#******************************************************************************

def findQuadrupletPrimes( arg ):
    n = int( validateRealInt( arg ) )

    if n < 5:
        return 1, [ 5, 7, 11, 13 ]

    if n < 11:
        return 2, [ 11, 13, 17, 19 ]

    if g.primeDataAvailable:
        openPrimeCache( 'quad_primes' )

        currentIndex, p = g.cursors[ 'quad_primes' ].execute(
            '''SELECT id, max( value ) FROM cache WHERE value <= ?''', ( n, ) ).fetchone( )
    else:
        p = 11

    while True:
        # All quadruplet primes mod 30 == 11
        p += 30

        if isPrime( p ) and isPrime( p + 2 ) and isPrime( p + 6 ) and isPrime( p + 8 ):
            currentIndex += 1

            if p > n:
                return currentIndex, [ p, p + 2, p + 6, p + 8 ]

@oneArgFunctionEvaluator( )
def findQuadrupletPrimeOperator( n ):
    return findQuadrupletPrimes( n )[ 0 ]

@oneArgFunctionEvaluator( )
def getNextQuadrupletPrime( n ):
    return findQuadrupletPrimes( n )[ 1 ][ 0 ]

@oneArgFunctionEvaluator( )
def getNextQuadrupletPrimes( n ):
    return findQuadrupletPrimes( n )[ 1 ]


#******************************************************************************
#
#  getNthQuadrupletPrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthQuadrupletPrime( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 5

    if n == 2:
        return 11

    if g.primeDataAvailable and n >= 10:
        openPrimeCache( 'quad_primes' )

        maxIndex = g.cursors[ 'quad_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        startingPlace, p = g.cursors[ 'quad_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        startingPlace = 2
        p = 11

    # after 5, the first of a prime quadruplet must be a number of the form 30n + 11
    while n > startingPlace:
        p += 30

        if isPrime( p ) and isPrime( p + 2 ) and isPrime( p + 6 ) and isPrime( p + 8 ):
            n -= 1

    return p


#******************************************************************************
#
#  getNthIsolatedPrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthIsolatedPrime( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 2

    if n == 2:
        return 23

    if g.primeDataAvailable:
        openPrimeCache( 'isolated_primes' )

        currentIndex, p = g.cursors[ 'isolated_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        currentIndex = 2
        p = 23

    while n > currentIndex:
        p = getNextPrime( p )

        if not isPrime( p - 2 ) and not isPrime( p + 2 ):
            currentIndex += 1

    return p


#******************************************************************************
#
#  getNextTwinPrimeCandidate
#
#  Looking at ( p - 10 ) mod 30, the only twin prime candidates are 1, 7,
#  and 19.
#
#******************************************************************************

def getNextTwinPrimeCandidate( p ):
    f = ( p - 10 ) % 30

    if f == 1:
        p += 6
    elif f == 7:
        p += 12
    else:  # f == 19
        p += 12

    return p


#******************************************************************************
#
#  getNthTwinPrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthTwinPrime( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 3

    if n == 2:
        return 5

    if n == 3:
        return 11

    if g.primeDataAvailable and n >= 100:
        openPrimeCache( 'twin_primes' )

        maxIndex = g.cursors[ 'twin_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        currentIndex, p = g.cursors[ 'twin_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        currentIndex = 3
        p = 11

    while n > currentIndex:
        p = getNextPrime( p, getNextTwinPrimeCandidate )

        if isPrime( p + 2 ):
            currentIndex += 1

    return p


#******************************************************************************
#
#  getNthTwinPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthTwinPrimeList( arg ):
    p = getNthTwinPrime( arg )
    return [ p, fadd( p, 2 ) ]


#******************************************************************************
#
#  getNthBalancedPrime
#
#  returns the first balanced prime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthBalancedPrime( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 5
    elif g.primeDataAvailable:
        openPrimeCache( 'balanced_primes' )

        maxIndex = g.cursors[ 'balanced_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        currentIndex, p = g.cursors[ 'balanced_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )

        if n == currentIndex:
            return p

        # fill in the variables as if we'd been searching...
        secondPrevPrime = getPreviousPrime( p )
        prevPrime = p
        p = getNextPrime( prevPrime )
    else:
        currentIndex = 1
        p = 7
        prevPrime = 5
        secondPrevPrime = 3

    while n > currentIndex:
        secondPrevPrime = prevPrime
        prevPrime = p
        p = getNextPrime( p )

        if ( prevPrime - secondPrevPrime ) == ( p - prevPrime ):
            currentIndex += 1

    return prevPrime


#******************************************************************************
#
#  getNthBalancedPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthBalancedPrimeList( arg ):
    q = getNthBalancedPrime( arg )
    p = getPreviousPrime( q )
    r = getNextPrime( q )

    return [ p, q, r ]


#******************************************************************************
#
#  getNthDoubleBalancedPrimeElement
#
#******************************************************************************

def getNthDoubleBalancedPrimeElement( arg, first = False ):
    n = int( validateRealInt( arg ) )

    numberOfPrimes = 5
    center = numberOfPrimes // 2

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 18731

    if g.primeDataAvailable:
        openPrimeCache( 'double_balanced_primes' )

        maxIndex = g.cursors[ 'double_balanced_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )
        currentIndex, p = g.cursors[ 'double_balanced_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )

        if currentIndex == n:
            return p
    else:
        if n > 50:
            sys.stderr.write( 'The prime number cache data is not available.  This could take some time...\n' )

        # no cache... we'll do this the hard way
        currentIndex = 1
        p = 18731

    primes = [ 0 ] * numberOfPrimes

    primes[ center ] = p

    for i in range( 1, center + 1 ):
        primes[ center - i ] = getPreviousPrime( primes[ center - i + 1 ] )
        primes[ center + i ] = getNextPrime( primes[ center + i - 1 ] )

    while n > currentIndex:
        p = getNextPrime( primes[ -1 ] )
        primes.append( p )
        del primes[ 0 ]

        balanced = True

        for i in range( 1, center + 1 ):
            if ( primes[ center - i + 1 ] - primes[ center - i ] ) != \
               ( primes[ center + i ] - primes[ center + i - 1 ] ):
                balanced = False
                break

        if balanced:
            currentIndex += 1

    return primes[ 0 ] if first else primes[ center ]


#******************************************************************************
#
#  getNthDoubleBalancedPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthDoubleBalancedPrimeList( arg ):
    p = getNthDoubleBalancedPrimeElement( arg, first = False )

    result = [ 0, 0, p, 0, 0 ]
    middle = 2

    for i in range( 0, 2 ):
        result[ middle + i + 1 ] = getNextPrime( result[ middle + i ] )
        result[ middle - ( i + 1 ) ] = getPreviousPrime( result[ middle - i ] )

    return result

@oneArgFunctionEvaluator( )
def getNthDoubleBalancedPrime( arg ):
    return getNthDoubleBalancedPrimeElement( arg )


#******************************************************************************
#
#  getNthTripleBalancedPrimeElement
#
#******************************************************************************

def getNthTripleBalancedPrimeElement( arg, first = False ):
    n = int( validateRealInt( arg ) )

    numberOfPrimes = 7
    center = numberOfPrimes // 2

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 683783

    if g.primeDataAvailable:
        openPrimeCache( 'triple_balanced_primes' )

        maxIndex = g.cursors[ 'triple_balanced_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        currentIndex, p = g.cursors[ 'triple_balanced_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )

        if n == currentIndex:
            return p
    else:
        if n > 10:
            sys.stderr.write( 'The prime number cache data is not available.  This could take some time...\n' )

        # no cache... we'll do this the hard way
        currentIndex = 1
        p = 683783

    primes = [ 0 ] * numberOfPrimes

    primes[ center ] = p

    for i in range( 1, center + 1 ):
        primes[ center - i ] = getPreviousPrime( primes[ center - i + 1 ] )
        primes[ center + i ] = getNextPrime( primes[ center + i - 1 ] )

    while n > currentIndex:
        p = getNextPrime( primes[ -1 ] )
        primes.append( p )
        del primes[ 0 ]

        balanced = True

        for i in range( 1, center + 1 ):
            if ( primes[ center - i + 1 ] - primes[ center - i ] ) != \
               ( primes[ center + i ] - primes[ center + i - 1 ] ):
                balanced = False
                break

        if balanced:
            currentIndex += 1

    return primes[ 0 ] if first else primes[ center ]


#******************************************************************************
#
#  getNthTripleBalancedPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthTripleBalancedPrimeList( arg ):
    p = getNthTripleBalancedPrimeElement( arg, first = True )

    result = [ 0, 0, 0, p, 0, 0, 0 ]
    middle = 3

    for i in range( 0, 3 ):
        result[ middle + i + 1 ] = getNextPrime( result[ middle + i ] )
        result[ middle - ( i + 1 ) ] = getPreviousPrime( result[ middle - i ] )

    return result


@oneArgFunctionEvaluator( )
def getNthTripleBalancedPrime( arg ):
    return getNthTripleBalancedPrimeElement( arg )


#******************************************************************************
#
#  getNthQuadrupleBalancedPrimeElement
#
#******************************************************************************

def getNthQuadrupleBalancedPrimeElement( arg, first = False ):
    n = int( validateRealInt( arg ) )

    numberOfPrimes = 9
    center = numberOfPrimes // 2

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 98303927

    if g.primeDataAvailable:
        openPrimeCache( 'quadruple_balanced_primes' )

        maxIndex = g.cursors[ 'quadruple_balanced_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        currentIndex, p = g.cursors[ 'quadruple_balanced_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )

        if n == currentIndex:
            return p
    else:
        if n > 10:
            sys.stderr.write( 'The prime number cache data is not available.  This could take some time...\n' )

        # no cache... we'll do this the hard way
        currentIndex = 1
        p = 98303927

    primes = [ 0 ] * numberOfPrimes

    primes[ center ] = p

    for i in range( 1, center + 1 ):
        primes[ center - i ] = getPreviousPrime( primes[ center - i + 1 ] )
        primes[ center + i ] = getNextPrime( primes[ center + i - 1 ] )

    while n > currentIndex:
        p = getNextPrime( primes[ -1 ] )
        print( p )
        primes.append( p )
        del primes[ 0 ]

        balanced = True

        for i in range( 1, center + 1 ):
            if ( primes[ center - i + 1 ] - primes[ center - i ] ) != \
               ( primes[ center + i ] - primes[ center + i - 1 ] ):
                balanced = False
                break

        if balanced:
            currentIndex += 1

    return primes[ 0 ] if first else primes[ center ]


#******************************************************************************
#
#  getNthQuadrupleBalancedPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthQuadrupleBalancedPrimeList( arg ):
    p = getNthQuadrupleBalancedPrimeElement( arg, first = True )

    result = [ 0, 0, 0, 0, p, 0, 0, 0, 0 ]
    middle = 4

    for i in range( 0, 4 ):
        result[ middle + i + 1 ] = getNextPrime( result[ middle + i ] )
        result[ middle - ( i + 1 ) ] = getPreviousPrime( result[ middle - i ] )

    return result


@oneArgFunctionEvaluator( )
def getNthQuadrupleBalancedPrime( arg ):
    return getNthQuadrupleBalancedPrimeElement( arg )


#******************************************************************************
#
#  getNthSophiePrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthSophiePrime( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 2

    if n == 2:
        return 3

    if n == 3:
        return 5

    if g.primeDataAvailable and n >= 100:
        openPrimeCache( 'sophie_primes' )

        maxIndex = g.cursors[ 'sophie_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        currentIndex, p = g.cursors[ 'sophie_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        currentIndex = 4
        p = 11

    while n > currentIndex:
        p = getNextPrime( p )

        if isPrime( 2 * p + 1 ):
            currentIndex += 1

    return p

@oneArgFunctionEvaluator( )
def getSafePrime( n ):
    return fadd( fmul( getNthSophiePrime( n ), 2 ), 1 )


#******************************************************************************
#
#  getNthCousinPrime
#
#  http://oeis.org/A023200
#
#  Validate: rpn 1 52 range cousin 23200 oeis  -
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthCousinPrime( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 3

    if g.primeDataAvailable and n >= 100:
        openPrimeCache( 'cousin_primes' )

        maxIndex = g.cursors[ 'cousin_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        currentIndex, p = g.cursors[ 'cousin_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        currentIndex = 2
        p = 7

    while n > currentIndex:
        p = getNextPrime( p )

        if isPrime( p + 4 ):
            currentIndex += 1

    return p


#******************************************************************************
#
#  getNthCousinPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthCousinPrimeList( arg ):
    p = getNthCousinPrime( arg )
    return [ p, fadd( p, 4 ) ]


#******************************************************************************
#
#  getNextSexyPrimeCandidate
#
#  For a number ( p - 10 ) mod 30, the only sexy candidates are 1, 3, 7, 13,
#  21 and 27.
#
#******************************************************************************

def getNextSexyPrimeCandidate( p ):
    f = ( p - 10 ) % 30

    if f == 1:
        p += 2
    elif f == 3:
        p += 4
    elif f == 7:
        p += 6
    elif f == 13:
        p += 8
    elif f == 21:
        p += 6
    else:   # f == 27
        p += 4

    return p


#******************************************************************************
#
#  getNthSexyPrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthSexyPrime( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 5

    if g.primeDataAvailable and n >= 100:
        openPrimeCache( 'sexy_primes' )

        maxIndex = g.cursors[ 'sexy_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        startingPlace, p = g.cursors[ 'sexy_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        startingPlace = 2
        p = 7

    while n > startingPlace:
        p = getNextPrime( p, getNextSexyPrimeCandidate )

        if isPrime( p + 6 ):
            n -= 1

    return p


#******************************************************************************
#
#  getNthSexyPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthSexyPrimeList( arg ):
    p = getNthSexyPrime( arg )
    return [ p, fadd( p, 6 ) ]


#******************************************************************************
#
#  getNthSexyTriplet
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthSexyTriplet( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 7

    if g.primeDataAvailable and n >= 100:
        openPrimeCache( 'sexy_triplets' )

        maxIndex = g.cursors[ 'sexy_triplets' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        startingPlace, p = g.cursors[ 'sexy_triplets' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        startingPlace = 1
        p = 7

    f = p % 10

    while n > startingPlace:
        if f == 1:
            p += 6
            f = 7
        else:
            p += 4
            f = 1

        if isPrime( p ) and isPrime( p + 6 ) and isPrime( p + 12 ) and not isPrime( p + 18 ):
            n -= 1

    return p


#******************************************************************************
#
#  getNthSexyTripletList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthSexyTripletList( arg ):
    p = getNthSexyTriplet( arg )
    return [ p, fadd( p, 6 ), fadd( p, 12 ) ]


#******************************************************************************
#
#  getNthSexyQuadruplet
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthSexyQuadruplet( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 5

    if n < 100:
        startingPlace = 2
        p = 11
    elif g.primeDataAvailable and n >= 100:
        openPrimeCache( 'sexy_quadruplets' )

        maxIndex = g.cursors[ 'sexy_quadruplets' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        startingPlace, p = g.cursors[ 'sexy_quadruplets' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )

    while n > startingPlace:
        p += 10

        if isPrime( p ) and isPrime( p + 6 ) and isPrime( p + 12 ) and isPrime( p + 18 ):
            n -= 1

    return p


#******************************************************************************
#
#  getNthSexyQuadrupletList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthSexyQuadrupletList( arg ):
    p = getNthSexyQuadruplet( arg )
    return [ p, fadd( p, 6 ), fadd( p, 12 ), fadd( p, 18 ) ]


#******************************************************************************
#
#  getNextOctyPrimeCandidate
#
#  For a number ( p - 10 ) mod 30, the only sexy candidates are 1, 3, 9, 11,
#  13, 19, 21, 23 and 29.
#
#******************************************************************************

def getNextOctyPrimeCandidate( p ):
    f = ( p - 10 ) % 30

    if f == 1:
        p += 2
    elif f == 3:
        p += 6
    elif f == 9:
        p += 2
    elif f == 11:
        p += 2
    elif f == 13:
        p += 6
    elif f == 19:
        p += 2
    elif f == 21:
        p += 2
    elif f == 23:
        p += 6
    else:  # 29
        p += 2

    return p


#******************************************************************************
#
#  getNthOctyPrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthOctyPrime( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 3

    if n == 2:
        return 5

    if g.primeDataAvailable:
        openPrimeCache( 'octy_primes' )

        maxIndex = g.cursors[ 'octy_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        startingPlace, p = g.cursors[ 'octy_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        startingPlace = 3
        p = 11

    while n > startingPlace:
        p = getNextPrime( p, getNextOctyPrimeCandidate )

        if isPrime( p + 8 ):
            n -= 1

    return p


#******************************************************************************
#
#  getNthSexyPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthOctyPrimeList( arg ):
    p = getNthOctyPrime( arg )
    return [ p, fadd( p, 8 ) ]


#******************************************************************************
#
#  getNthTripletPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthTripletPrimeList( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return [ 5, 7, 11 ]

    if n == 2:
        return [ 7, 11, 13 ]

    if n == 3:
        return [ 11, 13, 17 ]

    if g.primeDataAvailable and n >= 100:
        openPrimeCache( 'triplet_primes' )

        maxIndex = g.cursors[ 'triplet_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        currentIndex, p = g.cursors[ 'triplet_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        currentIndex = 3
        p = 11

    f = p % 10

    pPlus2 = False

    while n > currentIndex:
        if f == 1:
            p += 2
            f = 3
        elif f == 3:
            p += 4
            f = 7
        else:
            p += 4
            f = 1

        if isPrime( p ) and isPrime( p + 6 ):
            if isPrime( p + 2 ):
                pPlus2 = True
                currentIndex += 1
            elif isPrime( p + 4 ):
                pPlus2 = False
                currentIndex += 1

    return [ p, p + 2 if pPlus2 else p + 4, p + 6 ]


#******************************************************************************
#
#  getNthTripletPrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthTripletPrime( arg ):
    return getNthTripletPrimeList( arg )[ 0 ]


#******************************************************************************
#
#  getNthQuadrupletPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthQuadrupletPrimeList( arg ):
    p = getNthQuadrupletPrime( arg )
    return [ p, fadd( p, 2 ), fadd( p, 6 ), fadd( p, 8 ) ]


#******************************************************************************
#
#  getNextQuintupletPrimeCandidate
#
#  For ( p - 10 ) mod 30, the only quintuplet prime candidates are:
#  1, 7, 21, or 27
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNextQuintupletPrimeCandidate( p ):
    f = p % 30

    if f == 7:
        p += 4
    else:
        p += 26

    return p


#******************************************************************************
#
#  getNthQuintupletPrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthQuintupletPrime( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 5

    if n == 2:
        return 7

    if g.primeDataAvailable and n >= 10:
        openPrimeCache( 'quint_primes' )

        maxIndex = g.cursors[ 'quint_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        currentIndex, p = g.cursors[ 'quint_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        currentIndex = 3
        p = 11

    # after 5, the first of a prime quintruplet must be a number of the form 30n + 11
    while n > currentIndex:
        p = getNextPrime( p, getNextQuintupletPrimeCandidate )

        f = p % 30

        if ( ( f == 11 ) and isPrime( p + 2 ) and isPrime( p + 6 ) and \
                             isPrime( p + 8 ) and isPrime( p + 12 ) ) or \
           ( ( f == 7 ) and isPrime( p + 4 ) and isPrime( p + 6 ) and \
                            isPrime( p + 10 ) and isPrime( p + 12 ) ):
            currentIndex += 1

    return p


#******************************************************************************
#
#  getNthQuintupletPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthQuintupletPrimeList( arg ):
    if arg == 1:
        return [ 5, 7, 11, 13, 17 ]

    p = getNthQuintupletPrime( arg )

    f = p % 10

    if f == 1:
        return [ p, fadd( p, 2 ), fadd( p, 6 ), fadd( p, 8 ), fadd( p, 12 ) ]
    elif f == 7:
        return [ p, fadd( p, 4 ), fadd( p, 6 ), fadd( p, 10 ), fadd( p, 12 ) ]
    else:
        raise ValueError( 'internal error:  getNthQuintupletPrimeList is broken' )


#******************************************************************************
#
#  findQuintupletPrimes
#
#******************************************************************************

def findQuintupletPrimes( arg ):
    n = int( validateRealInt( arg ) )

    if n < 5:
        return 1, [ 5, 7, 11, 13, 17 ]
    elif n < 7:
        return 2, [ 7, 11, 13, 17, 19 ]

    if g.primeDataAvailable:
        openPrimeCache( 'quint_primes' )

        currentIndex, p = g.cursors[ 'quint_primes' ].execute(
            '''SELECT id, max( value ) FROM cache WHERE value <= ?''', ( n, ) ).fetchone( )
    else:
        currentIndex = 3
        p = 11

    while True:
        p += 30

        f = p % 10

        if ( ( f == 1 ) and isPrime( p + 2 ) and isPrime( p + 6 ) and isPrime( p + 8 ) and isPrime( p + 12 ) ) or \
           ( ( f == 7 ) and isPrime( p + 4 ) and isPrime( p + 6 ) and isPrime( p + 10 ) and isPrime( p + 12 ) ):
            currentIndex += 1

            if p > n:
                if f == 1:
                    return currentIndex, [ p, fadd( p, 2 ), fadd( p, 6 ), fadd( p, 8 ), fadd( p, 12 ) ]
                elif f == 7:
                    return currentIndex, [ p, fadd( p, 4 ), fadd( p, 6 ), fadd( p, 10 ), fadd( p, 12 ) ]

@oneArgFunctionEvaluator( )
def findQuintupletPrimeOperator( n ):
    return findQuintupletPrimes( n )[ 0 ]

@oneArgFunctionEvaluator( )
def getNextQuintupletPrime( n ):
    return findQuintupletPrimes( n )[ 1 ][ 0 ]

@oneArgFunctionEvaluator( )
def getNextQuintupletPrimes( n ):
    return findQuintupletPrimes( n )[ 1 ]


#******************************************************************************
#
#  findSextupletPrimes
#
#******************************************************************************

def findSextupletPrimes( arg ):
    n = int( validateRealInt( arg ) )

    if n < 7:
        return 1, [ 7, 11, 13, 17, 19, 23 ]
    elif n < 97:
        return 2, [ 97, 101, 103, 107, 109, 113 ]

    if g.primeDataAvailable:
        openPrimeCache( 'sext_primes' )

        currentIndex, p = g.cursors[ 'sext_primes' ].execute(
            '''SELECT id, max( value ) FROM cache WHERE value <= ?''', ( n, ) ).fetchone( )
    else:
        currentIndex = 3
        p = 16057

    while True:
        p += 210

        if isPrime( p + 4 ) and isPrime( p + 6 ) and isPrime( p + 10 ) and \
           isPrime( p + 12 ) and isPrime( p + 16 ):
            currentIndex += 1

            if p > n:
                return currentIndex, [ p, fadd( p, 4 ), fadd( p, 6 ), fadd( p, 10 ), fadd( p, 12 ), fadd( p, 16 ) ]

@oneArgFunctionEvaluator( )
def findSextupletPrimeOperator( n ):
    return findSextupletPrimes( n )[ 0 ]

@oneArgFunctionEvaluator( )
def getNextSextupletPrime( n ):
    return findSextupletPrimes( n )[ 1 ][ 0 ]

@oneArgFunctionEvaluator( )
def getNextSextupletPrimes( n ):
    return findSextupletPrimes( n )[ 1 ]


#******************************************************************************
#
#  findTripletPrimes
#
#******************************************************************************

def findTripletPrimes( arg ):
    n = int( validateRealInt( arg ) )

    if n < 5:
        return 1, [ 5, 7, 11 ]

    if g.primeDataAvailable:
        openPrimeCache( 'triplet_primes' )

        currentIndex, p = g.cursors[ 'triplet_primes' ].execute(
            '''SELECT id, max( value ) FROM cache WHERE value <= ?''', ( n, ) ).fetchone( )
    else:
        currentIndex = 2
        p = 7

    while True:
        p = getNextPrime( p )

        if isPrime( p + 6 ):
            if isPrime( p + 2 ):
                currentIndex += 1

                if p > n:
                    return currentIndex, [ p, fadd( p, 2 ), fadd( p, 6 ) ]
            elif isPrime( p + 4 ):
                currentIndex += 1

                if p > n:
                    return currentIndex, [ p, fadd( p, 4 ), fadd( p, 6 ) ]

@oneArgFunctionEvaluator( )
def findTripletPrimeOperator( n ):
    return findTripletPrimes( n )[ 0 ]

@oneArgFunctionEvaluator( )
def getNextTripletPrime( n ):
    return findTripletPrimes( n )[ 1 ][ 0 ]

@oneArgFunctionEvaluator( )
def getNextTripletPrimes( n ):
    return findTripletPrimes( n )[ 1 ]



#******************************************************************************
#
#  findTwinPrimes
#
#******************************************************************************

def findTwinPrimes( arg ):
    n = int( validateRealInt( arg ) )

    if n < 5:
        return 1, [ 3, 5 ]

    if g.primeDataAvailable:
        openPrimeCache( 'twin_primes' )

        currentIndex, p = g.cursors[ 'twin_primes' ].execute(
            '''SELECT id, max( value ) FROM cache WHERE value <= ?''', ( n, ) ).fetchone( )
    else:
        currentIndex = 2
        p = 5

    while True:
        p = getNextPrime( p )

        if isPrime( p + 2 ):
            currentIndex += 1

            if p > n:
                return currentIndex, [ p, fadd( p, 2 ) ]

@oneArgFunctionEvaluator( )
def findTwinPrimeOperator( n ):
    return findTwinPrimes( n )[ 0 ]

@oneArgFunctionEvaluator( )
def getNextTwinPrime( n ):
    return findTwinPrimes( n )[ 1 ][ 0 ]

@oneArgFunctionEvaluator( )
def getNextTwinPrimes( n ):
    return findTwinPrimes( n )[ 1 ]


#******************************************************************************
#
#  getNthSextupletPrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthSextupletPrime( arg ):
    n = int( validateRealInt( arg ) )

    if n < 1:
        raise ValueError( 'index must be > 0' )

    if n == 1:
        return 7

    if n == 2:
        return 97

    if g.primeDataAvailable:
        openPrimeCache( 'sext_primes' )

        maxIndex = g.cursors[ 'sext_primes' ].execute(
            '''SELECT MAX( id ) FROM cache''' ).fetchone( )[ 0 ]

        if n > maxIndex:
            sys.stderr.write( '{:,} is above the max cached index of {:,}.  This could take some time...\n'.
                              format( n, maxIndex ) )

        startingPlace, p = g.cursors[ 'sext_primes' ].execute(
            '''SELECT MAX( id ), value FROM cache WHERE id <= ?''', ( int( n ), ) ).fetchone( )
    else:
        startingPlace = 2
        p = 97

    # all sets of prime sextuplets must start with 210x+97
    while n > startingPlace:
        p += 210

        if isPrime( p ) and isPrime( p + 4 ) and isPrime( p + 6 ) and \
           isPrime( p + 10 ) and isPrime( p + 12 ) + isPrime( p + 16 ):
            n -= 1

    return p

@oneArgFunctionEvaluator( )
def findSextupletPrimeOperator( n ):
    return findSextupletPrimes( n )[ 0 ]

@oneArgFunctionEvaluator( )
def getNextSextupletPrime( n ):
    return findSextupletPrimes( n )[ 1 ][ 0 ]

@oneArgFunctionEvaluator( )
def getNextSextupletPrimes( n ):
    return findSextupletPrimes( n )[ 1 ]


#******************************************************************************
#
#  getNthSextupletPrimeList
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthSextupletPrimeList( arg ):
    p = getNthSextupletPrime( arg )
    return [ p, fadd( p, 4 ), fadd( p, 6 ), fadd( p, 10 ), fadd( p, 12 ), fadd( p, 16 ) ]


#******************************************************************************
#
#  getNthPrimeRange
#
#******************************************************************************

@twoArgFunctionEvaluator( )
def getNthPrimeRange( arg1, arg2 ):
    n = int( validateRealInt( arg1 ) )
    count = int( validateRealInt( arg2 ) )

    if count < 1:
        return [ ]

    # for primes below 7, we have to do it manually
    if n == 1:
        if count == 1:
            return [ 2 ]
        elif count == 2:
            return[ 2, 3 ]
        elif count == 3:
            return [ 2, 3, 5 ]
        else:
            result = [ 2, 3, 5, 7 ]
            count -= 3
            p = 7
    elif n == 2:
        if count == 1:
            return [ 3 ]
        elif count == 2:
            return [ 3, 5 ]
        else:
            result = [ 3, 5, 7 ]
            count -= 2
            p = 7
    elif n == 3:
        if count == 1:
            return [ 5 ]
        else:
            result = [ 5, 7 ]
            count -= 1
            p = 7
    else:
        p = getNthPrime( n )
        result = [ p ]

    found = 1

    while found < count:
        p = getNextPrimeCandidateForAny( p )

        if isPrime( p ):
            result.append( p )
            found += 1

    return result


#******************************************************************************
#
#  getNthSuperPrime
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthSuperPrime( arg ):
    return getNthPrime( getNthPrime( arg ) )


#******************************************************************************
#
#  getNthPolyPrime
#
#******************************************************************************

@twoArgFunctionEvaluator( )
def getNthPolyPrime( n, poly ):
    result = getNthPrime( n )

    for _ in arange( 1, poly ):
        result = getNthPrime( result )

    return result


#******************************************************************************
#
#  getPrimes
#
#******************************************************************************

def getPrimes( value, count ):
    for i in getNthPrimeRange( value, count ):
        yield i

@twoArgFunctionEvaluator( )
def getPrimesGenerator( n, k ):
    return RPNGenerator( getPrimes( n, k ) )


#******************************************************************************
#
#  getPrimeRange
#
#******************************************************************************

@twoArgFunctionEvaluator( )
def getPrimeRange( start, end ):
    result = list( )

    for i in getNthPrimeRange( start, fadd( fsub( end, start ), 1 ) ):
        result.append( i )

    return result


#******************************************************************************
#
#  getNthPrimorial
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getNthPrimorial( n ):
    if validateRealInt( n ) == 0:
        return 1

    return fprod( getPrimes( 1, n ) )


#******************************************************************************
#
#  countCache
#
#******************************************************************************

def countCache( name ):
    openPrimeCache( name )
    return g.cursors[ name ].execute( '''SELECT count( id ) FROM cache''' ).fetchone( )[ 0 ]


#******************************************************************************
#
#  getMaxPrime
#
#******************************************************************************

def getMaxPrime( name ):
    openPrimeCache( name )
    return g.cursors[ name ].execute( '''SELECT max( id ), value FROM cache''' ).fetchone( )


#******************************************************************************
#
#  millerRabinPass
#
#  https://gist.github.com/sharnett/5479106
#
#******************************************************************************

def millerRabinPass( a, s, d, n ):
    aToPower = pow( a, d, n )

    if aToPower == 1:
        return True

    for _ in range( s - 1 ):
        if aToPower == n - 1:
            return True

        aToPower = ( aToPower * aToPower ) % n

    return aToPower == n - 1


#******************************************************************************
#
#  isPrimeMillerRabin
#
#  https://gist.github.com/sharnett/5479106
#  http://mathworld.wolfram.com/StrongPseudoprime.html
#
#  http://pi-e.de/Miller-Rabin-Pseudoprimzahlen.htm - check this out!
#
#******************************************************************************

def isPrimeMillerRabin( n ):
    if n < 2:
        return False

    if n < 2047:
        testPrimes = [ 2 ]
    elif n < 1373653:
        testPrimes = [ 2, 3 ]
    elif n < 9080191:
        testPrimes = [ 31, 73 ]
    elif n < 4759123141:
        testPrimes = [ 2, 7, 61 ]
    elif n < 2152302898747:
        testPrimes = [ 2, 3, 5, 7, 11 ]
    elif n < 3474749660383:
        testPrimes = [ 2, 3, 5, 7, 11, 13 ]
    elif n < 341550071728321:
        testPrimes = [ 2, 3, 5, 7, 11, 13, 17 ]
    elif n < 18446744073709551616:
        testPrimes = [ 2, 325, 9375, 28178, 450775, 9780504, 1795265022 ]
    elif n < 318665857834031151167461:
        testPrimes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 ]
    elif n < 3317044064679887385961981:
        testPrimes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41 ]
    elif n < 6003094289670105800312596501 and g.zhangConjecturesAllowed:
        testPrimes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43 ]
    elif n < 59276361075595573263446330101 and g.zhangConjecturesAllowed:
        testPrimes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47 ]
    elif n < 564132928021909221014087501701 and g.zhangConjecturesAllowed:
        testPrimes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59 ]
    elif n < 1543267864443420616877677640751301 and g.zhangConjecturesAllowed:
        testPrimes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67 ]
    else:
        if g.zhangConjecturesAllowed:
            raise ValueError( str( n ) + ' is too large to use the deterministic Miller-Rabin test for 19 prime bases' )

        raise ValueError( str( n ) + ' is too large to use the deterministic Miller-Rabin test for 13 prime bases' )

    if n in testPrimes:
        return True

    d = n - 1
    s = 0

    while d % 2 == 0:
        d >>= 1
        s += 1

    for a in testPrimes:
        if not millerRabinPass( a, s, d, n ):
            return False

    return True


#******************************************************************************
#
#  isStrongPseudoprime
#
#******************************************************************************

@twoArgFunctionEvaluator( )
def isStrongPseudoprime( n, k ):
    if n < k or fmod( n, 2 ) == 0 or isPrime( n ):
        return 0

    d = int( n - 1 )
    s = 0

    while d % 2 == 0:
        d >>= 1
        s += 1

    return 1 if millerRabinPass( int( k ), s, d, int( n ) ) else 0


#******************************************************************************
#
#  checkForPrimeData
#
#******************************************************************************

def checkForPrimeData( ):
    primeFile = Path( getUserDataPath( ) + os.sep + 'large_primes.cache' )

    if primeFile.is_file( ):
        g.primeDataAvailable = True
    else:
        g.primeDataAvailable = False

