/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
*/


use xxx::fp::FP;
use xxx::ecp::ECP;
use xxx::fp2::FP2;
use xxx::ecp4::ECP4;
use xxx::fp4::FP4;
use xxx::fp8::FP8;
use xxx::fp24::FP24;
use xxx::big::BIG;
use xxx::ecp;
use xxx::rom;

#[allow(non_snake_case)]
fn linedbl(A: &mut ECP4,qx: &FP,qy: &FP) -> FP24 {
	let mut a=FP8::new();
	let mut b=FP8::new();
	let mut c=FP8::new();	

	let mut xx=FP4::new_copy(&A.getpx());  //X
	let mut yy=FP4::new_copy(&A.getpy());  //Y
	let mut zz=FP4::new_copy(&A.getpz());  //Z
	let mut yz=FP4::new_copy(&yy);        //Y 
	yz.mul(&zz);                //YZ
	xx.sqr();	               //X^2
	yy.sqr();	               //Y^2
	zz.sqr();			       //Z^2
			
	yz.imul(4);
	yz.neg(); yz.norm();       //-2YZ
	yz.qmul(qy);               //-2YZ.Ys

	xx.imul(6);                //3X^2
	xx.qmul(qx);               //3X^2.Xs

	let sb=3*rom::CURVE_B_I;
	zz.imul(sb); 	
	if ecp::SEXTIC_TWIST==ecp::D_TYPE {				
		zz.div_2i();  
	}
	if ecp::SEXTIC_TWIST==ecp::M_TYPE {	
		zz.times_i();
		zz.dbl();
		yz.times_i();
		//yz.norm();
	}	

	zz.norm(); // 3b.Z^2 

	yy.dbl();
	zz.sub(&yy); zz.norm();     // 3b.Z^2-Y^2

	a.copy(&FP8::new_fp4s(&yz,&zz)); // -2YZ.Ys | 3b.Z^2-Y^2 | 3X^2.Xs 
	if ecp::SEXTIC_TWIST==ecp::D_TYPE {		
		b.copy(&FP8::new_fp4(&xx));       // L(0,1) | L(0,0) | L(1,0)
	}
	if ecp::SEXTIC_TWIST==ecp::M_TYPE {
		c.copy(&FP8::new_fp4(&xx));
		c.times_i();  
	}		
	A.dbl();
	return FP24::new_fp8s(&a,&b,&c);	
}

#[allow(non_snake_case)]
fn lineadd(A: &mut ECP4,B: &ECP4,qx: &FP,qy: &FP) -> FP24 {

	let mut a=FP8::new();
	let mut b=FP8::new();
	let mut c=FP8::new();	

	let mut x1=FP4::new_copy(&A.getpx());    // X1
	let mut y1=FP4::new_copy(&A.getpy());    // Y1
	let mut t1=FP4::new_copy(&A.getpz());    // Z1
	let mut t2=FP4::new_copy(&A.getpz());    // Z1
			
	t1.mul(&B.getpy());    // T1=Z1.Y2 
	t2.mul(&B.getpx());    // T2=Z1.X2

	x1.sub(&t2); x1.norm();  // X1=X1-Z1.X2
	y1.sub(&t1); y1.norm();  // Y1=Y1-Z1.Y2

	t1.copy(&x1);            // T1=X1-Z1.X2
	x1.qmul(qy);            // X1=(X1-Z1.X2).Ys
	if ecp::SEXTIC_TWIST==ecp::M_TYPE {
		x1.times_i();
		//x1.norm();
	}

	t1.mul(&B.getpy());       // T1=(X1-Z1.X2).Y2

	t2.copy(&y1);            // T2=Y1-Z1.Y2
	t2.mul(&B.getpx());       // T2=(Y1-Z1.Y2).X2
	t2.sub(&t1); t2.norm();          // T2=(Y1-Z1.Y2).X2 - (X1-Z1.X2).Y2
	y1.qmul(qx); y1.neg(); y1.norm(); // Y1=-(Y1-Z1.Y2).Xs

	a.copy(&FP8::new_fp4s(&x1,&t2)); // (X1-Z1.X2).Ys  |  (Y1-Z1.Y2).X2 - (X1-Z1.X2).Y2  | - (Y1-Z1.Y2).Xs
	if ecp::SEXTIC_TWIST==ecp::D_TYPE {	
		b.copy(&FP8::new_fp4(&y1));
	}
	if ecp::SEXTIC_TWIST==ecp::M_TYPE {
		c.copy(&FP8::new_fp4(&y1));
		c.times_i();
	}	

	A.add(B);
	return FP24::new_fp8s(&a,&b,&c);
}

