/* map3d.c */

#include <stdio.h>
#include <math.h>
#include <strings.h>
#include <fcntl.h>
#include <unistd.h>

#include "vm.h"

#define NLIGHTS 16

#define PANMAX 127
#define PANRANGE M_PI_2
#define PANOFFSET 128
#define TILTMAX 127
#define TILTRANGE M_PI_4
#define TILTOFFSET 128

#define MAP3D_FILENAME ".map3d.caldata"

struct light {
	double M[4][3];
	double cp[3][3];
	double pan[3];
	double tilt[3];
};

struct light map3d_cal[NLIGHTS];

#define MAG(x) (sqrt(SQUARE(x[0]) + SQUARE(x[1]) + SQUARE(x[2])))
#define SQUARE(x) ((x) * (x))
#define PYTHAG3(a, b) sqrt(SQUARE(a[0]-b[0]) + SQUARE(a[1]-b[1]) + SQUARE(a[2]-b[2]))

#define DEG(x) (180 * (x) / M_PI)

void normalise(double *v)
{
	double mag;
	int i;

	mag = MAG(v);

	for (i = 0; i < 3; i++)
		v[i] /= mag;
}

#define MM map3d_cal[light].M

void multiply(int light, double *i, double *o) {
	o[0] = i[0] * MM[0][0] + i[1] * MM[1][0] + i[2] * MM[2][0] + MM[3][0];
	o[1] = i[0] * MM[0][1] + i[1] * MM[1][1] + i[2] * MM[2][1] + MM[3][1];
	o[2] = i[0] * MM[0][2] + i[1] * MM[1][2] + i[2] * MM[2][2] + MM[3][2];
}

#undef MM

void map3d_init(void)
{
}

void map3d_close(void)
{
}

void map3d_save(void)
{
	int fd, rv;

	fd = open(MAP3D_FILENAME, O_WRONLY | O_CREAT, 0666);
	rv = write(fd, map3d_cal, sizeof(map3d_cal));
	if (rv != sizeof(map3d_cal))
		printf("Warning: Calibration data not saved correctly\n");
	close(fd);
}

int map3d_load(void)
{
	int fd, rv;

	fd = open(MAP3D_FILENAME, O_RDONLY, 0666);
	if (!fd)
		return 0;
	rv = read(fd, map3d_cal, sizeof(map3d_cal));
	if (rv != sizeof(map3d_cal))
		printf("Warning: Calibration data not read correctly\n");
	close(fd);
	return 1;
}

void map3d_transform(int light, double x, double y, double z,
    int *pan, int *tilt)
{
	double pv[3];
	double rv[3];
	double p, t;

//	printf("Transforming for light %d: (%f, %f, %f)\n", light, x, y, z);
	fflush(stdout);
	pv[0] = x;
	pv[1] = y;
	pv[2] = z;
	multiply(light, pv, rv);
	normalise(rv);
	t = asin(rv[1]);
	p = asin(rv[0]/cos(t));

	*pan = (int)round((p * PANMAX)/PANRANGE) + PANOFFSET;
	*tilt = (int)round((t * TILTMAX)/TILTRANGE) + TILTOFFSET;
	if (*pan < 0)
		*pan = 0;
	if (*pan > 255)
		*pan = 255;
	if (*tilt < 0)
		*tilt = 0;
	if (*tilt > 255)
		*tilt = 255;
//	printf("pan = %d, tilt = %d\n", *pan, *tilt);
}

void map3d_setcal(int light, int n, double x, double y, double z,
	int pan, int tilt)
{
	printf("setcal(%d, %d, %f, %f, %f, %d, %d)\n", light, n, x, y, z, pan, tilt);
	map3d_cal[light].cp[n][0] = x;
	map3d_cal[light].cp[n][1] = y;
	map3d_cal[light].cp[n][2] = z;
	map3d_cal[light].pan[n] = PANRANGE * ((double)pan - PANOFFSET) / PANMAX;
	map3d_cal[light].tilt[n] = TILTRANGE * ((double)tilt - TILTOFFSET)
	    / TILTMAX;
	printf("Setcal: pan = %f, tilt = %f\n", map3d_cal[light].pan[n], map3d_cal[light].tilt[n]);
}

