/*
 * Copyright (c) 2010 Jiri Svoboda
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @file Big integers.
 *
 * Sysel type @c int should accomodate large numbers. This implementation
 * is limited by the available memory and range of the @c size_t type used
 * to index digits.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "debug.h"
#include "mytypes.h"

#include "bigint.h"

static void bigint_sign_comb(bool_t srf_a, bigint_t *a, bool_t srf_b,
    bigint_t *b, bigint_t *dest);
static void bigint_add_abs(bigint_t *a, bigint_t *b, bigint_t *dest);
static void bigint_sub_abs(bigint_t *a, bigint_t *b, bigint_t *dest);
static void bigint_shift_mul_dig(bigint_t *a, bigint_word_t b, size_t shift,
    bigint_t *dest);

static void bigint_alloc(bigint_t *bigint, size_t length);
static void bigint_refine_len(bigint_t *bigint);

/** Initialize bigint with value from small integer.
 *
 * Initializes a bigint structure with the provided small value.
 *
 * @param value		Initial value (small int).
 */
void bigint_init(bigint_t *bigint, int value)
{
	size_t length;
	size_t idx;
	int t;

#ifdef DEBUG_BIGINT_TRACE
	printf("Initialize bigint with int value %d.\n", value);
#endif

	if (value < 0) {
		bigint->negative = b_true;
		value = -value;
	} else {
		bigint->negative = b_false;
	}

	/* Determine length. */
	length = 0;
	t = value;
	while (t > 0) {
		length += 1;
		t = t / BIGINT_BASE;
	}

	/* Allocate digit array. */
	bigint_alloc(bigint, length);

	/* Compute digits. */
	t = value;
	for (idx = 0; idx < length; ++idx) {
		bigint->digit[idx] = t % BIGINT_BASE;
		t = t / BIGINT_BASE;
	}
}

/** Shallow copy of integer.
 *
 * @param src		Source.
 * @param dest		Destination.
 */
void bigint_shallow_copy(bigint_t *src, bigint_t *dest)
{
#ifdef DEBUG_BIGINT_TRACE
	printf("Shallow copy of bigint.\n");
#endif
	dest->negative = src->negative;
	dest->digit = src->digit;
	dest->length = src->length;
}
/** Clone big integer.
 *
 * @param src		Source.
 * @param dest		Destination.
 */
void bigint_clone(bigint_t *src, bigint_t *dest)
{
	size_t idx;

#ifdef DEBUG_BIGINT_TRACE
	printf("Clone bigint.\n");
#endif
	/* Copy sign. */
	dest->negative = src->negative;

	/* Allocate dest digit array. */
	bigint_alloc(dest, src->length);

	/* Copy digits. */
	for (idx = 0; idx < dest->length; ++idx)
		dest->digit[idx] = src->digit[idx];
}

/** Compute big integer with reversed sign.
 *
 * @param src		Source.
 * @param dest		Destination.
 */
void bigint_reverse_sign(bigint_t *src, bigint_t *dest)
{
	size_t idx;

#ifdef DEBUG_BIGINT_TRACE
	printf("Reverse-sign copy of bigint.\n");
#endif
	/* Copy reversed sign. */
	dest->negative = !src->negative;

	/* Allocate dest digit array. */
	bigint_alloc(dest, src->length);

	/* Copy digits. */
	for (idx = 0; idx < dest->length; ++idx)
		dest->digit[idx] = src->digit[idx];
}

/** Destroy big integer.
 *
 * Any bigint that is initialized via bigint_init() or any other bigint
 * function that constructs a new bigint value should be destroyed with
 * this function to free memory associated with the bigint. It should
 * also be used to destroy a bigint before it is reused.
 *
 * @param bigint	The bigint to destroy.
 */
void bigint_destroy(bigint_t *bigint)
{
#ifdef DEBUG_BIGINT_TRACE
	printf("Destroy bigint.\n");
#endif
	bigint->negative = b_false;

	bigint->length = 0;

	free(bigint->digit);
	bigint->digit = NULL;
}

/** Get value of big integer.
 *
 * Allows to obtain the value of big integer, provided that it fits
 * into a small integer.
 *
 * @param bigint	Bigint to obtain value from.
 * @param dval		Place to store value.
 * @return		EOK on success, ELIMIT if bigint is too big to fit
 *			to @a dval.
 */
