#!/usr/bin/env python

#******************************************************************************
#
#  rpnPrimeUtils.py
#
#  rpnChilada prime number utilies
#  copyright (c) 2021, 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
from rpn.rpnValidator import argValidator, IntValidator

import rpn.rpnGlobals as g


#******************************************************************************
#
#  isPrimeOperator
#
#******************************************************************************

def isPrime( n ):
    if g.zhangConjecturesAllowed and n < 1543267864443420616877677640751301:
        return isPrimeMillerRabin( int( n ) )

    if n < 3317044064679887385961981:
        return isPrimeMillerRabin( int( n ) )

    debugPrint( 'primality testing of ' + str( int( n ) ) )
    return 1 if gmpy2.is_bpsw_prp( int( n ) ) else 0


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 0 ) ] )
def isCompositeOperator( n ):
    return 0 if n == 1 or isPrime( n ) else 1


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 0 ) ] )
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 ]


#******************************************************************************
#
#  getNextPrimeOperator
#
#******************************************************************************

@cachedFunction( 'next_prime' )
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( )
@argValidator( [ IntValidator( 0 ) ] )
def getNextPrimeOperator( n ):
    return getNextPrime( n, func=getNextPrimeCandidateForAny )


#******************************************************************************
#
#  getPreviousPrimeOperator
#
#******************************************************************************

@cachedFunction( 'previous_prime' )
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( )
@argValidator( [ IntValidator( 0 ) ] )
def getPreviousPrimeOperator( n ):
    return getPreviousPrime( n, func=getPreviousPrimeCandidateForAny )


#******************************************************************************
#
#  getNextPrimesOperator
#
#******************************************************************************

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

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

    return result


@twoArgFunctionEvaluator( )
@argValidator( [ IntValidator( 0 ), IntValidator( 0 ) ] )
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( )
@argValidator( [ IntValidator( 0 ), IntValidator( 0 ) ] )
def getPreviousPrimesOperator( n, k ):
    return getPreviousPrimes( n, k, func=getPreviousPrimeCandidateForAny )


#******************************************************************************
#
#  getNthPrimeOperator
#
#******************************************************************************

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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthPrimeOperator( n ):
    return getNthPrime( n )


#******************************************************************************
#
#  findPrimeOperator
#
#******************************************************************************

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

    if target < 3:
        return 1, 2

    if target == 3:
        return 2, 3

    if target <= 5:
        return 3, 5

    if target <= 7:
        return 4, 7

    if 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( )
@argValidator( [ IntValidator( 1 ) ] )
def findPrimeOperator( n ):
    return findPrime( n )[ 0 ]


#******************************************************************************
#
#  findQuadrupletPrimesOperator
#
#******************************************************************************

