#include "isog.h"
#include "ec.h"
#include <assert.h>

// -------------------------------------------------------------------------
// -------------------------------------------------------------------------

// Degree-2 isogeny with kernel generated by P != (0 ,0)
// Outputs the curve coefficient in the form A24=(A+2C:4C)
void xisog_2(ec_point_t* B, ec_point_t const P)
{
        fp2_sqr(&B->x, &P.x);
        fp2_sqr(&B->z, &P.z);
        fp2_sub(&B->x, &B->z, &B->x);
        fp2_add(&K[0].x, &P.x, &P.z);
        fp2_sub(&K[0].z, &P.x, &P.z);
}

// Degree-4 isogeny with kernel generated by P such that [2]P != (0 ,0)
// Outputs the curve coefficient in the form A24=(A+2C:4C)
void xisog_4(ec_point_t* B, ec_point_t const P)
{
	fp2_sqr(&K[0].x, &P.x);
	fp2_sqr(&K[0].z, &P.z);
	fp2_add(&K[1].x, &K[0].z, &K[0].x);
	fp2_sub(&K[1].z, &K[0].z, &K[0].x);
	fp2_mul(&B->x, &K[1].x, &K[1].z);
	fp2_sqr(&B->z, &K[0].z);

	// Constants for xeval_4
	fp2_add(&K[2].x, &P.x, &P.z);
	fp2_sub(&K[1].x, &P.x, &P.z);
	fp2_add(&K[0].x, &K[0].z, &K[0].z);
	fp2_add(&K[0].x, &K[0].x, &K[0].x);
}

// Degree-4 isogeny with kernel generated by P such that [2]P = (0 ,0)
void xisog_4_singular(ec_point_t* B24, ec_point_t const P, ec_point_t A24)
{
	fp2_copy(&K[0].z, &A24.z);
	if(fp2_is_equal(&P.x, &P.z)){
		// Case for P=(1,_)
		fp2_copy(&K[0].x, &A24.x);
		fp2_sub(&K[1].x, &A24.x, &A24.z);
		fp2_neg(&B24->z, &K[1].x);
	}
	else{
		// Case for P=(-1,_)
		fp2_copy(&K[1].x, &A24.x);
		fp2_sub(&K[0].x, &A24.x, &A24.z);
		fp2_neg(&B24->z, &K[0].x);
		fp2_copy(&B24->z, &K[1].x);
	}
	fp2_copy(&B24->x, &K[0].z);
}

// xISOG procedure, which is a hybrid between Montgomery and Twisted Edwards
// This tradition fomulae corresponds with the Twisted Edwards formulae but 
// mapping the output into Montgomery form
void xisog_t(ec_point_t* B, uint64_t const i, ec_point_t const A)
{
	int j;
	int d = ((int)TORSION_ODD_PRIMES[i] - 1) / 2;	// Here, l = 2d + 1

	fp2_t By, Bz, constant_d_edwards, tmp_a, tmp_d;

	fp2_copy(&By, &K[0].x);
	fp2_copy(&Bz, &K[0].z);

	for (j = 1; j < d; j++)
	{
		fp2_mul(&By, &By, &K[j].x);
		fp2_mul(&Bz, &Bz, &K[j].z);
	};

	// Mapping Montgomery curve coefficients into Twisted Edwards form
	fp2_sub(&constant_d_edwards, &A.x, &A.z);
	fp2_copy(&tmp_a, &A.x);
	fp2_copy(&tmp_d, &constant_d_edwards);

	// left-to-right method for computing a^l and d^l
	for (j = 1; j < (int)p_plus_minus_bitlength[i]; j++)
	{
		fp2_sqr(&tmp_a, &tmp_a);
		fp2_sqr(&tmp_d, &tmp_d);
		if( ( ((int)TORSION_ODD_PRIMES[i] >> ((int)p_plus_minus_bitlength[i] - j - 1)) & 1 ) != 0 )
		{
			fp2_mul(&tmp_a, &tmp_a, &A.x);
			fp2_mul(&tmp_d, &tmp_d, &constant_d_edwards);
		};
	};

	// raising to 8-th power
	for (j = 0; j < 3; j++)
	{
		fp2_sqr(&By, &By);
		fp2_sqr(&Bz, &Bz);
	};

	// Mapping Twisted Edwards curve coefficients into Montgomery form
	fp2_mul(&(B->x), &tmp_a, &Bz);
	fp2_mul(&(B->z), &tmp_d, &By);
	fp2_sub(&(B->z), &(B->x), &(B->z));
}

// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
//  Isogeny construction (xISOG) used in velu SQRT

