//#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

//#include <sys/types.h>

#include "base/bignum.h"
#include "base/dblock.h"
#include "base/mem.h"
#include "base/logger.h"
#include "base/str.h"


//static U16 _endianTest = 0x1234;
//#define BIG_ENDIAN (*((U8 *)&_endianTest) == 0x12)

//todo: make bignum routines NULL tolerant

BND lowbitfilter[] = BND_LOWBITS;

#define ctohex(dig) ((dig >= '0' && dig <= '9') ? (dig - '0') : (dig - 'a' + 10))


void bignumFree(BigNum *num) {
	LOGDEBUGSPAM(stringJoin("bignumFree:", ptrToString(num)));
	if(num == NULL) {
		return;
	}
	dblockFree(num->dblock);
}



// expands the available space to able to hold length data
BigNum *bignumExpandOnly(BigNum *num, int length) {
	DataBlock *db;
	if(num->size >= length) {
		if(num->maxSize != 0) {
			return num;
		}
	}
	if(length > num->maxSize || num->maxSize == 0) {
		db = dblockExpand(num->dblock, length * sizeof(BND) + sizeof(BigNum));
		db->size = db->maxSize;
		num = (BigNum *) db->data;
		num->dblock = db;
		num->maxSize = (db->maxSize - sizeof(BigNum)) / sizeof(BND);
	}

	return num;
}


// expand bignum to hold length more digits (will not shrink or truncate existing bignums.
BigNum *bignumExpand(BigNum *num, int length) {
	DataBlock *db;
	if(num->size >= length && num->maxSize != 0) {
		return num;
	}
	if(length > num->maxSize || num->maxSize == 0) {
		db = dblockExpand(num->dblock, length * sizeof(BND) + sizeof(BigNum));
		db->size = db->maxSize;
		num = (BigNum *) db->data;
		num->dblock = db;
		num->maxSize = (db->maxSize - sizeof(BigNum)) / sizeof(BND);
	}

	//num = bignumExpandOnly(num, length);

	memset(num->data + num->size, 0, (length - num->size) * sizeof(BND));
	num->size = length;

	return num;
}

BigNum *bignumMake(int maxlen, int initlen) {
	DataBlock *db;
	BigNum *bn;
	if(maxlen < initlen) {
		maxlen = initlen;
	}
	db = dblockMake(0, 0, "bignum");
	bn = (BigNum *)db->data;
	bn->dblock = db;
	bn->maxSize = 0;
	bn->size = 0;
	bn = bignumExpand(bn, maxlen);
	bn->size = initlen;
	LOGDEBUGSPAM(stringJoin("bignumMake:", ptrToString(bn)));
	return bn;
}


/*
 * Copies a BigNum and returns the handle to the new one.
 */

BigNum *bignumCopy(BigNum *src)
{
	DataBlock *db = dblockCopy(src->dblock);
	LOGDEBUGSPAM(stringJoin("bignumCopy:", ptrToString(src)));
	src = (BigNum *)db->data;
	src->dblock = db;
	LOGDEBUGSPAM(stringJoin("- copy:", ptrToString(src)));
	return src;
}

//replace an existing bignum with another bignum
BigNum *bignumAssign(BigNum *dst, BigNum *src) {
	dst->size = 0;
	dst = bignumExpandOnly(dst, src->size);
	dst->size = src->size;
	memcpy(dst->data, src->data, src->size * sizeof(BND));

	return dst;
}





/*
 * bigDropZeros - deduct leading zeros from digit count
 */
//Doesn't resize/move the datablock for num. Returns num for convenience.
BigNum *bignumDropZeros(BigNum *num) {
	int i;

	for (i = num->size - 1; i >= 0; i--) {
		if (num->data[i] != 0) {
			break;
		}
	}
	num->size = i + 1;

	return num;
}


/*
 * bigFromInt - create a bigint from a normal C int
 */