errno_t bigint_get_value_int(bigint_t *bigint, int *dval)
{
	bigint_t vval, diff;
	size_t idx;
	int val;
	bool_t zf;

#ifdef DEBUG_BIGINT_TRACE
	printf("Get int value of bigint.\n");
#endif
	val = 0;
	for (idx = 0; idx < bigint->length; ++idx) {
		val = val * BIGINT_BASE + bigint->digit[idx];
	}

	if (bigint->negative)
		val = -val;

	/* If the value did not fit @c val now contains garbage. Verify. */
	bigint_init(&vval, val);

	bigint_sub(bigint, &vval, &diff);
	zf = bigint_is_zero(&diff);

	bigint_destroy(&vval);
	bigint_destroy(&diff);

	/* If the difference is not zero, the verification failed. */
	if (zf != b_true)
		return EINVAL;

	*dval = val;
	return EOK;
}

/** Determine if bigint is zero.
 *
 * @param bigint	Big integer.
 * @return		b_true if @a bigint is zero, b_false otherwise.
 */
bool_t bigint_is_zero(bigint_t *bigint)
{
#ifdef DEBUG_BIGINT_TRACE
	printf("Determine if bigint is zero.\n");
#endif
	return (bigint->length == 0);
}

/** Determine if bigint is negative.
 *
 * @param bigint	Big integer.
 * @return		b_true if @a bigint is negative, b_false otherwise.
 */
bool_t bigint_is_negative(bigint_t *bigint)
{
#ifdef DEBUG_BIGINT_TRACE
	printf("Determine if bigint is negative\n");
#endif
	/* Verify that we did not accidentaly introduce a negative zero. */
	assert(bigint->negative == b_false || bigint->length > 0);

	return bigint->negative;
}

/** Divide bigint by (unsigned) digit.
 *
 * @param a		Bigint dividend.
 * @param b		Divisor digit.
 * @param quot		Output bigint quotient.
 * @param rem		Output remainder digit.
 */
void bigint_div_digit(bigint_t *a, bigint_word_t b, bigint_t *quot,
    bigint_word_t *rem)
{
	size_t lbound;
	size_t idx;
	bigint_dword_t da, db;
	bigint_dword_t q, r;

#ifdef DEBUG_BIGINT_TRACE
	printf("Divide bigint by digit.\n");
#endif
	lbound = a->length;
	bigint_alloc(quot, lbound);

	quot->negative = a->negative;

	r = 0;
	idx = lbound;
	while (idx > 0) {
		--idx;

		da = a->digit[idx] + r * BIGINT_BASE;
		db = b;

		q = da / db;
		r = da % db;

		quot->digit[idx] = q;
	}

	bigint_refine_len(quot);
	*rem = r;
}

/** Add two big integers.
 *
 * The big integers @a a and @a b are added and the result is stored in
 * @a dest.
 *
 * @param a		First addend.
 * @param b		Second addend.
 * @param dest		Destination bigint.
 */
void bigint_add(bigint_t *a, bigint_t *b, bigint_t *dest)
{
#ifdef DEBUG_BIGINT_TRACE
	printf("Add bigints.\n");
#endif
	bigint_sign_comb(b_false, a, b_false, b, dest);
}

/** Subtract two big integers.
 *
 * The big integers @a a and @a b are subtracted and the result is stored in
 * @a dest.
 *
 * @param a		Minuend.
 * @param b		Subtrahend.
 * @param dest		Destination bigint.
 */
void bigint_sub(bigint_t *a, bigint_t *b, bigint_t *dest)
{
#ifdef DEBUG_BIGINT_TRACE
	printf("Subtract bigints.\n");
#endif
	bigint_sign_comb(b_false, a, b_true, b, dest);
}

/** Multiply two big integers.
 *
 * The big integers @a a and @a b are multiplied and the result is stored in
 * @a dest.
 *
 * @param a		First factor.
 * @param b		Second factor.
 * @param dest		Destination bigint.
 */
void bigint_mul(bigint_t *a, bigint_t *b, bigint_t *dest)
{
	size_t idx;
	bigint_t dprod;
	bigint_t sum;
	bigint_t tmp;

#ifdef DEBUG_BIGINT_TRACE
	printf("Multiply bigints.\n");
#endif
	bigint_init(&sum, 0);
	for (idx = 0; idx < b->length; ++idx) {
		bigint_shift_mul_dig(a, b->digit[idx], idx, &dprod);
		bigint_add(&dprod, &sum, &tmp);
		bigint_destroy(&dprod);

		bigint_destroy(&sum);
		bigint_shallow_copy(&tmp, &sum);
	}

	if (b->negative)
		sum.negative = !sum.negative;

	bigint_shallow_copy(&sum, dest);
}

/** Convert bigint to string.
 *
 * @param bigint	Bigint to convert.
 * @param dptr		Place to store pointer to new string.
 */
