#!/usr/bin/env python

#******************************************************************************
#
#  rpnComputer.py
#
#  rpnChilada computing operators
#  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 struct

from mpmath import fadd, fdiv, floor, fmod, fmul, fsub, fsum, log, mpf, mp, mpmathify, power

from rpn.rpnGenerator import RPNGenerator
from rpn.rpnMeasurement import RPNMeasurement
from rpn.rpnSettings import setAccuracy
from rpn.rpnUtils import oneArgFunctionEvaluator, twoArgFunctionEvaluator, validateReal, validateRealInt

import rpn.rpnGlobals as g


#******************************************************************************
#
#  convertToSignedInt
#
#  two's compliment logic is in effect here
#
#******************************************************************************

def convertToSignedInt( n, k ):
    newPrecision = ( int( validateRealInt( k ) ) * 3 / 10 ) + 3

    if mp.dps < newPrecision:
        setAccuracy( newPrecision )

    value = fadd( validateRealInt( n ), ( power( 2, fsub( k, 1 ) ) ) )
    value = fmod( value, power( 2, k ) )
    value = fsub( value, ( power( 2, fsub( k, 1 ) ) ) )

    return value

@twoArgFunctionEvaluator( )
def convertToSignedIntOperator( n, k ):
    return convertToSignedInt( n, k )

@oneArgFunctionEvaluator( )
def convertToChar( n ):
    return convertToSignedInt( n, 8 )

@oneArgFunctionEvaluator( )
def convertToShort( n ):
    return convertToSignedInt( n, 16 )

@oneArgFunctionEvaluator( )
def convertToLong( n ):
    return convertToSignedInt( n, 32 )

@oneArgFunctionEvaluator( )
def convertToLongLong( n ):
    return convertToSignedInt( n, 64 )

@oneArgFunctionEvaluator( )
def convertToQuadLong( n ):
    return convertToSignedInt( n, 128 )


#******************************************************************************
#
#  getInvertedBits
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def getInvertedBits( n ):
    value = validateRealInt( n )

    # determine how many groups of bits we will be looking at
    if value == 0:
        groupings = 1
    else:
        groupings = int( fadd( floor( fdiv( ( log( value, 2 ) ), g.bitwiseGroupSize ) ), 1 ) )

    placeValue = mpmathify( 1 << g.bitwiseGroupSize )
    multiplier = mpmathify( 1 )
    remaining = value

    result = mpmathify( 0 )

    for _ in range( 0, groupings ):
        # Let's let Python do the actual inverting
        group = fmod( ~int( fmod( remaining, placeValue ) ), placeValue )

        result += fmul( group, multiplier )

        remaining = floor( fdiv( remaining, placeValue ) )
        multiplier = fmul( multiplier, placeValue )

    return result


#******************************************************************************
#
#  performBitwiseOperation
#
#  The operations are performed on groups of bits as specified by the variable
#  bitwiseGroupSize.  Although doing it this way isn't really necessary, it
#  does mean that under normal circumstances the regular Python bit operators
#  can be used.
#
#******************************************************************************

def performBitwiseOperation( i, j, operation ):
    value1 = validateRealInt( i )
    value2 = validateRealInt( j )

    # determine how many groups of bits we will be looking at
    if value1 == 0:
        groupings = 1
    else:
        groupings = int( fadd( floor( fdiv( ( log( value1, 2 ) ), g.bitwiseGroupSize ) ), 1 ) )

    if value2 == 0:
        groupings2 = 1
    else:
        groupings2 = int( fadd( floor( fdiv( ( log( value2, 2 ) ), g.bitwiseGroupSize ) ), 1 ) )

    if groupings2 > groupings:
        groupings = groupings2

    placeValue = mpmathify( 1 << g.bitwiseGroupSize )
    multiplier = mpmathify( 1 )
    remaining1 = value1
    remaining2 = value2

    result = mpmathify( 0 )

    for _ in range( 0, groupings ):
        mod1 = fmod( remaining1, placeValue )
        mod2 = fmod( remaining2, placeValue )

        result = fadd( fmul( operation( int( mod1 ), int( mod2 ) ), multiplier ), result )

        remaining1 = floor( fdiv( remaining1, placeValue ) )
        remaining2 = floor( fdiv( remaining2, placeValue ) )

        multiplier = fmul( multiplier, placeValue )

    return result

@twoArgFunctionEvaluator( )
def getBitwiseAnd( n, k ):
    return performBitwiseOperation( n, k, lambda x, y: x & y )

@twoArgFunctionEvaluator( )
def getBitwiseNand( n, k ):
    return getInvertedBits( performBitwiseOperation( n, k, lambda x, y: x & y ) )

@twoArgFunctionEvaluator( )
def getBitwiseNor( n, k ):
    return getInvertedBits( performBitwiseOperation( n, k, lambda x, y: x | y ) )

@twoArgFunctionEvaluator( )
def getBitwiseOr( n, k ):
    return performBitwiseOperation( n, k, lambda x, y: x | y )

@twoArgFunctionEvaluator( )
def getBitwiseXor( n, k ):
    return performBitwiseOperation( n, k, lambda x, y: x ^ y )

@twoArgFunctionEvaluator( )
def getBitwiseXnor( n, k ):
    return getInvertedBit( performBitwiseOperation( n, k, lambda x, y: x ^ y ) )

@twoArgFunctionEvaluator( )
def shiftLeft( n, k ):
    return fmul( n, 1 << int( k ) )