BigNum *bignumFromInt(uint64 n) {
	BigNum *num = bignumMake(sizeof(n) / sizeof(BND), 0);

	while (n > 0) {
		num->data[num->size++] = (BND) (n & BND_MAX);
		n >>= BND_BITS;
	}

	//num->size = i;
	return num;
}


/*
 * bigToInt - extract the bottom 32 bits of bigint
 */

uint64 bignumToInt(BigNum *num) {
	unsigned int i;
	unsigned int n = num->size;
	uint64 total = 0;

	if(n * sizeof(BND) > sizeof(total)) {
		n = sizeof(total) / sizeof(BND);
	}

	for (i = 0; i < n; i++) {
		total |= ((uint64) num->data[i] << (i << BND_BITS_BITS));
	}
	return total;
}


/*
 * bigFromBuf - creates a bignum from a string of bytes in a buffer
 *
 * Note - it's assumed that the buffer is little-endian format, ie
 * least significant byte first.
 *
 * Also, len is in bytes, not words
 */

BigNum *bignumFromBuffer(uint8 *buf, int len) {
	int i;
	BigNum *big = bignumMake(0, (len + (sizeof(BND) - 1)) / sizeof(BND));

	//memcpy(big->data, buf, len);

	for(i = 0; i < len; i++) {
		big->data[i / sizeof(BND)] |= buf[i] << ((i % sizeof(BND)) << 3);
	}

	return big;
}


/*
 * bigToBuf - extracts the digits of a bignum into a buffer
 *            buffer is written into little-endian format
 */

int bignumToBuf(uint8 *buf, BigNum *big, int maxlength) {
	int i;
	if((int)(big->size * sizeof(BND)) < maxlength) {
		maxlength = big->size * sizeof(BND);
	}
	for(i = 0; i < maxlength; i++) {
		buf[i] = (uint8) (big->data[i / sizeof(BND)] >> ((i % sizeof(BND)) << 3));
	}
	return maxlength;
}




/*
 * bigFromHex - parse a hex string and assign to a bigint
 */

BigNum *bignumFromHex(char *hex){
	DataBlock *db;
	BigNum *big;
	int len = strlen(hex);
	//char *tmp = NULL;
	//char *hex1;
	//int nyb, dig;
	int i = 0;
	int dig;

	db = dblockMake((len + 1) / 2, (len + 1) / 2, "bignum");
	dig = db->size - 1;
	if (len & 1) {
		//len &= ~1;
		db->data[dig] = ctohex(tolower(hex[i]));
		i++;
		dig--;
	}

	for(;i < len; i += 2, dig--) {
		db->data[dig] = (ctohex(tolower(hex[i])) << 4) + ctohex(tolower(hex[i + 1]));
	}

	big = bignumFromBuffer(db->data, db->size);

	dblockFree(db);

	return big;
}


char *bignumToHex(BigNum *num) {
	char *buffer;
	DataBlock *db;
	int i;
	int length;

	if(num == NULL) {
		buffer = stringBlank();
		return buffer;
	}
	length = num->size * sizeof(BND);
	db = dblockMake(length, length, "bignumToHex");

	bignumToBuf(db->data, num, length);

	buffer = stringMake(2 * length);
	for(i = 0; i < length; i++) {
		buffer[i * 2] = intToHexChar(db->data[length - i - 1] >> 4);
		buffer[i * 2 + 1] = intToHexChar(db->data[length - i - 1]);
	}
	dblockFree(db);
	return buffer;
}


/*
 * bigAdd - add two bignums
 */

BigNum *bignumAdd(BigNum *n1, BigNum *n2) {
	int i;
	int len = ((n1->size > n2->size) ? n1->size : n2->size);
	//BigNum *src = bignumExpand(dblockCopy(n1), len + 1);
	BigNum *dst = bignumExpand(n1, len); //bignumExpand(dblockCopy(n1), len);
	BND2 sum = 0;
	for(i = 0; i < n2->size; i++) {
		dst->data[i] = (BND) (sum = (sum + dst->data[i]) + n2->data[i]);
		sum >>= BND_BITS;
	}
	for(; i < len && sum != 0; i++) {
		dst->data[i] = (BND) (sum = dst->data[i] + sum);
		sum >>= BND_BITS;
	}
	if(sum != 0) {
		dst = bignumExpand(dst, dst->size + 1);
		dst->data[i] = (BND) sum;
	}
	return dst;

}

