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

CAO_RES CAO_modpol_decl(CAO_modpol * a, CAO_int degree, CAO_int p,
						CAO_int par_list[])
{
	CAO_modpol_s *_a = (CAO_modpol_s *) malloc(sizeof(CAO_modpol_s));

	CAO_mod m;
	CAO_mod_decl(&m, p);
	_CAO_mod_init(m, par_list[0]);
	((CAO_mod_s *) m)->bak->restore();

	CAO_rint _degree = _CAO_int_cast_rint(degree);

	ZZ_pX modulus(INIT_SIZE, _degree);

	for (int j = _degree; j >= 0; j--)
	{
		_CAO_mod_init(m, par_list[j]);
		ZZ_p c = *((CAO_mod_s *) m)->val;
		SetCoeff(modulus, j, c);
	}

	CAO_mod_dispose(m);

	ZZ_pE::init(modulus);

	_a->val = new ZZ_pE();
	_a->bak = new ZZ_pBak();
	_a->bakE = new ZZ_pEBak();

	_a->bak->save();
	_a->bakE->save();

	*a = (CAO_modpol) _a;
	return CAO_OK;
}

CAO_RES _CAO_modpol_decl(CAO_modpol * a, const int degree, CAO_mod par_list[])
{
	CAO_modpol_s *_a = (CAO_modpol_s *) malloc(sizeof(CAO_modpol_s));
	((CAO_mod_s *) (par_list[0]))->bak->restore();

	ZZ_pX modulus(INIT_SIZE, degree);

	for (int j = degree; j >= 0; j--)
	{
		ZZ_p c = *((CAO_mod_s *) (par_list[j]))->val;
		SetCoeff(modulus, j, c);
	}

	ZZ_pE::init(modulus);

	_a->val = new ZZ_pE();
	_a->bak = new ZZ_pBak();
	_a->bakE = new ZZ_pEBak();

	_a->bak->save();
	_a->bakE->save();

	*a = (CAO_modpol) _a;
	return CAO_OK;
}

CAO_RES CAO_modpol_init(CAO_modpol a, const char *par_list)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	_a->bak->restore();
	_a->bakE->restore();
	int degree = _a->val->degree();

	// coefficient starts + end of last
	int *parameters = (int *)malloc((degree + 1) * sizeof(int));
	int i = (degree - 1), offset = 0;

	while(i >= 0)
	{
		parameters[i] = offset;

		while ((par_list[offset] >= '0') && (par_list[offset] <= '9'))
			offset++;

		offset++;				// jump to first digit of next param (may be off 
								// limits)
		i--;
	}
	parameters[degree] = offset;

	char *buffer = (char *)malloc(offset * sizeof(char));
	memcpy(buffer, par_list, offset);

	istringstream ins;
	ZZ_pX val(INIT_SIZE, degree);
	ZZ_p c;
	for (int j = 0; j < degree; j++)
	{
		buffer[parameters[j + 1] - 1] = '\0';
		ins.str(buffer + parameters[j]);
		ins >> c;
		ins.clear();
		SetCoeff(val, j, c);
	}
	free(parameters);
	free(buffer);
	*_a->val = to_ZZ_pE(val);
	_a->bak->save();
	_a->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_assign(CAO_modpol a, CAO_modpol b)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_a->bak->restore();
	_a->bakE->restore();
	*_a->val = *_b->val;
	_a->bak->save();
	_a->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_assign_one(CAO_modpol a)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	_a->bak->restore();
	_a->bakE->restore();
	set(*_a->val);
	return CAO_OK;
}

CAO_RES CAO_modpol_assign_zero(CAO_modpol a)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	_a->bak->restore();
	_a->bakE->restore();
	clear(*_a->val);
	return CAO_OK;
}

CAO_RES CAO_modpol_clone(CAO_modpol * a, CAO_modpol b)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) malloc(sizeof(CAO_modpol_s));
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_b->bak->restore();
	_b->bakE->restore();
	_a->val = new ZZ_pE(*_b->val);
	_a->bak = new ZZ_pBak();
	_a->bakE = new ZZ_pEBak();
	_a->bak->save();
	_a->bakE->save();
	_b->bak->save();
	_b->bakE->save();
	*a = _a;
	return CAO_OK;
}