void bigint_get_as_string(bigint_t *bigint, char **dptr)
{
	static const char digits[] = { '0', '1', '2', '3', '4', '5', '6',
		'7', '8', '9' };

	bigint_t val, tmp;
	bigint_word_t rem;
	size_t nchars;
	char *str;
	size_t idx;

#ifdef DEBUG_BIGINT_TRACE
	printf("Convert bigint to string.\n");
#endif
	static_assert(BIGINT_BASE >= 10);

	/* Compute number of characters. */
	nchars = 0;

	if (bigint_is_zero(bigint) || bigint->negative)
		nchars += 1; /* '0' or '-' */

	bigint_clone(bigint, &val);
	while (bigint_is_zero(&val) != b_true) {
		bigint_div_digit(&val, 10, &tmp, &rem);
		bigint_destroy(&val);
		bigint_shallow_copy(&tmp, &val);

		nchars += 1;
	}
	bigint_destroy(&val);

	/* Store characters to array. */

	str = malloc(nchars * sizeof(char) + 1);
	if (str == NULL) {
		printf("Memory allocation failed.\n");
		exit(1);
	}

	if (bigint_is_zero(bigint)) {
		str[0] = '0';
	} else if (bigint->negative) {
		str[0] = '-';
	}

	idx = 1;
	bigint_clone(bigint, &val);
	while (bigint_is_zero(&val) != b_true) {
		bigint_div_digit(&val, 10, &tmp, &rem);
		bigint_destroy(&val);
		bigint_shallow_copy(&tmp, &val);

		str[nchars - idx] = digits[(int) rem];
		++idx;
	}

	bigint_destroy(&val);
	str[nchars] = '\0';
	*dptr = str;
}

/** Print bigint to standard output.
 *
 * @param bigint	Bigint to print.
 */
void bigint_print(bigint_t *bigint)
{
	char *str;

#ifdef DEBUG_BIGINT_TRACE
	printf("Print bigint.\n");
#endif
	bigint_get_as_string(bigint, &str);
	printf("%s", str);
	free(str);
}

/** Compute sign combination of two big integers.
 *
 * Of the big integers @a a and @a b each is optionally sign-reversed and then
 * they are added and the result is stored in @a dest.
 *
 * @param srf_a		First sign reversal flag.
 * @param a		First bigint.
 * @param srf_b		Second sign reversal flag.
 * @param b		Second bigint.
 * @param dest		Destination bigint.
 */
static void bigint_sign_comb(bool_t srf_a, bigint_t *a, bool_t srf_b,
    bigint_t *b, bigint_t *dest)
{
	bool_t neg_a, neg_b;

#ifdef DEBUG_BIGINT_TRACE
	printf("Signed combination of two bigints.\n");
#endif
	/* Compute resulting signs of combination elements. */
	neg_a = (srf_a != a->negative);
	neg_b = (srf_b != b->negative);

	if (neg_a == neg_b) {
		bigint_add_abs(a, b, dest);
		dest->negative = neg_a;
	} else {
		bigint_sub_abs(a, b, dest);
		dest->negative = (neg_a != dest->negative);
	}
}

/** Add absolute values of two big integers.
 *
 * The absolute values of big integers @a a and @a b are added and the result
 * is stored in @a dest.
 *
 * @param a		First addend.
 * @param b		Second addend.
 * @param dest		Destination bigint.
 */
static void bigint_add_abs(bigint_t *a, bigint_t *b, bigint_t *dest)
{
	size_t lbound;
	size_t idx;
	bigint_dword_t da, db;
	bigint_dword_t tmp;
	bigint_word_t res, carry;

#ifdef DEBUG_BIGINT_TRACE
	printf("Add absolute values of bigints.\n");
#endif
	/* max(a->length, b->length) + 1 */
	lbound = (a->length > b->length ? a->length : b->length) + 1;
	dest->negative = b_false;

	bigint_alloc(dest, lbound);
	carry = 0;

	for (idx = 0; idx < lbound; ++idx) {
		da = idx < a->length ? a->digit[idx] : 0;
		db = idx < b->length ? b->digit[idx] : 0;

		tmp = da + db + (bigint_word_t) carry;

		carry = (bigint_word_t) (tmp / BIGINT_BASE);
		res = (bigint_word_t) (tmp % BIGINT_BASE);

		dest->digit[idx] = res;
	}

	/* If our lbound is correct, carry must be zero. */
	assert(carry == 0);

	bigint_refine_len(dest);
}

/** Subtract absolute values of two big integers.
 *
 * The absolute values of big integers @a a and @a b are subtracted and the
 * result is stored in @a dest.
 *
 * @param a		Minuend.
 * @param b		Subtrahend.
 * @param dest		Destination bigint.
 */