BigNum *bignumAddKeep(BigNum *n1, BigNum *n2) {
	return bignumAdd(bignumCopy(n1), n2);
}

/*
 * bigSubtract - subtract one bigint from another
 *
 * Note: result is undefined if n1 < n2
 */

BigNum *bignumSubtract(BigNum *n1, BigNum *n2) {
	int i;
	int len = ((n1->size > n2->size) ? n1->size : n2->size);
	BigNum *dst = bignumExpand(n1, len);
	BND2S sum = 0;
	for(i = 0; i < n2->size; i++) {
//#ifdef BN_FORCE_SUB_2
//		dst->data[i] = (BND) (sum = (BND2) dst->data[i] - n2->data[i] + sum);
//		sum >>= BND_BITS;
//#else
		//sum += dst->data[i];
		//dst->data[i] = (BND) (sum -= n2->data[i]);
		dst->data[i] = (BND) (sum = (sum + dst->data[i]) - n2->data[i]);
		sum >>= BND_BITS;
//#endif
	}
	for(; i < len && sum != 0; i++) {
//#ifdef BN_FORCE_SUB_2
//		dst->data[i] = (BND) (sum = (BND2) dst->data[i] + sum);
//		sum >>= BND_BITS;
//#else
		dst->data[i] = (BND) (sum += dst->data[i]);
		sum >>= BND_BITS;
//#endif
	}
	if(sum) {
		LOGERROR("bignumSubtract: n1 < n2");
	}
	return dst;
}

BigNum *bignumSubtractKeep(BigNum *n1, BigNum *n2) {
	return bignumSubtract(bignumCopy(n1), n2);
}

//does a subtract but with n2 shifted left offset1 words
//
BigNum *bignumSubtractOffset(BigNum *n1, BigNum *n2, int offset1) {
	int i;
	int j;
	//int len = ((n1->size > n2->size) ? n1->size : n2->size);
	//BigNum *dst = bignumExpand(n1, len); //bignumExpand(dblockCopy(n1), len);
	BigNum *dst = n1;
	BND2S sum = 0;
	for(i = offset1, j = 0; j < n2->size && i < n1->size; i++, j++) {
		dst->data[i] = (BND) (sum = (sum + dst->data[i]) - n2->data[j]);
		sum >>= BND_BITS;
	}
	for(; i < n1->size && sum != 0; i++) {
		dst->data[i] = (BND) (sum += dst->data[i]);
		sum >>= BND_BITS;
	}
	if(sum) {
		LOGERROR("bignumSubtractOffset: n1 < (n2 << (wordsize * offset1))");
	}
	return dst;
}


/*
 * bigCarry - carry a value from a specified digit position
 *            you shouldn't need to call this
 */

BigNum *bignumCarry(BigNum *num, uint8 carry, int dignum) {
	int i;
	BND2 sum = carry;
	for(i = dignum; i < num->size && sum != 0; i++) {
		num->data[i] = (BND) (sum = num->data[i] + sum);
		sum >>= BND_BITS;
	}
	if(sum != 0) {
		bignumExpand(num, num->size + 1);
		num->data[i] = (BND) sum;
	}
	return num;

}


/*
 * bigDec - decrement a bignum
 */

BigNum *bignumDec(BigNum *num) {
	int i;
	BND2S sum = -1;
	for(i = 0; i < num->size && sum != 0; i++) {
		num->data[i] = (BND) (sum = num->data[i] + sum);
		sum >>= BND_BITS;
	}
	return num;
}


/*
 * bigIsZero - returns 1 if a bignum is zero
 */