def findQuadrupletPrimes( arg ):
    n = int( 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( )
@argValidator( [ IntValidator( 1 ) ] )
def findQuadrupletPrimeOperator( n ):
    return findQuadrupletPrimes( n )[ 0 ]


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNextQuadrupletPrimeOperator( n ):
    return findQuadrupletPrimes( n )[ 1 ][ 0 ]


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNextQuadrupletPrimesOperator( n ):
    return findQuadrupletPrimes( n )[ 1 ]


#******************************************************************************
#
#  getNthQuadrupletPrimeOperator
#
#******************************************************************************

def getNthQuadrupletPrime( arg ):
    n = int( 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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthQuadrupletPrimeOperator( n ):
    return getNthQuadrupletPrime( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthQuadrupletPrimeListOperator( n ):
    p = getNthQuadrupletPrime( n )
    return [ p, fadd( p, 2 ), fadd( p, 6 ), fadd( p, 8 ) ]


#******************************************************************************
#
#  getNthIsolatedPrimeOperator
#
#******************************************************************************

@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthIsolatedPrimeOperator( arg ):
    n = int( 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


#******************************************************************************
#
#  getNthTwinPrimeOperator
#
#******************************************************************************

def getNthTwinPrime( arg ):
    n = int( 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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthTwinPrimeOperator( n ):
    return getNthTwinPrime( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthTwinPrimeListOperator( n ):
    p = getNthTwinPrime( n )
    return [ p, fadd( p, 2 ) ]


#******************************************************************************
#
#  getNthBalancedPrimeOperator
#
#  returns the first balanced prime
#
#******************************************************************************

def getNthBalancedPrime( arg ):
    n = int( arg )

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

    if n == 1:
        return 5

    if 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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthBalancedPrimeOperator( n ):
    return getNthBalancedPrime( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthBalancedPrimeListOperator( n ):
    q = getNthBalancedPrime( n )
    p = getPreviousPrime( q )
    r = getNextPrime( q )

    return [ p, q, r ]


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

def getNthDoubleBalancedPrimeElement( arg, first = False ):
    n = int( 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

    workingPrimes = [ 0 ] * numberOfPrimes
    workingPrimes[ center ] = p

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

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

        balanced = True

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

        if balanced:
            currentIndex += 1

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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthDoubleBalancedPrimeListOperator( n ):
    p = getNthDoubleBalancedPrimeElement( n, 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( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthDoubleBalancedPrimeOperator( n ):
    return getNthDoubleBalancedPrimeElement( n )


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

def getNthTripleBalancedPrimeElement( arg, first = False ):
    n = int( 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

    workingPrimes = [ 0 ] * numberOfPrimes
    workingPrimes[ center ] = p

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

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

        balanced = True

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

        if balanced:
            currentIndex += 1

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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthTripleBalancedPrimeListOperator( n ):
    p = getNthTripleBalancedPrimeElement( n, 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( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthTripleBalancedPrimeOperator( n ):
    return getNthTripleBalancedPrimeElement( n )


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

def getNthQuadrupleBalancedPrimeElement( arg, first = False ):
    n = int( 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

    workingPrimes = [ 0 ] * numberOfPrimes
    workingPrimes[ center ] = p

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

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

        balanced = True

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

        if balanced:
            currentIndex += 1

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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthQuadrupleBalancedPrimeListOperator( n ):
    p = getNthQuadrupleBalancedPrimeElement( n, 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( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthQuadrupleBalancedPrimeOperator( n ):
    return getNthQuadrupleBalancedPrimeElement( n )


#******************************************************************************
#
#  getNthSophiePrimeOperator
#
#******************************************************************************

def getNthSophiePrime( 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 >= 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( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthSophiePrimeOperator( n ):
    return getNthSophiePrime( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getSafePrimeOperator( n ):
    return fadd( fmul( getNthSophiePrime( n ), 2 ), 1 )


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

def getNthCousinPrime( arg ):
    n = int( 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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthCousinPrimeOperator( n ):
    return getNthCousinPrime( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthCousinPrimeListOperator( n ):
    p = getNthCousinPrime( n )
    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
#
#******************************************************************************

def getNthSexyPrime( arg ):
    n = int( 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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthSexyPrimeOperator( n ):
    return getNthSexyPrime( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthSexyPrimeListOperator( n ):
    p = getNthSexyPrime( n )
    return [ p, fadd( p, 6 ) ]


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

def getNthSexyTriplet( arg ):
    n = int( 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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthSexyTripletOperator( n ):
    return getNthSexyTriplet( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthSexyTripletListOperator( n ):
    p = getNthSexyTriplet( n )
    return [ p, fadd( p, 6 ), fadd( p, 12 ) ]


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

def getNthSexyQuadruplet( arg ):
    n = int( 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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthSexyQuadrupletOperator( n ):
    return getNthSexyQuadruplet( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthSexyQuadrupletListOperator( n ):
    p = getNthSexyQuadruplet( n )
    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
#
#******************************************************************************

def getNthOctyPrime( arg ):
    n = int( 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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthOctyPrimeOperator( n ):
    return getNthOctyPrime( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthOctyPrimeListOperator( arg ):
    p = getNthOctyPrime( arg )
    return [ p, fadd( p, 8 ) ]


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

def getNthTripletPrimeList( arg ):
    n = int( arg )

    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 ]


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthTripletPrimeListOperator( n ):
    return getNthTripletPrimeList( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthTripletPrimeOperator( arg ):
    return getNthTripletPrimeList( arg )[ 0 ]


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

def getNextQuintupletPrimeCandidate( p ):
    f = p % 30

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

    return p


#******************************************************************************
#
#  getNthQuintupletPrimeOperator
#
#*****************************************************************************

def getNthQuintupletPrime( arg ):
    n = int( arg )

    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 quintuplet 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 ) ):
            currentIndex += 1
        elif ( ( f == 7 ) and isPrime( p + 4 ) and isPrime( p + 6 ) and
               isPrime( p + 10 ) and isPrime( p + 12 ) ):
            currentIndex += 1

    return p


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthQuintupletPrimeOperator( n ):
    return getNthQuintupletPrime( n )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthQuintupletPrimeListOperator( n ):
    if n == 1:
        return [ 5, 7, 11, 13, 17 ]

    p = getNthQuintupletPrime( n )

    f = p % 10

    if f == 1:
        return [ p, fadd( p, 2 ), fadd( p, 6 ), fadd( p, 8 ), fadd( p, 12 ) ]

    if f == 7:
        return [ p, fadd( p, 4 ), fadd( p, 6 ), fadd( p, 10 ), fadd( p, 12 ) ]

    raise ValueError( 'internal error:  getNthQuintupletPrimeList is broken' )


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

def findQuintupletPrimes( arg ):
    n = int( 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 ) ):
            currentIndex += 1

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

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


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


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNextQuintupletPrimeOperator( n ):
    return findQuintupletPrimes( n )[ 1 ][ 0 ]


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNextQuintupletPrimesOperator( n ):
    return findQuintupletPrimes( n )[ 1 ]


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

def findSextupletPrimes( arg ):
    n = int( 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( )
@argValidator( [ IntValidator( 1 ) ] )
def findSextupletPrimeOperator( n ):
    return findSextupletPrimes( n )[ 0 ]


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNextSextupletPrimeOperator( n ):
    return findSextupletPrimes( n )[ 1 ][ 0 ]


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNextSextupletPrimesOperator( n ):
    return findSextupletPrimes( n )[ 1 ]


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

def findTripletPrimes( arg ):
    n = int( 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( )
@argValidator( [ IntValidator( 1 ) ] )
def findTripletPrimeOperator( n ):
    return findTripletPrimes( n )[ 0 ]


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNextTripletPrimeOperator( n ):
    return findTripletPrimes( n )[ 1 ][ 0 ]


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNextTripletPrimesOperator( n ):
    return findTripletPrimes( n )[ 1 ]


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

def findTwinPrimes( arg ):
    n = int( 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( )
@argValidator( [ IntValidator( 1 ) ] )
def findTwinPrimeOperator( n ):
    return findTwinPrimes( n )[ 0 ]


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNextTwinPrimeOperator( n ):
    return findTwinPrimes( n )[ 1 ][ 0 ]


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNextTwinPrimesOperator( n ):
    return findTwinPrimes( n )[ 1 ]


#******************************************************************************
#
#  getNthSextupletPrimeOperator
#
#******************************************************************************

def getNthSextupletPrime( arg ):
    n = int( arg )

    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( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthSextupletPrimeOperator( n ):
    return getNthSextupletPrime( n )


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


#******************************************************************************
#
#  getNthPrimeRangeOperator
#
#******************************************************************************

def getNthPrimeRange( arg1, arg2 ):
    n = int( arg1 )
    count = int( 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


@twoArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ), IntValidator( 0 ) ] )
def getNthPrimeRangeOperator( n, k ):
    return getNthPrimeRange( n, k )


#******************************************************************************
#
#  getNthSuperPrimeOperator
#
#******************************************************************************

@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ) ] )
def getNthSuperPrimeOperator( arg ):
    return getNthPrime( getNthPrime( arg ) )


#******************************************************************************
#
#  getNthPolyPrimeOperator
#
#******************************************************************************

@twoArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ), IntValidator( 1 ) ] )
def getNthPolyPrimeOperator( n, poly ):
    result = getNthPrime( n )

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

    return result


#******************************************************************************
#
#  getPrimesOperator
#
#******************************************************************************

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


@twoArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ), IntValidator( 1 ) ] )
def getPrimesOperator( n, k ):
    return RPNGenerator( getPrimes( n, k ) )


#******************************************************************************
#
#  getPrimeRangeOperator
#
#******************************************************************************

@twoArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ), IntValidator( 1 ) ] )
def getPrimeRangeOperator( start, end ):
    result = list( )

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

    return result


#******************************************************************************
#
#  getNthPrimorialOperator
#
#******************************************************************************

def getNthPrimorial( n ):
    if n == 0:
        return 1

    return fprod( getPrimes( 1, n ) )


@oneArgFunctionEvaluator( )
@argValidator( [ IntValidator( 0 ) ] )
def getNthPrimorialOperator( n ):
    return getNthPrimorial( 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


#******************************************************************************
#
#  isStrongPseudoprimeOperator
#
#******************************************************************************

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


@twoArgFunctionEvaluator( )
@argValidator( [ IntValidator( 1 ), IntValidator( 1 ) ] )
def isStrongPseudoprimeOperator( n, k ):
    return isStrongPseudoprime( n, k )


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

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

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