/*  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_sbits.h"

CAO_RES CAO_sbits_decl(CAO_sbits * b, const int s)
{
	CAO_sbits_s *_b = (CAO_sbits_s *) malloc(sizeof(CAO_sbits_s));
	_b->size = s;
	_b->value = new ZZ;
	*b = _b;
	return CAO_OK;
}

CAO_RES CAO_sbits_init(CAO_sbits b, const char *val)
{
	CAO_sbits_s *_b = (CAO_sbits_s *) b;
	*(_b->value) = to_ZZ(val);
	// b = _b;
	return CAO_OK;
}

CAO_RES CAO_sbits_assign(CAO_sbits r, CAO_sbits b)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_b = (CAO_sbits_s *) b;

	ZZ *zr = _r->value;
	ZZ *zb = _b->value;
	(*zr) = (*zb);
	return CAO_OK;
}

CAO_RES CAO_sbits_clone(CAO_sbits * b, CAO_sbits a)
{
	CAO_sbits_s *_a = (CAO_sbits_s *) a;
	CAO_sbits_decl(b, _a->size);
	CAO_sbits_assign(*b, a);
	return CAO_OK;
}

CAO_bool _CAO_sbits_equal(CAO_sbits i, CAO_sbits j)
{
	CAO_sbits_s *_i = (CAO_sbits_s *) i;
	CAO_sbits_s *_j = (CAO_sbits_s *) j;

	ZZ *zi = _i->value;
	ZZ *zj = _j->value;
	CAO_bool r = ((*zi) == (*zj));
	return r;
}

CAO_bool _CAO_sbits_nequal(CAO_sbits i, CAO_sbits j)
{
	CAO_sbits_s *_i = (CAO_sbits_s *) i;
	CAO_sbits_s *_j = (CAO_sbits_s *) j;

	ZZ *zi = _i->value;
	ZZ *zj = _j->value;
	CAO_bool r = !((*zi) == (*zj));

	return r;
}

CAO_RES CAO_sbits_or(CAO_sbits r, CAO_sbits i, CAO_sbits j)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_i = (CAO_sbits_s *) i;
	CAO_sbits_s *_j = (CAO_sbits_s *) j;

	ZZ *zr = _r->value;
	ZZ *zi = _i->value;
	ZZ *zj = _j->value;
	*zr = (*zi) | (*zj);

	return CAO_OK;
}

CAO_RES CAO_sbits_and(CAO_sbits r, CAO_sbits i, CAO_sbits j)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_i = (CAO_sbits_s *) i;
	CAO_sbits_s *_j = (CAO_sbits_s *) j;

	ZZ *zr = _r->value;
	ZZ *zi = _i->value;
	ZZ *zj = _j->value;
	*zr = (*zi) & (*zj);

	return CAO_OK;
}

CAO_RES CAO_sbits_xor(CAO_sbits r, CAO_sbits i, CAO_sbits j)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_i = (CAO_sbits_s *) i;
	CAO_sbits_s *_j = (CAO_sbits_s *) j;

	ZZ *zr = _r->value;
	ZZ *zi = _i->value;
	ZZ *zj = _j->value;
	*zr = (*zi) ^ (*zj);

	return CAO_OK;
}

CAO_RES CAO_sbits_not(CAO_sbits r, CAO_sbits i)
{
	long j;
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_i = (CAO_sbits_s *) i;

	ZZ *zr = _r->value;
	ZZ *zi = _i->value;
	*zr = *zi;

	for (j = 0; j < _i->size; j++)
	{
		SwitchBit(*zr, j);
	}

	return CAO_OK;
}

CAO_RES CAO_sbits_shift_up(CAO_sbits r, CAO_sbits i, CAO_rint e)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_i = (CAO_sbits_s *) i;
	ZZ base;

	ZZ *zr = _r->value;
	ZZ *zi = _i->value;
	int si = _i->size;
	power(base, 2, si);
	*zr = ((*zi) << e) % base;

	return CAO_OK;
}

CAO_RES CAO_sbits_shift_down(CAO_sbits r, CAO_sbits i, CAO_rint e)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_i = (CAO_sbits_s *) i;
	ZZ base;

	ZZ *zi = _i->value;
	ZZ *zr = _r->value;
	*zr = (*zi) >> e;

	return CAO_OK;
}

CAO_RES CAO_sbits_rot_up(CAO_sbits r, CAO_sbits i, CAO_rint e)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_i = (CAO_sbits_s *) i;
	ZZ a, base, upper;

	ZZ *zr = _r->value;
	ZZ *zi = _i->value;
	int si = _i->size;
	power(base, 2, si);
	a = *zi << e;
	upper = a / base;
	a = a % base;
	*zr = a + upper;

	return CAO_OK;
}

CAO_RES CAO_sbits_rot_down(CAO_sbits r, CAO_sbits i, CAO_rint e)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_i = (CAO_sbits_s *) i;
	ZZ a, base, lower;

	ZZ *zi = _i->value;
	ZZ *zr = _r->value;
	power(base, 2, e);
	lower = *zi % base;
	a = *zi / base;
	lower = lower * base;
	*zr = a + lower;

	return CAO_OK;
}

CAO_RES CAO_sbits_select(CAO_sbits r, CAO_sbits b, CAO_rint e)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_b = (CAO_sbits_s *) b;

	ZZ *zb = _b->value;
	ZZ *zr = _r->value;
	long _bit = bit(*zb, e);
	*zr = _bit;

	return CAO_OK;
}

CAO_RES CAO_sbits_set(CAO_sbits r, CAO_sbits b, CAO_rint e)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_b = (CAO_sbits_s *) b;

	ZZ *zb = _b->value;
	ZZ *zr = _r->value;

	if (bit(*zr, e) != bit(*zb, 0))
	{
		SwitchBit(*zr, e);
	}

	return CAO_OK;
}

CAO_RES CAO_sbits_range_select(CAO_sbits r, CAO_sbits b, CAO_rint e, CAO_rint j)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_b = (CAO_sbits_s *) b;
	ZZ a, base, lower;

	ZZ *zb = _b->value;
	ZZ *zr = _r->value;
	int ns = j - e + 1;
	power(base, 2, ns);
	a = (*zb) >> e;
	lower = a % base;
	*zr = lower;

	return CAO_OK;
}

CAO_RES CAO_sbits_range_set(CAO_sbits r, CAO_sbits b, CAO_rint e, CAO_rint j)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_b = (CAO_sbits_s *) b;
	ZZ a, base, lower;

	ZZ *zb = _b->value;
	ZZ *zr = _r->value;
	int ns = j - e + 1;
	power(base, 2, e);
	lower = *zr % base;
	a = (*zr) >> (j + 1);
	a <<= ns;
	a += *zb;
	a <<= e;
	*zr = a + lower;

	return CAO_OK;
}

CAO_RES CAO_sbits_concat(CAO_sbits r, CAO_sbits a, CAO_sbits b)
{
	CAO_sbits_s *_r = (CAO_sbits_s *) r;
	CAO_sbits_s *_a = (CAO_sbits_s *) a;
	CAO_sbits_s *_b = (CAO_sbits_s *) b;
	ZZ nval, base;

	ZZ *zr = _r->value;
	ZZ *za = _a->value;
	ZZ *zb = _b->value;
	int sa = _a->size;
	power(base, 2, sa);
	nval = (*zb) * base;
	*zr = nval + *za;

	return CAO_OK;
}

CAO_RES CAO_sbits_dump(CAO_sbits b)
{
	CAO_sbits_s *_b = (CAO_sbits_s *) b;

	int size = _b->size;
	ZZ *val = _b->value;
	cout << "sbits[" << size << "] = " << (*val) << "\n";

	return CAO_OK;
}

CAO_RES CAO_sbits_dispose(CAO_sbits a)
{
	CAO_sbits_s *_a = (CAO_sbits_s *) a;
	delete(_a->value);
	free(_a);
	return CAO_OK;
}

CAO_RES CAO_sbits_cast_int(CAO_int b, CAO_sbits a)
{
	CAO_sbits_s *_a = (CAO_sbits_s *) a;
	ZZ base;

	ZZ *_b = (ZZ *) b;
	power(base, 2, _a->size);
	*_b = *_a->value - base;

	return CAO_OK;
}

CAO_RES CAO_int_cast_sbits(CAO_sbits b, CAO_int a)
{
	CAO_sbits_s *_b = (CAO_sbits_s *) b;
	ZZ base;

	ZZ *_a = (ZZ *) a;
	power(base, 2, _b->size);
	if (sign(*_a) == -1)
	{
		*_b->value = base + (*_a);
	}
	else
	{
		*_b->value = *_a;
	}
	*_b->value = (*_b->value) % base;

	return CAO_OK;
}

CAO_RES CAO_sbits_cast_sbits(CAO_sbits b, CAO_sbits a)
{
	CAO_sbits_s *_b = (CAO_sbits_s *) b;
	CAO_sbits_s *_a = (CAO_sbits_s *) a;
	ZZ base;

	power(base, 2, _b->size);
	*_b->value = *_a->value % base;

	return CAO_OK;
}