int bignumIsZero(BigNum *num) {
	int i;
	for(i = 0; i < num->size; i ++) {
		if(num->data[i] != 0) {
			return 0;
		}
	}
	return 1;
}


/*
 * bigIsEqual - returns 1 if two bignums are equal
 */

int bignumIsEqual(BigNum *n1, BigNum *n2) {
	int i;
	bignumDropZeros(n1);
	bignumDropZeros(n2);
	if(n1->size != n2->size) {
		return 0;
	}
	for(i = n1->size - 1; i >=0; i--) {
		if(n1->data[i] != n2->data[i]) {
			return 0;
		}
	}
	return 1;
}


/*
 * bigCompare - returns -1, 0 or 1 according to whether n1 is less than,
 *              equal to or greater than n2, respectively
 */

int bignumCompare(BigNum *n1, BigNum *n2) {
	int i;
	bignumDropZeros(n1);
	bignumDropZeros(n2);
	if(n1->size > n2->size) {
		return 1;
	}
	if(n1->size < n2->size) {
		return -1;
	}
	for(i = n1->size - 1; i >=0; i--) {
		if(n1->data[i] > n2->data[i]) {
			return 1;
		}
		if(n1->data[i] < n2->data[i]) {
			return -1;
		}
	}
	return 0;
}


//does a compare but with n2 shifted left offset1 words
//note: assumes n2 has had it's leading zeros dropped
//note: offset1 must be 0 or greater.
int bignumCompareOffset(BigNum *n1, BigNum *n2, int offset1) {
	int i;
	int j;
	bignumDropZeros(n1);
	//bignumDropZeros(n2);
	if(n1->size - offset1 > n2->size) {
		return 1;
	}
	if(n1->size - offset1 < n2->size) {
		return -1;
	}
	for(i = n1->size - 1, j = n2->size - 1; i >= offset1 && j >= 0; i--, j--) {
		if(n1->data[i] > n2->data[j]) {
			return 1;
		}
		if(n1->data[i] < n2->data[j]) {
			return -1;
		}
	}
	return 0;
}

/*
 * This is a somewhat faster shift routine, that shifts a number byte-wise
 * the specified number of digits
 *
 * Note - the shifted number is assigned back to itself
 */
// num * 256 ^ nbytes
BigNum *bignumShiftLeftWords(BigNum *num, int nwords) {
	num = bignumExpand(num, num->size + nwords);
	memmove(num->data + nwords, num->data, (num->size - nwords) * sizeof(BND));
	memset(num->data, 0, nwords * sizeof(BND));
	//num->size += nwords;
	return num;
}


/*
 * This is a somewhat faster shift routine, that shifts a number byte-wise
 * the specified number of digits
 *
 * Note - the shifted number is assigned back to itself
 */

// num * 256 ^ -nbytes
BigNum *bignumShiftRightWords(BigNum *num, int nwords) {
	memmove(num->data, num->data + nwords, (num->size - nwords) * sizeof(BND));
	num->size -= nwords;
	return num;

}


/*
 * bigShiftLeft - shifts a bignum one bit to the left
 */
//num * 2
BigNum *bignumShiftLeft(BigNum *num) {
	int i;
	BND2 carry = 0;
	bignumDropZeros(num);
	for (i = 0; i < num->size; i++) {
		num->data[i] = (BND) (carry = ((BND2) num->data[i] << 1) | carry);
		carry >>= BND_BITS;
	}
	if(carry != 0) {
		num = bignumExpand(num, num->size + 1);
		num->data[i] = (BND) carry;
		//num->size++;
	}
	return num;
}


/*
 * bigShiftRight - shifts a bignum one bit to the right
 *
 * Note - the shifted number is assigned back to itself
 */
//num / 2
BigNum *bignumShiftRight(BigNum *num) {
	int i;
	BND carry = 0;
	BND v;
	bignumDropZeros(num);
	for (i = num->size - 1; i >=0; i--) {
		v = num->data[i]; // & 1; // & 1 needed. let the left shift, assign to BND variable truncate it
		num->data[i] = (num->data[i] >> 1) | carry;
		carry = v << BND_BITS1;
	}
	bignumDropZeros(num);
	return num;
}