void unitvector(double *vec, double pan, double tilt)
{
	double tmp[3];
	double cosp, sinp, cost, sint;

	/* Precalculate some values */
	cosp = cos(pan);
	sinp = sin(pan);
	cost = cos(tilt);
	sint = sin(tilt);

	/* Start with a unit vector */
	vec[0] = 0;
	vec[1] = 0;
	vec[2] = 1;

	/* Rotate around X axis (tilt) */
	tmp[0] = vec[0];
	tmp[1] = vec[1]*cost + vec[2]*sint;
	tmp[2] = vec[2]*cost - vec[1]*sint;

	/* Rotate around Y axis (pan) */
	vec[0] = tmp[0]*cosp + tmp[2]*sinp;
	vec[1] = tmp[1];
	vec[2] = tmp[2]*cosp - tmp[0]*sinp;
}

double dotproduct(double *a, double *b)
{
	return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
}

void crossproduct(double *a, double *b, double *r)
{
	r[0] = a[1]*b[2] - a[2]*b[1];
	r[1] = a[2]*b[0] - a[0]*b[2];
	r[2] = a[0]*b[1] - a[1]*b[0];
}

void dumpmatrix(double sim[12][13]) {
        int i, j;

	for (i = 0; i < 12; i++) {
		for (j = 0; j < 13; j++)
			printf("\t%f", sim[i][j]);
		printf("\n");
	}
	printf("\n");
}

void eliminate(double sim[12][13], double *var)
{
	int i, j, k, maxpos;
	double max, val, tmp, x;

	dumpmatrix(sim);

        for (i = 0; i < 12; i++) {
                /* Find the maximum element in the ith column */
                max = 0.0;
                maxpos = i;
                for (j = i; j < 12; j++) {
                        val = fabs(sim[j][i]);
                        if (val > max) {
                                max = val;
                                maxpos = j;
                        }
                }
                if (maxpos != i)
                        for (j = 0; j < 13; j++) {
                                tmp = sim[maxpos][j];
                                sim[maxpos][j] = sim[i][j];
                                sim[i][j] = tmp;
                        }
                if (fabs(sim[i][i]) < 0.0001)
                        printf("Warning: Lost accuracy at row %d\n", i);
                for (j = i+1; j < 12; j++) {
                        if (fabs(sim[j][i]) > 0.0) {
                                /*
                                 * Subtract x times row i from row j,
                                 * where x is chosen such that
                                 * sim[i][i] * x = sim[j][i]
                                 */
                                x = sim[j][i] / sim[i][i];
                                for (k = 0; k < 13; k++)
                                        sim[j][k] -= x*sim[i][k];
                        }
                }
        }

	dumpmatrix(sim);

        /* Next, substitute in the unknowns. */
        for (i = 12-1; i >= 0; i--) {
                var[i] = sim[i][12];
                for (j = i+1; j < 12; j++)
                        var[i] -= var[j] * sim[i][j];
                var[i] /= sim[i][i];
        }
}

/* In: Guess, length, angle, space for solutions */
void solvetriangle(double c, double a, double A, double *solp, double *soln)
{
        double root;
        double cosA;
        double med;

        cosA = cos(A);
        med = 4*c*c*cosA*cosA - 4*(c*c-a*a);
        root = sqrt(med);

        if (isnan(root))
                /* sqrt() of a negative number */
                if (med > -0.000000001)
                        root = 0;

        *solp = (2*c*cosA + root)/2;
        *soln = (2*c*cosA - root)/2;
}

double try(int n, double dguess, double a, double b, double c, double A, double B, double C)
{
	double s1, s2;
	double e, f, d;

	solvetriangle(dguess, a, A, &s1, &s2);
	e = (n>=4?s2:s1);
	n = n % 4;
	solvetriangle(e, c, C, &s1, &s2);
	f = (n>=2?s2:s1);
	n = n % 2;
	solvetriangle(f, b, B, &s1, &s2);
	d = (n>=1?s2:s1);

	return d;
}

void getsolution(int n, double dguess, double a, double b, double c, double A, double B, double C, double *e, double *f)
{
	double s1, s2;

	solvetriangle(dguess, a, A, &s1, &s2);
	*e = (n>=4?s2:s1);
	n = n % 4;
	solvetriangle(*e, c, C, &s1, &s2);
	*f = (n>=2?s2:s1);
}

#define DINCR 0.005

/*
 * A is angle opposite a, between d and e
 * B is angle opposite b, between d and f
 * C is angle opposite c, between e and f
 */