#[allow(non_snake_case)]
/* Optimal R-ate pairing */
pub fn ate(P: &ECP4,Q: &ECP) -> FP24 {
	let x = BIG::new_ints(&rom::CURVE_BNX);
	let n = BIG::new_copy(&x);

	let mut n3 = BIG::new_copy(&n);
	n3.pmul(3);
	n3.norm();

	let qx=FP::new_copy(&Q.getpx());
	let qy=FP::new_copy(&Q.getpy());

	let mut A=ECP4::new();
	let mut r=FP24::new_int(1);

	A.copy(&P);
	let mut NP=ECP4::new();
	NP.copy(&P);
	NP.neg();

	let nb=n3.nbits();

	for i in (1..nb-1).rev() {
		r.sqr();		
		let mut lv=linedbl(&mut A,&qx,&qy);
		r.smul(&lv,ecp::SEXTIC_TWIST);
		let bt=n3.bit(i)-n.bit(i);
		if bt==1 {
			lv=lineadd(&mut A,P,&qx,&qy);
			r.smul(&lv,ecp::SEXTIC_TWIST);
		}		
		if bt == -1 {

			lv=lineadd(&mut A,&NP,&qx,&qy);
			r.smul(&lv,ecp::SEXTIC_TWIST);	

		}
	}

	if ecp::SIGN_OF_X == ecp::NEGATIVEX {
		r.conj();			
	}

	return r;
}

#[allow(non_snake_case)]
/* Optimal R-ate double pairing e(P,Q).e(R,S) */
pub fn ate2(P: &ECP4,Q: &ECP,R: &ECP4,S: &ECP) -> FP24 {
	let x = BIG::new_ints(&rom::CURVE_BNX);
	let n = BIG::new_copy(&x);
	
	let mut n3 = BIG::new_copy(&n);
	n3.pmul(3);
	n3.norm();


	let qx=FP::new_copy(&Q.getpx());
	let qy=FP::new_copy(&Q.getpy());

	let sx=FP::new_copy(&S.getpx());
	let sy=FP::new_copy(&S.getpy());

	let mut A=ECP4::new();
	let mut B=ECP4::new();
	let mut r=FP24::new_int(1);

	A.copy(&P);
	B.copy(&R);

	let mut NP=ECP4::new();
	NP.copy(&P);
	NP.neg();
	let mut NR=ECP4::new();
	NR.copy(&R);
	NR.neg();

	let nb=n3.nbits();

	for i in (1..nb-1).rev() {
		r.sqr();		
		let mut lv=linedbl(&mut A,&qx,&qy);
		r.smul(&lv,ecp::SEXTIC_TWIST);
		lv=linedbl(&mut B,&sx,&sy);
		r.smul(&lv,ecp::SEXTIC_TWIST);
		let bt=n3.bit(i)-n.bit(i);		
		if bt == 1 {
			lv=lineadd(&mut A,P,&qx,&qy);
			r.smul(&lv,ecp::SEXTIC_TWIST);
			lv=lineadd(&mut B,R,&sx,&sy);
			r.smul(&lv,ecp::SEXTIC_TWIST);
		}
		if bt == -1 {

			lv=lineadd(&mut A,&NP,&qx,&qy);
			r.smul(&lv,ecp::SEXTIC_TWIST);
			lv=lineadd(&mut B,&NR,&sx,&sy);
			r.smul(&lv,ecp::SEXTIC_TWIST);

		}
	}

	if ecp::SIGN_OF_X == ecp::NEGATIVEX {
		r.conj();			
	}

	return r;
}