CAO_RES CAO_modpol_add(CAO_modpol r, CAO_modpol a, CAO_modpol b)
{
	CAO_modpol_s *_r = (CAO_modpol_s *) r;
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_r->bak->restore();
	_r->bakE->restore();
	*_r->val = (*_a->val) + (*_b->val);
	_r->bak->save();
	_r->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_addTo(CAO_modpol r, CAO_modpol a)
{
	CAO_modpol_s *_r = (CAO_modpol_s *) r;
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	_r->bak->restore();
	_r->bakE->restore();
	*_r->val += (*_a->val);
	_r->bak->save();
	_r->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_sub(CAO_modpol r, CAO_modpol a, CAO_modpol b)
{
	CAO_modpol_s *_r = (CAO_modpol_s *) r;
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_r->bak->restore();
	_r->bakE->restore();
	*_r->val = (*_a->val) - (*_b->val);
	_r->bak->save();
	_r->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_subTo(CAO_modpol r, CAO_modpol a)
{
	CAO_modpol_s *_r = (CAO_modpol_s *) r;
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	_r->bak->restore();
	_r->bakE->restore();
	*_r->val -= (*_a->val);
	_r->bak->save();
	_r->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_mul(CAO_modpol r, CAO_modpol a, CAO_modpol b)
{
	CAO_modpol_s *_r = (CAO_modpol_s *) r;
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_r->bak->restore();
	_r->bakE->restore();
	*_r->val = (*_a->val) * (*_b->val);
	_r->bak->save();
	_r->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_div(CAO_modpol r, CAO_modpol a, CAO_modpol b)
{
	CAO_modpol_s *_r = (CAO_modpol_s *) r;
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_r->bak->restore();
	_r->bakE->restore();
	*_r->val = (*_a->val) / (*_b->val);
	_r->bak->save();
	_r->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_pow(CAO_modpol r, CAO_modpol a, CAO_int b)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	ZZ *_b = (ZZ *) b;
	CAO_modpol_s *_r = (CAO_modpol_s *) r;
	_r->bak->restore();
	_r->bakE->restore();
	*_r->val = power(*_a->val, *_b);
	_r->bak->save();
	_r->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_sym(CAO_modpol r, CAO_modpol a)
{
	CAO_modpol_s *_r = (CAO_modpol_s *) r;
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	_r->bak->restore();
	_r->bakE->restore();
	*_r->val = -*_a->val;
	_r->bak->save();
	_r->bakE->save();
	return CAO_OK;
}

CAO_bool _CAO_modpol_equal(CAO_modpol a, CAO_modpol b)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_a->bak->restore();
	_a->bakE->restore();
	CAO_bool r = ((*_a->val) == (*_b->val));
	_a->bak->save();
	_a->bakE->save();
	return r;
}

CAO_bool _CAO_modpol_nequal(CAO_modpol a, CAO_modpol b)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_a->bak->restore();
	_a->bakE->restore();
	CAO_bool r = ((*_a->val) != (*_b->val));
	_a->bak->save();
	_a->bakE->save();
	return r;
}

CAO_RES CAO_modpol_dump(CAO_modpol a)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	_a->bak->restore();
	_a->bakE->restore();
	cout << *_a->val << "\n";
	_a->bak->save();
	_a->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_dispose(CAO_modpol a)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	delete(_a->val);
	delete(_a->bak);
	delete(_a->bakE);
	free(_a);
	return CAO_OK;
}

CAO_RES CAO_mod_cast_modpol(CAO_modpol b, CAO_mod a)
{
	CAO_mod_s *_a = (CAO_mod_s *) a;
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_b->bak->restore();
	_b->bakE->restore();
	*(_b->val) = *(_a->val);
	_b->bak->save();
	_b->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_matrix_cast_modpol(CAO_modpol b, CAO_matrix a)
{
	CAO_matrix_s *_a = (CAO_matrix_s *) a;
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_b->bak->restore();
	_b->bakE->restore();
	ZZ_p c;
	int degree = _b->val->degree();
	ZZ_pX val(INIT_SIZE, degree);
	for (int i = 0; i <= degree; i++)
	{
		c = *((ZZ_p *) _a->value[i]);
		SetCoeff(val, i, c);
	}
	*_b->val = to_ZZ_pE(val);
	_b->bak->save();
	_b->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_cast_matrix(CAO_matrix b, CAO_modpol a)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	CAO_matrix_s *_b = (CAO_matrix_s *) b;
	_a->bak->restore();
	_a->bakE->restore();
	ZZ_p *c;
	int degree = _a->val->degree();
	for (int i = 0; i < degree; i++)
	{
		c = (ZZ_p *) _b->value[i];
		GetCoeff(*c, rep(*_a->val), i);
	}
	_a->bak->save();
	_a->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_vector_cast_modpol(CAO_modpol b, CAO_vector a)
{
	CAO_vector_s *_a = (CAO_vector_s *) a;
	CAO_modpol_s *_b = (CAO_modpol_s *) b;
	_b->bak->restore();
	_b->bakE->restore();
	ZZ_p c;
	int degree = _b->val->degree();
	ZZ_pX val(INIT_SIZE, degree);
	for (int i = 0; i < _a->size; i++)
	{
		c = *((ZZ_p *) _a->value[i]);
		SetCoeff(val, i, c);
	}
	*_b->val = to_ZZ_pE(val);
	_b->bak->save();
	_b->bakE->save();
	return CAO_OK;
}

CAO_RES CAO_modpol_cast_vector(CAO_vector b, CAO_modpol a)
{
	CAO_modpol_s *_a = (CAO_modpol_s *) a;
	CAO_vector_s *_b = (CAO_vector_s *) b;
	_a->bak->restore();
	_a->bakE->restore();
	ZZ_p *c;
	for (int i = 0; i < _b->size; i++)
	{
		c = (ZZ_p *) _b->value[i];
		GetCoeff(*c, rep(*_a->val), i);
	}
	_a->bak->save();
	_a->bakE->save();
	return CAO_OK;
}