BigNum *bignumShiftRightX(BigNum *num, int shift) {
	int i;
	BND carry = 0;
	BND v;
	int shiftx;
	//int shiftfilter;
	if(shift >= BND_BITS) {
		num = bignumShiftRightWords(num, shift >> BND_BITS_BITS);
	}
	shift &= BND_BITS1;
	if(shift == 0) {
		return num;
	}
	shiftx = BND_BITS - shift;
	//shiftfilter = lowbitfilter[shift];
	
	bignumDropZeros(num);
	for (i = num->size - 1; i >=0; i--) {
		v = num->data[i]; // & shiftfilter; //let the shift do the truncating
		num->data[i] = (v >> shift) | carry;
		carry = v << shiftx;
	}
	bignumDropZeros(num);
	return num;
}

/*
 * bigBottomBit - tests the least significant bit of a bignum, retuns 1 if set or 0 if not
 */

int bignumBottomBit(BigNum *num) {
	return num->size ? (num->data[0] & 1) : 0;
}


/*
 * bigMod - Computes the modulus of two numbers
 */

BigNum *bignumMod(BigNum *num, BigNum *modulus, BigNum **scratch) {
	int bitshift;
	int modindex;
	BND bitmod;
	//int shift = 0;
	int check = 0;
	int i;
	//BigNum *mods[BND_BITS];
	BigNum **mods;
	//BigNum *modorig = modulus;
	bignumDropZeros(num);
	bignumDropZeros(modulus);
	//num = bignumCopy(num);
	if(bignumCompare(num, modulus) < 0) {
		return num;
	}
	if(bignumIsZero(modulus)) {
		LOGERROR("bignum: Mod by zero attempted.");
		return num;
	}
	//modulus = bignumCopy(modulus);
	//modulus = *scratch = bignumAssign(*scratch, modulus);

	if(scratch == NULL) {
		mods = memAlloc(BND_BITS * sizeof(BigNum *), "ModScratch", NULL);//ximalloc(BND_BITS * sizeof(BigNum *));
		mods[0] = bignumCopy(modulus);
		for(i = 1; i < BND_BITS; i++) {
			mods[i] = bignumShiftLeft(bignumCopy(mods[i - 1]));
			bignumDropZeros(mods[i]);
		}
	} else {
		mods = scratch;
	}

	bitshift = (num->size - modulus->size) + 1;
	//modulus = bignumShiftLeftWords(modulus, bitshift);
	modindex = bitshift + modulus->size - 1; //modulus->size - 1;
	//adjust bitmod to the highest bit in modulus
	for(bitmod = BND_MAX_BIT; !(modulus->data[modulus->size - 1] & bitmod); bitmod >>= 1) {
		if(bitmod == 0) {
			LOGERROR("error in bignumMod");			
			return num;
		}
	}
	
	bitshift <<= BND_BITS_BITS;
	for(; bitshift >= 0; bitshift--) {
		//if bit bitmod is set in num->data[modindex} we might have a possible divide
		if(check || ((modindex < num->size) && (num->data[modindex] & bitmod) != 0)) {// || (bitshift == 0)
			if(bignumCompareOffset(num, mods[bitshift & BND_BITS_MASK], bitshift >> BND_BITS_BITS) >= 0) {
				num = bignumSubtractOffset(num, mods[bitshift & BND_BITS_MASK], bitshift >> BND_BITS_BITS);
				if((num->size > modindex + 1) || 
						((modindex < num->size) && (num->data[modindex] >= bitmod))) {
					check = 1;
				} else {
					check = 0;
				}
			} else {
				check = 1;
			}

		} 
		if(bitmod & 1) {
			bitmod = BND_MAX_BIT;
			modindex--;
		} else {
			bitmod >>= 1;
		}
	}

	// //todo: remove once certain the above doesn't have bugs
	//while(bignumCompare(num, modorig) >= 0) { //in case of bug
	//	num = bignumSubtract(num, modorig);
	//}
	if(scratch == NULL) {
		for(i = 0; i < BND_BITS; i++) {
			bignumFree(mods[i]);
		}
		memFree(mods);//xifree(mods);
	}

	bignumDropZeros(num);

	//bignumFree(modulus);

	return num;

}