/* final exponentiation - keep separate for multi-pairings and to avoid thrashing stack */
pub fn fexp(m: &FP24) -> FP24 {
	let f = FP2::new_bigs(&BIG::new_ints(&rom::FRA),&BIG::new_ints(&rom::FRB));
	let mut x = BIG::new_ints(&rom::CURVE_BNX);
	let mut r=FP24::new_copy(m);
		
/* Easy part of final exp */
	let mut lv=FP24::new_copy(&r);
	lv.inverse();
	r.conj();

	r.mul(&lv);
	lv.copy(&r);
	r.frob(&f,4);
	r.mul(&lv);

/* Hard part of final exp */
// Ghamman & Fouotsa Method


	let mut t7=FP24::new_copy(&r); t7.usqr();
	let mut t1=t7.pow(&mut x);

	x.fshr(1);
	let mut t2=t1.pow(&mut x);
	x.fshl(1);

	if ecp::SIGN_OF_X==ecp::NEGATIVEX {
		t1.conj();
	}
	let mut t3=FP24::new_copy(&t1); t3.conj();
	t2.mul(&t3);
	t2.mul(&r);


	t3.copy(&t2.pow(&mut x));
	let mut t4=t3.pow(&mut x);
	let mut t5=t4.pow(&mut x);

	if ecp::SIGN_OF_X==ecp::NEGATIVEX {
		t3.conj(); t5.conj();
	}

	t3.frob(&f,6); t4.frob(&f,5);
	t3.mul(&t4);

	let mut t6=t5.pow(&mut x);
	if ecp::SIGN_OF_X==ecp::NEGATIVEX {
		t6.conj();
	}

	t5.frob(&f,4);
	t3.mul(&t5);

	let mut t0=FP24::new_copy(&t2); t0.conj();
	t6.mul(&t0);

	t5.copy(&t6);
	t5.frob(&f,3);

	t3.mul(&t5);
	t5.copy(&t6.pow(&mut x));
	t6.copy(&t5.pow(&mut x));

	if ecp::SIGN_OF_X==ecp::NEGATIVEX {
		t5.conj();
	}

	t0.copy(&t5);
	t0.frob(&f,2);
	t3.mul(&t0);
	t0.copy(&t6);
	t0.frob(&f,1);

	t3.mul(&t0);
	t5.copy(&t6.pow(&mut x));

	if ecp::SIGN_OF_X==ecp::NEGATIVEX {
		t5.conj();
	}
	t2.frob(&f,7);

	t5.mul(&t7);
	t3.mul(&t2);
	t3.mul(&t5);

	r.mul(&t3);


	r.reduce();
	return r;
}

#[allow(non_snake_case)]
/* GLV method */
fn glv(e: &BIG) -> [BIG;2] {
	let mut u:[BIG;2]=[BIG::new(),BIG::new()];
	let q=BIG::new_ints(&rom::CURVE_ORDER);
	let mut x=BIG::new_ints(&rom::CURVE_BNX);
	let x2=BIG::smul(&x,&x);
	x.copy(&BIG::smul(&x2,&x2));
	u[0].copy(&e);  
	u[0].rmod(&x);
	u[1].copy(&e); 
	u[1].div(&x);
	u[1].rsub(&q);
	
	return u;
}

#[allow(non_snake_case)]
/* Galbraith & Scott Method */
pub fn gs(e: &BIG) -> [BIG;8] {
	let mut u:[BIG;8]=[BIG::new(),BIG::new(),BIG::new(),BIG::new(),BIG::new(),BIG::new(),BIG::new(),BIG::new()];
	let q=BIG::new_ints(&rom::CURVE_ORDER);	
	let x=BIG::new_ints(&rom::CURVE_BNX);
	let mut w=BIG::new_copy(&e);
	for i in 0..7 {
		u[i].copy(&w);
		u[i].rmod(&x);
		w.div(&x);
	}
	u[7].copy(&w);
	if ecp::SIGN_OF_X == ecp::NEGATIVEX {
		let mut t=BIG::new();	
		t.copy(&BIG::modneg(&mut u[1],&q));
		u[1].copy(&t);
		t.copy(&BIG::modneg(&mut u[3],&q));	
		u[3].copy(&t);	
		t.copy(&BIG::modneg(&mut u[5],&q));
		u[5].copy(&t);
		t.copy(&BIG::modneg(&mut u[7],&q));	
		u[7].copy(&t);			
	}
	return u;
}	

