/*  CAO Compiler
    Copyright (C) 2014 Cryptography and Information Security Group, HASLab - INESC TEC and Universidade do Minho

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "CAO_int.h"

CAO_RES CAO_int_decl(CAO_int * i)
{
	*i = (CAO_int) new(ZZ);
	return CAO_OK;
}

CAO_RES CAO_int_init(CAO_int i, const char *val)
{
	ZZ *_i = (ZZ *) i;
	(*_i) = to_ZZ(val);
	return CAO_OK;
}

CAO_RES CAO_int_assign(CAO_int i, CAO_int j)
{
	ZZ *_i = (ZZ *) i;
	ZZ *_j = (ZZ *) j;
	(*_i) = (*_j);
	return CAO_OK;
}

CAO_RES CAO_int_assign_one(CAO_int i)
{
	ZZ *_i = (ZZ *) i;
	set(*_i);
	return CAO_OK;
}

CAO_RES CAO_int_assign_zero(CAO_int i)
{
	ZZ *_i = (ZZ *) i;
	clear(*_i);
	return CAO_OK;
}

CAO_RES CAO_int_clone(CAO_int * i, CAO_int j)
{
	CAO_int_decl(i);
	CAO_int_assign(*i, j);
	return CAO_OK;
}

CAO_RES CAO_int_dispose(CAO_int i)
{
	ZZ *_i = (ZZ *) i;
	delete(_i);
	return CAO_OK;
}

CAO_RES CAO_int_add(CAO_int r, CAO_int a, CAO_int b)
{
	ZZ *_a = (ZZ *) a;
	ZZ *_b = (ZZ *) b;
	ZZ *_r = (ZZ *) r;
	(*_r) = (*_a) + (*_b);
	return CAO_OK;
}

CAO_RES CAO_int_addTo(CAO_int r, CAO_int a)
{
	ZZ *_a = (ZZ *) a;
	ZZ *_r = (ZZ *) r;
	(*_r) += (*_a);
	return CAO_OK;
}

CAO_RES CAO_int_sub(CAO_int r, CAO_int a, CAO_int b)
{
	ZZ *_a = (ZZ *) a;
	ZZ *_b = (ZZ *) b;
	ZZ *_r = (ZZ *) r;
	(*_r) = (*_a) - (*_b);
	return CAO_OK;
}

CAO_RES CAO_int_subTo(CAO_int r, CAO_int a)
{
	ZZ *_a = (ZZ *) a;
	ZZ *_r = (ZZ *) r;
	(*_r) -= (*_a);
	return CAO_OK;
}

CAO_RES CAO_int_sym(CAO_int r, CAO_int a)
{
	ZZ *_a = (ZZ *) a;
	ZZ *_r = (ZZ *) r;
	(*_r) = -(*_a);
	return CAO_OK;
}

CAO_RES CAO_int_mul(CAO_int r, CAO_int a, CAO_int b)
{
	ZZ *_a = (ZZ *) a;
	ZZ *_b = (ZZ *) b;
	ZZ *_r = (ZZ *) r;
	(*_r) = (*_a) * (*_b);
	return CAO_OK;
}

CAO_RES CAO_int_div(CAO_int r, CAO_int a, CAO_int b)
{
	ZZ *_a = (ZZ *) a;
	ZZ *_b = (ZZ *) b;
	ZZ *_r = (ZZ *) r;
	(*_r) = (*_a) / (*_b);
	return CAO_OK;
}

CAO_RES CAO_int_mod(CAO_int r, CAO_int a, CAO_int b)
{
	ZZ *_a = (ZZ *) a;
	ZZ *_b = (ZZ *) b;
	ZZ *_r = (ZZ *) r;
	(*_r) = (*_a) % (*_b);
	return CAO_OK;
}

CAO_RES CAO_int_pow(CAO_int r, CAO_int a, CAO_int b)
{
	// r = a ^ b;
	ZZ *_r = (ZZ *) r;
	ZZ *_a = (ZZ *) a;
	ZZ *_b = (ZZ *) b;

	long i, k = NumBits(*_b);
	*_r = 1;

	for (i = k - 1; i >= 0; i--)
	{
		(*_r) = (*_r) * (*_r);
		if (bit(*_b, i) == 1)
		{
			(*_r) = ((*_r) * (*_a));
		}
	}

	return CAO_OK;
}

CAO_bool _CAO_int_equal(CAO_int i, CAO_int j)
{
	ZZ *_i = (ZZ *) i;
	ZZ *_j = (ZZ *) j;
	CAO_bool r;
	r = ((*_i) == (*_j));
	return r;
}

CAO_bool _CAO_int_nequal(CAO_int i, CAO_int j)
{
	ZZ *_i = (ZZ *) i;
	ZZ *_j = (ZZ *) j;
	CAO_bool r;
	r = ((*_i) != (*_j));
	return r;
}

CAO_bool _CAO_int_lt(CAO_int i, CAO_int j)
{
	ZZ *_i = (ZZ *) i;
	ZZ *_j = (ZZ *) j;
	CAO_bool r;
	r = ((*_i) < (*_j));
	return r;
}

CAO_bool _CAO_int_lte(CAO_int i, CAO_int j)
{
	ZZ *_i = (ZZ *) i;
	ZZ *_j = (ZZ *) j;
	CAO_bool r;
	r = ((*_i) <= (*_j));
	return r;
}

CAO_bool _CAO_int_gt(CAO_int i, CAO_int j)
{
	ZZ *_i = (ZZ *) i;
	ZZ *_j = (ZZ *) j;
	CAO_bool r;
	r = ((*_i) > (*_j));
	return r;
}

CAO_bool _CAO_int_gte(CAO_int i, CAO_int j)
{
	ZZ *_i = (ZZ *) i;
	ZZ *_j = (ZZ *) j;
	CAO_bool r;
	r = ((*_i) >= (*_j));
	return r;
}

CAO_RES CAO_int_dump(CAO_int i)
{
	ZZ *_i = (ZZ *) i;
	std::cout << (*_i) << "\n";
	return CAO_OK;
}

CAO_rint _CAO_int_cast_rint(CAO_int i)
{
	unsigned char rep[4];
	int out;
	ZZ *_i = (ZZ *) i;

	if (((unsigned long)NumBits(*_i)) > ((8 * sizeof(int)) - 1))
	{
		return CAO_ERR;
	}

	BytesFromZZ(rep, (*_i), 4);
	out = rep[3];
	out = (out << 8) | rep[2];
	out = (out << 8) | rep[1];
	out = (out << 8) | rep[0];

	return out;
}