BigNum *bignumModKeep(BigNum *num, BigNum *modulus, BigNum **scratch) {
	return bignumMod(bignumCopy(num), modulus, scratch);
}

/*
 * bigMod - Divides two numbers
 */
//rounds down
BigNum *bignumDiv(BigNum *num, BigNum *dividend) {//bignum *quotient, bignum *dividend, bignum *divisor)
	BigNum *result;
	int bitshift;
	int byteindex;
	BND bitbyte;
	bignumDropZeros(num);
	bignumDropZeros(dividend);
	//num = bignumCopy(num);
	if(bignumCompare(num, dividend) < 0) {
		num->size = 0;
		return num;
	}
	if(bignumIsZero(dividend)) {
		LOGERROR("bignum: Divide by zero attempted.");
		return num;
	}

	dividend = bignumCopy(dividend);
	byteindex = bitshift = (num->size - dividend->size) + 1;

	result = bignumMake(num->maxSize, byteindex);

	dividend = bignumShiftLeftWords(dividend, bitshift);
	bitshift <<= BND_BITS_BITS;

	bitbyte = 0x01;
	
	for(; bitshift >= 0; bitshift--) {
		if(bignumCompare(num, dividend) >= 0) {
			num = bignumSubtract(num, dividend);
			result->data[byteindex] |= bitbyte;
		}
		dividend = bignumShiftRight(dividend);
		if(bitbyte & 1) {
			bitbyte = BND_MAX_BIT;
			byteindex--;
		} else {
			bitbyte >>= 1;
		}
	}

	bignumFree(num);
	bignumFree(dividend);

	return result;

}


BigNum *bignumDivKeep(BigNum *num, BigNum *dividend) {
	return bignumDiv(bignumCopy(num), dividend);
}

/*
 * bigMul - multiply two bigints
 */

BigNum *bignumMulKeep(BigNum *result, BigNum *num1, BigNum *num2) {//bignum *prod, bignum *n1, bignum *n2)
	int i, j, k;
	BND2 carry;
	//BigNum *result;
	BigNum *tmp;
#ifdef USE_PTR
#ifdef BN_FORCE_MUL_2
	BND2 ival;
#else
	BND ival;
#endif
	BND *iptr, *jptr, *kptr, *ijptr;
#endif

	bignumDropZeros(num1);
	bignumDropZeros(num2);

	result->size = 0;
	if(bignumIsZero(num1) || bignumIsZero(num2)) {
		//return bignumMake(0, 0);
		return result;
	}
	if(num1->size > num2->size) {
		tmp = num1;
		num1 = num2;
		num2 = tmp;
	}
	//result = bignumMake(0, num1->size + num2->size);
	result = bignumExpand(result, num1->size + num2->size);

#ifdef USE_PTR
	for(i = 0, iptr = num1->data; i < num1->size; i++, iptr++) {
		ival = *iptr;
		ijptr = result->data + i;
		for(j = 0, jptr = num2->data; j < num2->size; j++, jptr++, ijptr++) {			
			carry = ival * *jptr;
			for(k = i + j, kptr = ijptr; carry !=0; k++, kptr++) {
				*kptr = (BND) (carry += *kptr);
				carry >>= BND_BITS;
			}
		}
	}
#else
	for(i = 0; i < num1->size; i++) {
		for(j = 0; j < num2->size; j++) {
#ifdef BN_FORCE_MUL_2
			carry = (BND2) num1->data[i] * num2->data[j];
#else
			carry = num1->data[i] * num2->data[j];
#endif
			for(k = i + j; carry !=0; k++) {
				result->data[k] = (BND) (carry += result->data[k]);
				carry >>= BND_BITS;
			}
		}
	}
#endif
	return result;

}