void xisog_s(ec_point_t* B, uint64_t const i, ec_point_t const A)
{
	// =================================================================================
	assert(TORSION_ODD_PRIMES[i] > gap);     // Ensuring velusqrt is used for l_i > gap
	sI = sizeI[i];          // size of I
	sJ = sizeJ[i];          // size of J
	sK = sizeK[i];          // size of K

	assert(sI >= sJ);       // Ensuring #I >= #J
	assert(sK >= 0);         // Recall, L is a prime and therefore it must be that #K > 0
	assert(sJ > 1);         // ensuring sI >= sJ > 1
	// =================================================================================
	
	// We require the curve coefficient A = A'/C ... well, a multiple of these ones
	fp2_t Ap;
	fp2_add(&Ap, &A.x, &A.x);	// 2A' + 4C
	fp2_sub(&Ap, &Ap, &A.z);	// 2A'
	fp2_add(&Ap, &Ap, &Ap);	// 4A'

	fp2_t ADD_SQUARED[sJ_max],	// (Xj + Zj)^2
	   SUB_SQUARED[sJ_max];	// (Xj - Zj)^2

	int j;
	// Next loop precompute some variables to be used in the reaminder of xisog
	for (j = 0; j < sJ; j++)
	{
		fp2_sub(&SUB_SQUARED[j], &J[j].x, &J[j].z);		// (Xj - Zj)
		fp2_sqr(&SUB_SQUARED[j], &SUB_SQUARED[j]);		// (Xj - Zj)^2
		fp2_sub(&ADD_SQUARED[j], &SUB_SQUARED[j], &XZJ4[j]);	// (Xj + Zj)^2
	};

	//  --------------------------------------------------------------------------------------------------
	//                   ~~~~~~~~
	//                    |    | 
	// Computing E_J(W) = |    | [ F0(W, x([j]P)) * alpha^2 + F1(W, x([j]P)) * alpha + F2(W, x([j]P)) ]
	//                    j in J 
	// In order to avoid costly inverse computations in fp, we are gonna work with projective coordinates
	// In particular, for a degree-l isogeny construction, we need alpha = 1 and alpha = -1

	//fp2_t EJ_0[sJ][3],	// quadratic factors of one polynomial to be used in a resultant 
	//   EJ_1[sJ][3];	// quadratic factors of one polynomial to be used in a resultant

	// Next loop computes all the quadratic factors of EJ_0 and EJ_1
	fp2_t t1;
	for (j = 0; j < sJ; j++)
	{
		// Each SUB_SQUARED[j] and ADD_SQUARED[j] should be multiplied by C
		fp2_mul(&EJ_1[j][0], &ADD_SQUARED[j], &A.z);
		fp2_mul(&EJ_0[j][0], &SUB_SQUARED[j], &A.z);
		// We require the double of tadd and tsub
		fp2_add(&EJ_0[j][1], &EJ_1[j][0], &EJ_1[j][0]);
		fp2_add(&EJ_1[j][1], &EJ_0[j][0], &EJ_0[j][0]);

		fp2_mul(&t1, &XZJ4[j], &Ap);			// A' *(-4*Xj*Zj)

		// Case alpha = 1
		fp2_sub(&EJ_0[j][1], &t1, &EJ_0[j][1]);
		fp2_copy(&EJ_0[j][2], &EJ_0[j][0]);		// E_[0,j} is a palindrome
		
		// Case alpha = -1
		fp2_sub(&EJ_1[j][1], &EJ_1[j][1], &t1);
		fp2_copy(&EJ_1[j][2], &EJ_1[j][0]);		// E_{1,j} is a palindrome
	};

	// ---------------------------------------------------------------------
	// The faster way for multiplying is using a divide-and-conquer approach
	
	// selfreciprocal product tree of EJ_0 (we only require the root)
	product_tree_selfreciprocal_LENFeq3(ptree_EJ, deg_ptree_EJ, 0, EJ_0, sJ);
	assert( deg_ptree_EJ[0] == (2*sJ) );
	if (!scaled)
	{
		// (unscaled) remainder tree approach
		multieval_unscaled(leaves, ptree_EJ[0], 2*sJ + 1, rtree_hI, (const fp2_t*)rtree_A, ptree_hI, deg_ptree_hI, 0, sI);
	}
	else
	{
		// scaled remainder tree approach
		fp2_t G[sI_max], G_rev[sI_max];
		poly_redc(G, ptree_EJ[0], 2*sJ + 1, ptree_hI[0], sI + 1, R0, A0);
		for (j = 0; j < sI; j++)
			fp2_copy(&G_rev[j], &G[sI - 1 - j]);

		poly_mul_middle(G_rev, G_rev, sI, R0, sI);
		for (j = 0; j < sI; j++)
			fp2_copy(&G[j], &G_rev[sI - 1 - j]);

		multieval_scaled(leaves, G, ptree_hI, deg_ptree_hI, 0, sI);
	};
	clear_tree(ptree_EJ, 0, sJ);
	// Finally, we must multiply the leaves of the outpur of remainders
	fp2_t r0;
	product(&r0, (const fp2_t*)leaves, sI);

	// selfreciprocal product tree of EJ_1 (we only require the root)
	product_tree_selfreciprocal_LENFeq3(ptree_EJ, deg_ptree_EJ, 0, EJ_1, sJ);
	assert( deg_ptree_EJ[0] == (2*sJ) );
	if (!scaled)
	{
		// (unscaled) remainder tree approach
		multieval_unscaled(leaves, ptree_EJ[0], 2*sJ + 1, rtree_hI, (const fp2_t*)rtree_A, ptree_hI, deg_ptree_hI, 0, sI);
	}
	else
	{
		// scaled remainder tree approach
		fp2_t G[sI_max], G_rev[sI_max];
		poly_redc(G, ptree_EJ[0], 2*sJ + 1, ptree_hI[0], sI + 1, R0, A0);
		for (j = 0; j < sI; j++)
			fp2_copy(&G_rev[j], &G[sI - 1 - j]);

		poly_mul_middle(G_rev, G_rev, sI, R0, sI);
		for (j = 0; j < sI; j++)
			fp2_copy(&G[j], &G_rev[sI - 1 - j]);

		multieval_scaled(leaves, G, ptree_hI, deg_ptree_hI, 0, sI);
	};
	clear_tree(ptree_EJ, 0, sJ);
	// Finally, we must multiply the leaves of the outpur of remainders
	fp2_t r1;
	product(&r1, (const fp2_t*)leaves, sI);

	// -------------------------------
	// Sometimes the public value sK is equal to zero,
	// Thus for avoing runtime error we add one when sK =0
	fp2_t hK_0[sK_max + 1], hK_1[sK_max + 1], hk_0, hk_1;
	for (j = 0; j < sK; j++)
	{
		fp2_sub(&hK_0[j], &K[j].z, &K[j].x);
		fp2_add(&hK_1[j], &K[j].z, &K[j].x);
	};

	// hk_0 <- use product to mulitiply all the elements in hK_0
	product(&hk_0, (const fp2_t*)hK_0, sK);
	// hk_1 <- use product to mulitiply all the elements in hK_1
	product(&hk_1, (const fp2_t*)hK_1, sK);
	
	// --------------------------------------------------------------
	// Now, we have all the ingredients for computing the image curve
	fp2_t A24, A24m,
	   t24, t24m;	// <---- JORGE creo que podemos omitir estas variables, se usan cuando ya no se requiren los valores de la entrada A (podemos cambiar estos t's por B[0] y B[1]

	fp2_copy(&A24, &A.x);			// A' + 2C
	fp2_sub(&A24m, &A.x, &A.z);		// A' - 2C
	fp2_copy(&Ap, &A24m);

	// left-to-right method for computing (A' + 2C)^l and (A' - 2C)^l
	for (j = 1; j < (int)p_plus_minus_bitlength[i]; j++)
	{
		fp2_sqr(&A24, &A24);
		fp2_sqr(&A24m, &A24m);
		if( ( ((int)TORSION_ODD_PRIMES[i] >> ((int)p_plus_minus_bitlength[i] - j - 1)) & 1 ) != 0 )
		{
			fp2_mul(&A24, &A24, &A.x);
			fp2_mul(&A24m, &A24m, &Ap);
		};
	};

	fp2_mul(&t24m, &hk_1, &r1);			// output of algorithm 2 with alpha =-1 and without the demoninator
	fp2_sqr(&t24m, &t24m);			// raised at 2
	fp2_sqr(&t24m, &t24m);			// raised at 4
	fp2_sqr(&t24m, &t24m);			// raised at 8

	fp2_mul(&t24, &hk_0, &r0);			// output of algorithm 2 with alpha = 1 and without the demoninator 
	fp2_sqr(&t24, &t24);			// raised at 2
	fp2_sqr(&t24, &t24);			// raised at 4
	fp2_sqr(&t24, &t24);			// raised at 8

	fp2_mul(&A24, &A24, &t24m);
	fp2_mul(&A24m, &A24m, &t24);

	// Now, we have d = (A24m / A24) where the image Montgomery cuve coefficient is
	//      B'   2*(1 + d)   2*(A24 + A24m)
	// B = ---- = --------- = --------------
	//      C      (1 - d)     (A24 - A24m)
	// However, we required B' + 2C = 4*A24 and 4C = 4 * (A24 - A24m)

	fp2_sub(&t24m, &A24, &A24m);		//   (A24 - A24m)
	fp2_add(&t24m, &t24m, &t24m);		// 2*(A24 - A24m)
	fp2_add(&t24m, &t24m, &t24m);		// 4*(A24 - A24m)

	fp2_add(&t24, &A24, &A24);			// 2 * A24
	fp2_add(&t24, &t24, &t24);			// 4 * A24

	fp2_copy(&(B->x), &t24);
	fp2_copy(&(B->z), &t24m);
}