int trysolvetetra(int n, double a, double b, double c,
		  double A, double B, double C,
		  double *dr, double *er, double *fr)
{
	double dguess;
	double thresh = 0.000000000001;
	double d, lastd;
	double mind, maxd;
	double d1, d2, d3;
	double e, f;
	int found;

	lastd = 0.0/0.0; /* NaN */
	mind = maxd = 0; /* Shut up compiler */

	/* Find a bracket */
	found = 0;
	for (dguess = 0.0; dguess < 100.0; dguess+=DINCR) {
		d = try(n, dguess, a, b, c, A, B, C) - dguess;
		if (((d >= 0) && (lastd < 0)) || ((d <= 0) && (lastd > 0))) {
			mind = dguess - DINCR;
			maxd = dguess;
			printf("Found bracket: (%f, %f)\n", mind, maxd);
			found = 1;
			break;
		}
		lastd = d;
	}
	if (!found)
		return 0;

	while ((maxd-mind) > thresh) {
		d1 = try(n, mind, a, b, c, A, B, C) - mind;
		d2 = try(n, (mind+maxd)/2, a, b, c, A, B, C) - (mind+maxd)/2;
		d3 = try(n, maxd, a, b, c, A, B, C) - maxd;
		if (((d2 >= 0) && (d1 < 0)) || ((d2 < 0) && (d1 >= 0)))
			maxd = (mind+maxd)/2;
		else
			mind = (mind+maxd)/2;
	}

	d = (mind+maxd)/2;

	getsolution(n, d, a, b, c, A, B, C, &e, &f);

	printf("Found solution: (%f, %f, %f)\n", d, e, f);

	*dr = d;
	*er = e;
	*fr = f;

	if (!isfinite(d))
		return 0;
	if (!isfinite(e))
		return 0;
	if (!isfinite(f))
		return 0;
	if (d < 0)
		return 0;
	if (e < 0)
		return 0;
	if (f < 0)
		return 0;
	return 1;
}

int solvetetra(double a, double b, double c,
	       double A, double B, double C,
	       double *dr, double *er, double *fr)
{
	int i;

	for (i = 0; i < 8; i++)
		if (trysolvetetra(i, a, b, c, A, B, C, dr, er, fr))
			return 1;
	return 0;
}