BigNum *bignumMul(BigNum *result, BigNum *num1, BigNum *num2) {
	result = bignumMulKeep(result, num1, num2);
	bignumFree(num1);
	return result;
}


/*
 * bigSqr - compute the square of a bigint
 *
 * Note - squared num is assigned back to itself
 */

BigNum *bignumSqrKeep(BigNum *result, BigNum *num) {
	return bignumMulKeep(result, num, num);
}

BigNum *bignumSqr(BigNum *result, BigNum *num) {
	result = bignumSqrKeep(result, num);
	bignumFree(num);
	return result;
}

/*
 * bigExp - raises one bigint to the power of another
 */

BigNum *bignumExpKeep(BigNum *n, BigNum *y) {
	BigNum *power = bignumCopy(y);
	BigNum *multiplier = bignumCopy(n);
	BigNum *result = bignumFromInt(1);
	BigNum *tmp = bignumMake(0, 0);
	BigNum *tmp2;

	while(!bignumIsZero(power)) {
		if(bignumBottomBit(power)) {
			tmp2 = result;
			result = bignumMulKeep(tmp, result, multiplier);
			tmp = tmp2;
		}
		tmp2 = multiplier;
		multiplier = bignumSqrKeep(tmp, multiplier);
		tmp = tmp2;
		power = bignumShiftRight(power);
	}
	bignumFree(multiplier);
	bignumFree(power);

	return result;
}


/*
 * bigExpMod - raises one bigint to the power of another bigint,
 *             modulus another bigint
 */

BigNum *bignumExpModKeep(BigNum *n, BigNum *y, BigNum *modulus) {
//	return bignumMod(bignumExpKeep(n, y), modulus);

	int powerword = 0;
	int powerbit = 1;
	//BigNum *power = bignumCopy(y);
	BigNum *multiplier = bignumCopy(n);
	BigNum *result = bignumFromInt(1);
	BigNum *tmp = bignumMake(0, 0);
	BigNum *tmp2; //temporary holding when swapping between tmp and other bignums

	int i;
	BigNum *mods[BND_BITS];

	mods[0] = bignumCopy(modulus);
	for(i = 1; i < BND_BITS; i++) {
		mods[i] = bignumShiftLeft(bignumCopy(mods[i - 1]));
		bignumDropZeros(mods[i]);
	}

	while(powerword < y->size) {
		if(y->data[powerword] & powerbit) {
			tmp2 = result;
			result = bignumMulKeep(tmp, result, multiplier);
			tmp = tmp2;
			result = bignumMod(result, modulus, mods);//&tmp);
		}
		tmp2 = multiplier;
		multiplier = bignumSqrKeep(tmp ,multiplier);
		tmp = tmp2;
		multiplier = bignumMod(multiplier, modulus, mods);//&tmp);
		//power = bignumShiftRight(power);
		if(powerbit == BND_MAX_BIT) {
			powerword++;
			powerbit = 1;
		} else {
			powerbit <<= 1;
		}
		//if(bignumIsZero(result)) {
		//	result = bignumShiftRight(result);
		//}
	}
	bignumFree(tmp);
	bignumFree(multiplier);
	//bignumFree(power);
	for(i = 0; i < BND_BITS; i++) {
		bignumFree(mods[i]);
	}

	return result;

}


/*
 * bigAvg - calculates the arithmetic mean of two bignums
 */
//(n1 + n2) / 2 (rounds down)
BigNum *bignumAvgKeep(BigNum *n1, BigNum *n2) {
	return bignumShiftRight(bignumAddKeep(n1, n2));
}


/*
 * bigNumBits - calculates the number of bits in a bignum
 */