static void bigint_sub_abs(bigint_t *a, bigint_t *b, bigint_t *dest)
{
	size_t lbound;
	size_t idx;
	bigint_dword_t da, db;
	bigint_dword_t tmp;
	bigint_word_t res, borrow;

#ifdef DEBUG_BIGINT_TRACE
	printf("Subtract absolute values of bigints.\n");
#endif
	/* max(a->length, b->length) */
	lbound = a->length > b->length ? a->length : b->length;

	bigint_alloc(dest, lbound);
	borrow = 0;

	for (idx = 0; idx < lbound; ++idx) {
		da = idx < a->length ? a->digit[idx] : 0;
		db = idx < b->length ? b->digit[idx] : 0;

		if (da > db + borrow) {
			tmp = da - db - borrow;
			borrow = 0;
		} else {
			tmp = da + BIGINT_BASE - db - borrow;
			borrow = 1;
		}

		res = (bigint_word_t) tmp;
		dest->digit[idx] = res;
	}

	if (borrow != 0) {
		/* We subtracted the greater number from the smaller. */
		dest->negative = b_true;

		/*
		 * Now we must complement the number to get the correct
		 * absolute value. We do this by subtracting from 10..0
		 * (0 repeated lbound-times).
		 */
		borrow = 0;

		for (idx = 0; idx < lbound; ++idx) {
			da = 0;
			db = dest->digit[idx];

			if (da > db + borrow) {
				tmp = da - db - borrow;
				borrow = 0;
			} else {
				tmp = da + BIGINT_BASE - db - borrow;
				borrow = 1;
			}

			res = (bigint_word_t) tmp;
			dest->digit[idx] = res;
		}

		/* The last step is the '1'. */
		assert(borrow == 1);
	} else {
		dest->negative = b_false;
	}

	bigint_refine_len(dest);
}

/** Multiply big integer by digit.
 *
 * @param a		Bigint factor.
 * @param b		Digit factor.
 * @param dest		Destination bigint.
 */
static void bigint_shift_mul_dig(bigint_t *a, bigint_word_t b, size_t shift,
    bigint_t *dest)
{
	size_t lbound;
	size_t idx;

	bigint_dword_t da, db;
	bigint_dword_t tmp;
	bigint_word_t res, carry;

#ifdef DEBUG_BIGINT_TRACE
	printf("Multiply bigint by digit.\n");
#endif
	/* Compute length bound and allocate. */
	lbound = a->length + shift + 1;
	bigint_alloc(dest, lbound);

	/* Copy sign. */
	dest->negative = a->negative;

	for (idx = 0; idx < shift; ++idx)
		dest->digit[idx] = 0;

	carry = 0;
	for (idx = 0; idx < lbound - shift; ++idx) {
		da = idx < a->length ? a->digit[idx] : 0;
		db = b;

		tmp = da * db + (bigint_word_t) carry;

		carry = (bigint_word_t) (tmp / BIGINT_BASE);
		res = (bigint_word_t) (tmp % BIGINT_BASE);

		dest->digit[shift + idx] = res;
	}

	/* If our lbound is correct, carry must be zero. */
	assert(carry == 0);

	bigint_refine_len(dest);
}

/** Allocate bigint of the given length.
 *
 * @param bigint	Bigint whose digit array should be allocated.
 * @param length	Length of array (also set as bigint length).
 */
static void bigint_alloc(bigint_t *bigint, size_t length)
{
	size_t a_length;

#ifdef DEBUG_BIGINT_TRACE
	printf("Allocate bigint digit array.\n");
#endif
	/* malloc() sometimes cannot allocate blocks of zero size. */
	if (length == 0)
		a_length = 1;
	else
		a_length = length;

	bigint->digit = malloc(a_length * sizeof(bigint_word_t));
	if (bigint->digit == NULL) {
		printf("Memory allocation failed.\n");
		exit(1);
	}

	bigint->length = length;
}

/** Adjust length field of bigint to be exact.
 *
 * When bigint is allocated with bigint_alloc() its length can be
 * imprecise (higher than actually number of non-zero digits).
 * Then this function is used to lower @c length to the exact value.
 */
static void bigint_refine_len(bigint_t *bigint)
{
#ifdef DEBUG_BIGINT_TRACE
	printf("Refine bigint length.\n");
#endif
	while (bigint->length > 0 && bigint->digit[bigint->length - 1] == 0)
		bigint->length -= 1;

	if (bigint->length == 0)
		bigint->negative = b_false;
}