int map3d_calibrate(int light)
{
	double v1[3], v2[3], v3[3], p4[3];
	double angle1, angle2, angle3;
	double a, b, c, d, e, f;
	double pAB[3], pBC[3];
	double axis1[3], axis2[3], axis3[3];
	double axm1, axm2, axm3;
	double hypot, alpha, beta, gamma, delta;
	double sim[12][13];
	double var[12];
	double pv[3], tv[3];
	int success, i;

	/*
	 * First, create unit vectors in the directions of the
	 * pan & tilt values given
	 */
	unitvector(v1, map3d_cal[light].pan[0], map3d_cal[light].tilt[0]);
	unitvector(v2, map3d_cal[light].pan[1], map3d_cal[light].tilt[1]);
	unitvector(v3, map3d_cal[light].pan[2], map3d_cal[light].tilt[2]);

	/*
	 * Now, we need the angles between them
	 */
	angle1 = acos(dotproduct(v1, v2));
	angle2 = acos(dotproduct(v2, v3));
	angle3 = acos(dotproduct(v3, v1));

	printf("angles (%f, %f, %f)\n", DEG(angle1), DEG(angle2), DEG(angle3));

	/*
	 * And the lengths of the edges which we know
	 */
	a = PYTHAG3(map3d_cal[light].cp[0], map3d_cal[light].cp[1]);
	b = PYTHAG3(map3d_cal[light].cp[1], map3d_cal[light].cp[2]);
	c = PYTHAG3(map3d_cal[light].cp[2], map3d_cal[light].cp[0]);

	/* Solve the tetrahedron */
	success = solvetetra(a, b, c, angle1, angle2, angle3, &d, &e, &f);
	if (!success) {
		printf("failed to solve tetrahedron\n");
		return 0;
	}

	/* Multiply the vectors by their magnitudes */
	for (i = 0; i < 3; i++) {
		v1[i] *= e;
		v2[i] *= d;
		v3[i] *= f;
	}

	/*
	 * Find two vectors to define the triangle between
	 * the calibration points
	 */
	for (i = 0; i < 3; i++) {
		pAB[i] = map3d_cal[light].cp[1][i]-map3d_cal[light].cp[0][i];
		pBC[i] = map3d_cal[light].cp[2][i]-map3d_cal[light].cp[1][i];
	}

	/*
	 * Create some perpendicular vectors in terms of which we can
	 * calculate a vector to the fourth point
	 */
	axis1[0] = pAB[0];
	axis1[1] = pAB[1];
	axis1[2] = pAB[2];
	normalise(axis1);
	crossproduct(axis1, pBC, axis2);
	normalise(axis2);
	crossproduct(axis1, axis2, axis3);
	normalise(axis3);

	/*
	 * Now we do some trigonometry to find out the distance to
	 * the fourth point in terms of the three axes
	 */
	beta = asin(d*sin(angle1)/a);
	gamma = asin(f*sin(angle3)/c);
	delta = acos(((a*a)+(c*c)-(b*b))/(2*a*c));

	alpha = acos((cos(gamma)-cos(beta)*cos(delta))/(sin(beta)*sin(delta)));

	hypot = e*sin(beta);
	axm1 = e*cos(beta);
	axm2 = hypot*sin(alpha);
	axm3 = hypot*cos(alpha);

	/* Now we have the magnitudes, let's get the vectors */
	for (i = 0; i < 3; i++) {
		axis1[i] *= axm1;
		axis2[i] *= axm2;
		axis3[i] *= axm3;
	}

	/*
	 * Now we can simply add these vectors to point A
	 * to get the fourth point
	 */
	for (i = 0; i < 3; i++)
		p4[i] = map3d_cal[light].cp[0][i] + axis1[i] +
		    axis2[i] + axis3[i];

	/*
	 * Now we can construct a matrix to represent 12 simultaneous
	 * equations, which we then solve to get our transformation matrix.
	 */
	bzero(sim, sizeof(sim));

	/* First point */
	sim[0][0] = map3d_cal[light].cp[0][0];
	sim[0][1] = map3d_cal[light].cp[0][1];
	sim[0][2] = map3d_cal[light].cp[0][2];
	sim[0][9] = 1;
	sim[0][12] = v1[0];

	sim[1][3] = map3d_cal[light].cp[0][0];
	sim[1][4] = map3d_cal[light].cp[0][1];
	sim[1][5] = map3d_cal[light].cp[0][2];
	sim[1][10] = 1;
	sim[1][12] = v1[1];

	sim[2][6] = map3d_cal[light].cp[0][0];
	sim[2][7] = map3d_cal[light].cp[0][1];
	sim[2][8] = map3d_cal[light].cp[0][2];
	sim[2][11] = 1;
	sim[2][12] = v1[2];

	/* Second point */
	sim[3][0] = map3d_cal[light].cp[1][0];
	sim[3][1] = map3d_cal[light].cp[1][1];
	sim[3][2] = map3d_cal[light].cp[1][2];
	sim[3][9] = 1;
	sim[3][12] = v2[0];

	sim[4][3] = map3d_cal[light].cp[1][0];
	sim[4][4] = map3d_cal[light].cp[1][1];
	sim[4][5] = map3d_cal[light].cp[1][2];
	sim[4][10] = 1;
	sim[4][12] = v2[1];

	sim[5][6] = map3d_cal[light].cp[1][0];
	sim[5][7] = map3d_cal[light].cp[1][1];
	sim[5][8] = map3d_cal[light].cp[1][2];
	sim[5][11] = 1;
	sim[5][12] = v2[2];

	/* Third point */
	sim[6][0] = map3d_cal[light].cp[2][0];
	sim[6][1] = map3d_cal[light].cp[2][1];
	sim[6][2] = map3d_cal[light].cp[2][2];
	sim[6][9] = 1;
	sim[6][12] = v3[0];

	sim[7][3] = map3d_cal[light].cp[2][0];
	sim[7][4] = map3d_cal[light].cp[2][1];
	sim[7][5] = map3d_cal[light].cp[2][2];
	sim[7][10] = 1;
	sim[7][12] = v3[1];

	sim[8][6] = map3d_cal[light].cp[2][0];
	sim[8][7] = map3d_cal[light].cp[2][1];
	sim[8][8] = map3d_cal[light].cp[2][2];
	sim[8][11] = 1;
	sim[8][12] = v3[2];

	/* Fourth point */
	sim[9][0] = p4[0];
	sim[9][1] = p4[1];
	sim[9][2] = p4[2];
	sim[9][9] = 1;
	sim[9][12] = 0;

	sim[10][3] = p4[0];
	sim[10][4] = p4[1];
	sim[10][5] = p4[2];
	sim[10][10] = 1;
	sim[10][12] = 0;

	sim[11][6] = p4[0];
	sim[11][7] = p4[1];
	sim[11][8] = p4[2];
	sim[11][11] = 1;
	sim[11][12] = 0;

	eliminate(sim, var);

	map3d_cal[light].M[0][0] = var[0];
	map3d_cal[light].M[1][0] = var[1];
	map3d_cal[light].M[2][0] = var[2];
	map3d_cal[light].M[0][1] = var[3];
	map3d_cal[light].M[1][1] = var[4];
	map3d_cal[light].M[2][1] = var[5];
	map3d_cal[light].M[0][2] = var[6];
	map3d_cal[light].M[1][2] = var[7];
	map3d_cal[light].M[2][2] = var[8];
	map3d_cal[light].M[3][0] = var[9];
	map3d_cal[light].M[3][1] = var[10];
	map3d_cal[light].M[3][2] = var[11];

	printf("%f\t%f\t%f\t\t%f\n", var[0], var[3], var[6], var[9]);
	printf("%f\t%f\t%f\t\t%f\n", var[1], var[4], var[7], var[10]);
	printf("%f\t%f\t%f\t\t%f\n", var[2], var[5], var[8], var[11]);

	printf("Calibration points are:\n");
	multiply(light, map3d_cal[light].cp[0], tv);
	printf("(%f, %f, %f) => (%f, %f, %f)\n", map3d_cal[light].cp[0][0], map3d_cal[light].cp[0][1], map3d_cal[light].cp[0][2], tv[0], tv[1], tv[2]);
	multiply(light, map3d_cal[light].cp[1], tv);
	printf("(%f, %f, %f) => (%f, %f, %f)\n", map3d_cal[light].cp[1][0], map3d_cal[light].cp[1][1], map3d_cal[light].cp[1][2], tv[0], tv[1], tv[2]);
	multiply(light, map3d_cal[light].cp[2], tv);
	printf("(%f, %f, %f) => (%f, %f, %f)\n", map3d_cal[light].cp[2][0], map3d_cal[light].cp[2][1], map3d_cal[light].cp[2][2], tv[0], tv[1], tv[2]);
	multiply(light, p4, tv);
	printf("(%f, %f, %f) => (%f, %f, %f)\n", p4[0], p4[1], p4[2], tv[0], tv[1], tv[2]);
	for (i = 0; i < 10; i++) {
		pv[0] = 0;
		pv[1] = (double)i/10.0;
		pv[2] = 0;
		multiply(light, pv, tv);
		printf("(%f, %f, %f) => (%f, %f, %f)\n", pv[0], pv[1], pv[2], tv[0], tv[1], tv[2]);
	}
	return 1;
}