int bignumNumBits(BigNum *n) {
	int i;
	BND b;
	bignumDropZeros(n);
	i = n->size;
	if (i == 0) {
		return i;
	}
	b = n->data[i - 1];
	i--;
	i <<= BND_BITS_BITS;
	while(b != 0) {
		b >>= 1;
		i++;
	}
	return i;

}



/*
 * bigSqrt - calculates a bignum's square root
 *
 * Stores in result the integer whose square is closest to n
 * Will round up or down to find the closest result
 */

BigNum *bignumSqrt(BigNum *n) {
	int i;
	BigNum *low;
	BigNum *mid = NULL;
	BigNum *high;
	BigNum *sqr;

	bignumDropZeros(n);

	low = bignumFromInt(0);
	if(n->size == 0) {
		return low;
	}
	high = bignumMake(0, n->size / 2 + 1);
	high->data[high->size - 1] = 0x80;
	sqr = bignumMake(0,0);
	for(i = high->size << 3; i >= 0; i--) {
		mid = bignumAvgKeep(high, low);
		sqr = bignumSqrKeep(sqr, mid);
		switch (bignumCompare(n , sqr)) {
		case 0:
			bignumFree(low);
			bignumFree(high);
			bignumFree(sqr);
			return mid;
		case 1:
			bignumFree(low);
			low = mid;
		case -1:
			bignumFree(high);
			high = mid;
		}
		//bignumFree(sqr);
	}

	bignumFree(low);
	bignumFree(high);
	bignumFree(sqr);

	return mid;

}





//copmute  num^-1
// (num * result) mod modulus == 1
BigNum *bignumModInverseKeep(BigNum *num, BigNum *modulus) {
#define isEVEN(x) ((x->data[0] & 1) == 0)
#define isODD(x) ((x->data[0] & 1) != 0)
#define SWAP(x, y, t)  {t = x; x = y; y = t;}
	BigNum *tmp;

	BigNum *u;
	BigNum *v;
	BigNum *u1;
	BigNum *u2;
	BigNum *u3;
	BigNum *t1;
	BigNum *t2;
	BigNum *t3;

	if(bignumIsZero(num) || bignumIsZero(modulus)){
		return bignumFromInt(0);
	}
	tmp = bignumModKeep(modulus, num, NULL);
	if(bignumIsZero(tmp)){
		return tmp;
	}
	u = bignumModKeep(num, modulus, NULL);
	v = bignumCopy(modulus);
	u1 = bignumFromInt(1);
	u2 = bignumFromInt(0);
	u3 = bignumCopy(u);
	t1 = bignumCopy(v);
	t2 = bignumCopy(u);
	t3 = bignumCopy(v);

	do {
		do {
			if(isEVEN(u3)) {
				if(isODD(u1) || isODD(u2)) {
					u1 = bignumAdd(u1, v);
					u2 = bignumAdd(u2, u);
				}
				u1 = bignumShiftRight(u1);
				u2 = bignumShiftRight(u2);
				u3 = bignumShiftRight(u3);
			}
			if(isEVEN(t3) || bignumCompare(u3, t3) < 0) {
				SWAP(u1, t1, tmp);
				SWAP(u2, t2, tmp);
				SWAP(u3, t3, tmp);
			}
		
		} while(isEVEN(u3));
		while(bignumCompare(u1, t1) < 0 || bignumCompare(u2, t2) < 0) {
			u1 = bignumAdd(u1, v);
			u2 = bignumAdd(u2, u);
		}
	} while(bignumCompare(t3, 0) > 0);
	while(bignumCompare(u1, v) >= 0 && bignumCompare(u2, u) >= 0) {
		u1 = bignumSubtract(u1, v);
		u2 = bignumSubtract(u2, u);
	}

	tmp = bignumSubtract(u, u2);
	bignumFree(v);
	bignumFree(u1);
	bignumFree(u2);
	bignumFree(u3);
	bignumFree(t1);
	bignumFree(t2);
	bignumFree(t3);

	return tmp;
}

