#include <iostream>
#include <time.h>
#include <math.h>
#include "graph_holder.h"

quaternion multiply(quaternion x, quaternion y);
quaternion reduce(quaternion x, int p);
void divide (quaternion arg1, quaternion arg2, quaternion remainder, quaternion divisor);
quaternion add( quaternion x, quaternion y);
quaternion subtract( quaternion x, quaternion y);


quaternion::quaternion() {
	q[0]=0; q[1]=0; q[2]=0; q[3]=0;

}

quaternion::quaternion(int n1, int n2, int n3, int n4) {
			q[0] = n1;
			q[1] = n2;
			q[2] = n3;
			q[3] = n4;
}

int equals_zero(quaternion x) {
int r;
if (x.q[0] == 0 && x.q[1] == 0 && x.q[2]==0 && x.q[3] == 0)
	r=1;
else
	r=0;
return r;
}
quaternion conjugate( quaternion x) {
	quaternion output;
	output.q[0] = x.q[0];
	output.q[1] = -x.q[1];
	output.q[2] = -x.q[2];
	output.q[3] = -x.q[3];

	return output;

}

int norm (quaternion x) {
int out;
out = x.q[0]*x.q[0]+x.q[1]*x.q[1]+x.q[2]*x.q[2]+ x.q[3]*x.q[3];
return out;
}

quaternion add( quaternion x, quaternion y) {
	quaternion out;
	out.q[0] = x.q[0]+y.q[0];
	out.q[1] = x.q[1]+y.q[1];
	out.q[2] = x.q[2]+y.q[2];
	out.q[3] = x.q[3]+y.q[3];
 return out;
}

quaternion subtract( quaternion x, quaternion y) {
	quaternion out;
	out.q[0] = x.q[0]-y.q[0];
	out.q[1] = x.q[1]-y.q[1];
	out.q[2] = x.q[2]-y.q[2];
	out.q[3] = x.q[3]-y.q[3];
	return out;

}

void quaternion::print_quaternion() {
	int i;
	std::cout << "\n";
	for (i=0;i<4;i++)
		std::cout << " " << q[i]; 
}

quaternion reduce(quaternion x, int p) {
	int i;
	quaternion output;
	for (i=0;i<4;i++) 
		output.q[i] = x.q[i] % p;
	return output;

}



quaternion multiply(quaternion x, quaternion y)	{
	
	quaternion result;
	result.q[0] = x.q[0]*y.q[0] - x.q[1]*y.q[1] - x.q[2]*y.q[2] - x.q[3]*y.q[3];
	result.q[1] = x.q[0]*y.q[1] + x.q[1]*y.q[0] + x.q[2]*y.q[3] - x.q[3]*y.q[2];
    result.q[2] = x.q[0]*y.q[2] - x.q[1]*y.q[3] + x.q[2]*y.q[0] + x.q[3]*y.q[1];
	result.q[3] = x.q[0]*y.q[3] + x.q[1]*y.q[2] - x.q[2]*y.q[1] + x.q[3]*y.q[0];
	return (result);

}

quaternion divisor (quaternion arg1, quaternion arg2) {
// arg1 = divisor*arg2 + remainder
// algorithm assumes that arg2 is odd
int i;
int m = norm(arg2); // m must be odd
quaternion div;
std::cout << "\n m is " << m;
quaternion sigma = multiply(arg1,conjugate(arg2));
std::cout <<  "\n sigma: ";
sigma.print_quaternion();
for (i=0;i < 4;i++) {
	if (sigma.q[i] %m <= m/2) {
		std::cout << "\n i = " << i << " , less";
		div.q[i]=sigma.q[i]/m;
	}
	else {
		std::cout << "\n i = " << i << " , greater";
		div.q[i]=1+sigma.q[i]/m;
}
}


return div; 
 
}

quaternion remainder (quaternion arg1, quaternion arg2, quaternion div) {
quaternion rem = subtract(arg1,multiply(div,arg2));
return rem;
}

quaternion gcd(quaternion arg1, quaternion arg2) {
	std::cout << "\n arg1: ";
	arg1.print_quaternion();
	std::cout << "\n arg2: ";
	arg2.print_quaternion();
quaternion ret;	
quaternion div = divisor(arg1,arg2);
quaternion rem = remainder(arg1,arg2,div);
std::cout << "\n div: ";
div.print_quaternion();
std::cout << "\n ret: ";
rem.print_quaternion();

if (equals_zero(rem)==1) {
    ret.q[0] = arg2.q[0];
	ret.q[1] = arg2.q[1];
	ret.q[2] = arg2.q[2];
	ret.q[3] = arg2.q[3];
}
else
ret = gcd(arg2,rem);

return ret;

}

void find_xy (int p, int *x, int *y) {
	// p is an odd prime
	int i,a;
	int *squares;
	squares = new int [p];
	for(i=0;i<p;i++) {
		squares[i] = (i*i)%p;
		std::cout << "\n squares [" << i << "] is " << squares[i];
	}
	a=0;
	*y=p+1;
		for(i=0;i<p;i++) {
		if (squares[i]==p-1){
			std::cout << "-1 is a square mod p!";
			*x=i;
			*y=0;
			break;
		}
		if (a < squares[i]) {
			a=squares[i];
			*x=i;

		}

	}

		if (*y == p+1) {
			for (i=0;i<p;i++) {
				if (squares[i] % p == p-abs((-a-1) % p)) {
					*y=i;
				break;

				}
			}
		}

		delete[] squares;

}

quaternion find_qsquare(int p) {
// p is an odd rational prime
int x,y;
	find_xy(p,&x,&y);
	quaternion alpha(1,x,y,0);
	quaternion ret = gcd(alpha,quaternion(p,0,0,0));
	return ret;
}