/* Set parameters based on position (x, y, z) and direction (tx, ty, tz) */
/* Temp: x, y = pan, tilt measured to (0, 0, 0)
         tx, ty = pan, tilt of light fixture
	 tz = distance from light fixture to (0, 0, 0)
 */
void map3d_setparams(int light, int opan, int otilt, double lpan, double ltilt, double dist)
{
	double n[3];
	double up[3];
	double right[3];
	double v[3];
	double op, ot;

	op = PANRANGE * ((double)opan - PANOFFSET) / PANMAX;
	ot = TILTRANGE * ((double)otilt - TILTOFFSET) / TILTMAX;

	unitvector(v, op, ot);
	unitvector(n, lpan, ltilt);

	up[0] = 0;
	up[1] = (n[1] + sqrt(n[1]*n[1]+4*n[2]*n[2])) /
		(4*n[2]);
	up[2] = -up[1]*n[1]/n[2];
	normalise(up);

	crossproduct(n, up, right);
	normalise(right);

	printf("n:\t%f\t%f\t%f\n", n[0], n[1], n[2]);
	printf("up:    \t%f\t%f\t%f\n", up[0], up[1], up[2]);
	printf("right: \t%f\t%f\t%f\n", right[0], right[1], right[2]);
	printf("\n");

	/* Construct matrix */

	map3d_cal[light].M[0][0] = right[0];
	map3d_cal[light].M[0][1] = right[1];
	map3d_cal[light].M[0][2] = right[2];
	map3d_cal[light].M[1][0] = up[0];
	map3d_cal[light].M[1][1] = up[1];
	map3d_cal[light].M[1][2] = up[2];
	map3d_cal[light].M[2][0] = n[0];
	map3d_cal[light].M[2][1] = n[1];
	map3d_cal[light].M[2][2] = n[2];
	map3d_cal[light].M[3][0] = dist*v[0];
	map3d_cal[light].M[3][1] = dist*v[1];
	map3d_cal[light].M[3][2] = dist*v[2];

	printf("\t%f\t%f\t%f\t\t%f\n", map3d_cal[light].M[0][0], map3d_cal[light].M[0][1], map3d_cal[light].M[0][2], map3d_cal[light].M[3][0]);
	printf("\t%f\t%f\t%f\t\t%f\n", map3d_cal[light].M[1][0], map3d_cal[light].M[1][1], map3d_cal[light].M[1][2], map3d_cal[light].M[3][1]);
	printf("\t%f\t%f\t%f\t\t%f\n", map3d_cal[light].M[2][0], map3d_cal[light].M[2][1], map3d_cal[light].M[2][2], map3d_cal[light].M[3][2]);
	printf("\n");

}