@twoArgFunctionEvaluator( )
def shiftRight( n, k ):
    return floor( fdiv( n, 1 << int( k ) ) )


#******************************************************************************
#
#  getBitCount
#
#******************************************************************************

def getBitCount( n ):
    n = validateRealInt( n )

    if validateRealInt( n ) < 0:
        raise ValueError( '\'bit_count\' requires a positive integer value' )

    result = 0

    if isinstance( n, RPNMeasurement ):
        value = n.value
    else:
        value = n

    while value:
        result += fmod( value, 2 )
        value = floor( fdiv( value, 2 ) )

    return result

@oneArgFunctionEvaluator( )
def getBitCountOperator( n ):
    return getBitCount( n )

@oneArgFunctionEvaluator( )
def getParity( n ):
    return fmod( getBitCount( n ), 2 )


#******************************************************************************
#
#  unpackInteger
#
#******************************************************************************

def unpackInteger( n, fields ):
    if isinstance( n, RPNGenerator ):
        return unpackInteger( list( n ), fields )
    elif isinstance( n, list ):
        return [ unpackInteger( i, fields ) for i in n ]

    if isinstance( fields, RPNGenerator ):
        return unpackInteger( n, list( fields ) )
    elif not isinstance( fields, list ):
        return unpackInteger( n, [ fields ] )

    value = validateRealInt( n )
    result = [ ]

    for field in reversed( fields ):
        size = int( field )
        result.insert( 0, value & ( 2 ** size - 1 ) )
        value >>= size

    return result


#******************************************************************************
#
#  packInteger
#
#******************************************************************************

def packInteger( values, fields ):
    if isinstance( values, RPNGenerator ):
        return packInteger( list( values ), fields )
    elif not isinstance( values, list ):
        return unpackInteger( [ values ], fields )

    if isinstance( fields, RPNGenerator ):
        return packInteger( values, list( fields ) )
    elif not isinstance( fields, list ):
        return unpackInteger( values, [ fields ] )

    if isinstance( values[ 0 ], list ):
        return [ unpackInteger( value, fields ) for value in values ]

    result = 0

    count = min( len( values ), len( fields ) )

    size = 0

    for i in range( count, 0, -1 ):
        field = int( fields[ i - 1 ] )
        value = int( values[ i - 1 ] ) & ( 2 ** field - 1 )
        result = fadd( result, fmul( value, power( 2, size ) ) )
        size += field

    return result


#******************************************************************************
#
#  interpretAsFloat
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def interpretAsFloat( n ):
    if ( validateRealInt( n ) < 0 ) or ( n >= 2 ** 32 - 1 ):
        raise ValueError( 'value out of range for a 32-bit float' )

    intValue = struct.pack( 'I', int( n ) )
    return mpf( struct.unpack( 'f', intValue )[ 0 ] )


#******************************************************************************
#
#  interpretAsDouble
#
#******************************************************************************

@oneArgFunctionEvaluator( )
def interpretAsDouble( n ):
    setAccuracy( 25 )

    if ( validateRealInt( n ) < 0 ) or ( n > 2 ** 64 - 1 ):
        raise ValueError( 'value out of range for a 64-bit float' )

    intValue = struct.pack( 'Q', int( n ) )
    return mpf( struct.unpack( 'd', intValue )[ 0 ] )


@oneArgFunctionEvaluator( )
def convertToDouble( n ):
    return fsum( b << 8 * i for i, b in enumerate( struct.pack( 'd', float( validateReal( n ) ) ) ) )

@oneArgFunctionEvaluator( )
def convertToFloat( n ):
    return fsum( b << 8 * i for i, b in enumerate( struct.pack( 'f', float( validateReal( n ) ) ) ) )

@twoArgFunctionEvaluator( )
def convertToUnsignedInt( n, k ):
    return fmod( validateRealInt( n ), power( 2, validateReal( k ) ) )

@oneArgFunctionEvaluator( )
def convertToUnsignedChar( n ):
    return fmod( validateRealInt( n ), power( 2, 8 ) )

@oneArgFunctionEvaluator( )
def convertToUnsignedShort( n ):
    return fmod( validateRealInt( n ), power( 2, 16 ) )

@oneArgFunctionEvaluator( )
def convertToUnsignedLong( n ):
    return fmod( validateRealInt( n ), power( 2, 32 ) )

@oneArgFunctionEvaluator( )
def convertToUnsignedLongLong( n ):
    return fmod( validateRealInt( n ), power( 2, 64 ) )

@oneArgFunctionEvaluator( )
def convertToUnsignedQuadLong( n ):
    return fmod( validateRealInt( n ), power( 2, 128 ) )

@twoArgFunctionEvaluator( )
def andOperands( n, k ):
    return 1 if ( n != 0 and k != 0 ) else 0

@oneArgFunctionEvaluator( )
def notOperand( n ):
    return 1 if n == 0 else 0

@twoArgFunctionEvaluator( )
def nandOperands( n, k ):
    return 0 if ( n != 0 and k != 0 ) else 1

@twoArgFunctionEvaluator( )
def norOperands( n, k ):
    return 0 if ( n != 0 or k != 0 ) else 1

@twoArgFunctionEvaluator( )
def orOperands( n, k ):
    return 1 if ( n != 0 or k != 0 ) else 0

@twoArgFunctionEvaluator( )
def xnorOperands( n, k ):
    return 1 if ( n != 0 ) == ( k != 0 ) else 0

@twoArgFunctionEvaluator( )
def xorOperands( n, k ):
    return 1 if ( n != 0 ) != ( k != 0 ) else 0