#[allow(non_snake_case)]
/* Multiply P by e in group G1 */
pub fn g1mul(P: &ECP,e: &mut BIG) -> ECP {
	let mut R=ECP::new();
	if rom::USE_GLV {
	//	P.affine();
		R.copy(P);
		let mut Q=ECP::new();
		Q.copy(P);
		let q=BIG::new_ints(&rom::CURVE_ORDER);
		let mut cru=FP::new_big(&BIG::new_ints(&rom::CURVE_CRU));
		let mut u=glv(e);
		Q.mulx(&mut cru);

		let mut np=u[0].nbits();
		let mut t:BIG=BIG::modneg(&mut u[0],&q);
		let mut nn=t.nbits();
		if nn<np {
			u[0].copy(&t);
			R.neg();
		}

		np=u[1].nbits();
		t=BIG::modneg(&mut u[1],&q);
		nn=t.nbits();
		if nn<np {
			u[1].copy(&t);
			Q.neg();
		}
		u[0].norm();
		u[1].norm();
		R=R.mul2(&u[0],&mut Q,&u[1]);
			
	} else {
		R=P.mul(e);
	}
	return R;
}

#[allow(non_snake_case)]
/* Multiply P by e in group G2 */
pub fn g2mul(P: &ECP4,e: &BIG) -> ECP4 {
	let mut R=ECP4::new();
	if rom::USE_GS_G2 {
		let mut Q:[ECP4;8]=[ECP4::new(),ECP4::new(),ECP4::new(),ECP4::new(),ECP4::new(),ECP4::new(),ECP4::new(),ECP4::new()];
		let q=BIG::new_ints(&rom::CURVE_ORDER);
		let mut u=gs(e);
		let mut T=ECP4::new();

		let f=ECP4::frob_constants();

		let mut t=BIG::new();
		//P.affine();
		Q[0].copy(&P);
		for i in 1..8 {
			T.copy(&Q[i-1]);
			Q[i].copy(&T);
			Q[i].frob(&f,1);
		}
		for i in 0..8 {
			let np=u[i].nbits();
			t.copy(&BIG::modneg(&mut u[i],&q));
			let nn=t.nbits();
			if nn<np {
				u[i].copy(&t);
				Q[i].neg();
			}
			u[i].norm();
		}

		R.copy(&ECP4::mul8(&mut Q,&u));

	} else {
		R.copy(&P.mul(e));
	}
	return R;
}

/* f=f^e */
/* Note that this method requires a lot of RAM! Better to use compressed XTR method, see FP4.java */
pub fn gtpow(d: &FP24,e: &BIG) -> FP24 {
	let mut r=FP24::new();
	if rom::USE_GS_GT {
		let mut g:[FP24;8]=[FP24::new(),FP24::new(),FP24::new(),FP24::new(),FP24::new(),FP24::new(),FP24::new(),FP24::new()];
		let f = FP2::new_bigs(&BIG::new_ints(&rom::FRA),&BIG::new_ints(&rom::FRB));
		let q=BIG::new_ints(&rom::CURVE_ORDER);
		let mut t=BIG::new();
		let mut u=gs(e);
		let mut w=FP24::new();

		g[0].copy(&d);
		for i in 1..8 {
			w.copy(&g[i-1]);
			g[i].copy(&w);
			g[i].frob(&f,1);
		}
		for i in 0..8 {
			let np=u[i].nbits();
			t.copy(&BIG::modneg(&mut u[i],&q));
			let nn=t.nbits();
			if nn<np {
				u[i].copy(&t);
				g[i].conj();
			}
			u[i].norm();			
		}
		r.copy(&FP24::pow8(&mut g,&u));
	} else {
		r.copy(&d.pow(e));
	}
	return r;
}
