/***************************************************************************
                           side.cpp
                           -------------------
    copyright            : (C) 2004 by Jos van den Oever
    email                : jos@vandenoever.info
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
#include "side.h"
#include "sidelib.h"
#include <QtGui/QPainter>
#include <Qt3Support/Q3Picture>
#include <iostream>
#include <cmath>

using namespace std;

#define SMALL_NUM  0.00000001 // anything that avoids division overflow
Side::Side() : rot(Rotation(0)),
        p1(opoints), p2(opoints+3), p3(opoints+6), p4(opoints+9), pict(0) {
cerr << "Sice()" << endl;
	pict = 0;
}
Side::Side(const double points[9]) : rot(Rotation(0)),
		p1(opoints), p2(opoints+3), p3(opoints+6), p4(opoints+9), pict(0) {
cerr << "Side()" << endl;
	setPoints(points);
}
Side::Side(const Side &s) : QObject(), rot(s.rot),
		p1(opoints), p2(opoints+3), p3(opoints+6), p4(opoints+9), pict(0) {
cerr << "Sive()" << endl;
	*this = s;
}
Side::~Side() {
}
const Side &
Side::operator=(const Side &s) {
	if (&s == this) return s;
	pict = s.pict;
	memcpy(ipoints, s.ipoints, sizeof(double)*12);
	memcpy(opoints, s.opoints, sizeof(double)*12);
	wm=s.wm;
	rot = s.rot;
	number = s.number;
	return s;
}
void
Side::setPoints(const double points[9]) {
	short i;
	// copy three input points
	memcpy(ipoints, points, sizeof(double)*9);
	// calculate fourth point
	for (i=0; i<3; i++) {
		ipoints[i+9] = ipoints[i+3]+ipoints[i+6]-ipoints[i];
	}
}
double
Side::getMaxDistance() const {
	int i;
	double max = 0;
	double value;

	for (i=0; i<12; i+=3) {
		value = ipoints[i]*ipoints[i] + ipoints[i+1]*ipoints[i+1]
		        + ipoints[i+2]*ipoints[i+2];
		if (value > max) {
			max = value;
		}
	}
	return sqrt(max);
}
void
Side::setPicture(const Q3Picture *newpict) {
cerr << "setPicture" << endl;
	newpict->boundingRect(); // should help find a crash when newpict == 0
	pict = newpict;
}
/*void
Side::setPicture(Symmetry s) {
	switch (s) {
	case none:
		break;
	case C1:
		pict = getC1Picture(0);
		break;
	case C2:
		pict = getC2Picture(0);
		break;
	case C4:
		pict = getC4picture(0);
	}
}*/
const Q3Picture *
Side::picture() const {
	return pict;
}
const QMatrix *
Side::matrix() const {
	return &wm;
}
void
Side::updateMatrix(const Matrix3D &m3d) {
	if (pict == NULL) return;
	double x, y, w, h;
	QRect r;

	m3d.transform(ipoints, opoints, 4);
	x = p1[0];
	y = p1[1];
	r = pict->boundingRect();
	w = r.width()-1;
	if (w == 0) w = SMALL_NUM;
	h = r.height()-1;
	if (h == 0) h = SMALL_NUM;
	if (rot%2 == 0) {
		wm.setMatrix((p2[0]-x)/w, (p2[1]-y)/w,
		             (p3[0]-x)/h, (p3[1]-y)/h, x, y);
	} else {
		wm.setMatrix((p2[0]-x)/h, (p2[1]-y)/h,
		             (p3[0]-x)/w, (p3[1]-y)/w, x, y);
	}
}
void
Side::paintSide(QPainter *p) const {
	QRect rect;
	p->save();
	p->setWorldMatrix(wm, true);
	p->rotate(rot*90);
	rect = pict->boundingRect();

/*	QFile f("log.bin");
	f.open(IO_WriteOnly);
	QDataStream ds(&f);
	ds << p->worldMatrix() << *pict << rect.x() << rect.y()
		<< rect.width() << rect.height();
	f.close();
	printf("m11: %.15f, m12: %.15f, m21: %.15f, m22: %.15f, "
			"dx: %.15f, dy: %.15f, rot: %i\n",
		wm.m11(), wm.m12(), wm.m21(), wm.m22(),
		wm.dx(), wm.dy(), rot);
	printf("x: %.15f, y: %.15f, width: %.15f, height: %.15f\n",
		rect.x(), rect.y(), rect.width(), rect.height());
*/
	switch (rot) {
	case 0:
		p->drawPicture(-rect.x(), -rect.y(), *pict);
		p->drawRect(0, 0, rect.width(), rect.height());
		break;
	case 1:
		p->drawPicture(-rect.x(), -rect.y()-rect.height()+1, *pict);
		p->drawRect(0, -rect.height()+1, rect.width(), rect.height());
		break;
	case 2:
		p->drawPicture(-rect.x()-rect.width()+1,
		               -rect.y()-rect.height()+1, *pict);
		p->drawRect(-rect.width()+1, -rect.height()+1,
		            rect.width(), rect.height());
		break;
	case 3:
		p->drawPicture(-rect.x()-rect.width()+1, -rect.y(), *pict);
		p->drawRect(-rect.width()+1, 0, rect.width(), rect.height());
		break;
	}
	p->restore();
}
bool
Side::processClick(const QPoint &p) {
	QPoint ip;
	if (pict == 0 || pict->isNull() || !wm.isInvertible()) {
		return false;
	}
	ip = p*wm.invert(); // hmm
	if (ip.x() >= 0 && ip.y() >= 0 && ip.x() < pict->boundingRect().width()
	        && ip.y() < pict->boundingRect().height()) {
		QRect rect = pict->boundingRect();
		QPoint p(ip.x()+rect.x(), ip.y()+rect.y());
		emit clickedAt(p);
		emit clicked();
		return true;
	}
	return false;
}
/* the three coordinates p1, p2 and p3 span a plane. given an x and y
   coordinate, this function computes the z coordinate.
   The plane is given by equation a*x + b*y + c*z + d = 0
   As it is now, it's a rather expensive computation. I'm sure that can be
   improved upon. */
double
Side::getZcoord(const double x, const double y) const {
	double d, z;
	double a,b,c;
	a = p1[1]*(p2[2]-p3[2]) + p2[1]*(p3[2]-p1[2]) + p3[1]*(p1[2]-p2[2]);
	b = p1[2]*(p2[0]-p3[0]) + p2[2]*(p3[0]-p1[0]) + p3[2]*(p1[0]-p2[0]);
	c = p1[0]*(p2[1]-p3[1]) + p2[0]*(p3[1]-p1[1]) + p3[0]*(p1[1]-p2[1]);
	d = -(p1[0]*(p2[1]*p3[2]-p3[1]*p2[2]) + p2[0]*(p3[1]*p1[2]-p1[1]*p3[2])
	      + p3[0]*(p1[1]*p2[2]-p2[1]*p1[2]));
	if (c == 0) c = SMALL_NUM;
	z = -(a*x + b*y +d)/c;
	return z;
}
int
Side::operator<(const Side &s) const {
	double L1x, L1y, L2x, L2y;
	double a, b, c, d, dn, d1, d2, z, sz;
	short i1, i2, i;
	bool oneside1, oneside2;

	// find the line that desribes the intersection of the two planes
	if (!intersect(s, L1x, L1y, L2x, L2y)) {
		return p2[2]+p3[2] < s.p2[2]+s.p3[2];
	}
	a = L1y-L2y;
	b = L2x-L1x;
	c = L1x*L2y-L1y*L2x;
	dn = sqrt(a*a+b*b);
	if (dn == 0) dn = SMALL_NUM;
	d1 = d2 = i1 = i2 = 0;
	oneside1 = oneside2 = false;
	for (i=0; i<12; i+=3) {
		d = (a*opoints[i]+b*opoints[i+1]+c)/dn;
		if (fabs(d) > SMALL_NUM) {
			if (d1 == 0) {
				oneside1 = true;
				d1 = d;
				i1 = i;
			} else {
				if (d*d1 < 0) {
					oneside1 = false;
				}
			}
			d1 = d;
			i1 = i;
		}
	}
	for (i=0; i<12; i+=3) {
		d = (a*s.opoints[i]+b*s.opoints[i+1]+c)/dn;
		if (fabs(d) > SMALL_NUM) {
			if (d2 == 0) {
				oneside2 = true;
				d2 = d;
				i2 = i;
			} else {
				if (d*d2 < 0) {
					oneside2 = false;
				}
			}
			d2 = d;
			i2 = i;
		}
	}
	if (oneside1) {
		z = opoints[i1+2];
		sz = s.getZcoord(opoints[i1], opoints[i1+1]);
	} else if (oneside2) {
		z = getZcoord(s.opoints[i2], opoints[i2+1]);
		sz = s.opoints[i2+2];
	} else {
		return p2[2]+p3[2] < s.p2[2]+s.p3[2];
	}
	return z < sz;
}
void
uitproduct(const double *a, const double *b, double *c) {
	c[0] = a[1]*b[2]-a[2]*b[1];
	c[1] = a[2]*b[0]-a[0]*b[2];
	c[2] = a[0]*b[1]-a[1]*b[0];
}
bool
Side::intersect(const Side &s, double &L1x, double &L1y, double &L2x, double &L2y) const {
	double n[3], sn[3], nn[3], ax, ay, az;
	double V11[3], V12[3], V21[3], V22[3];

	V11[0] = p2[0]-p1[0];
	V11[1] = p2[1]-p1[1];
	V11[2] = p2[2]-p1[2];
	V12[0] = p3[0]-p1[0];
	V12[1] = p3[1]-p1[1];
	V12[2] = p3[2]-p1[2];
	V21[0] = s.p2[0]-s.p1[0];
	V21[1] = s.p2[1]-s.p1[1];
	V21[2] = s.p2[2]-s.p1[2];
	V22[0] = s.p3[0]-s.p1[0];
	V22[1] = s.p3[1]-s.p1[1];
	V22[2] = s.p3[2]-s.p1[2];
	uitproduct(V11, V12, n);
	uitproduct(V21, V22, sn);
	uitproduct(n, sn, nn);
	ax = fabs(nn[0]);
	ay = fabs(nn[1]);
	az = fabs(nn[2]);
	if (ax+ay+az < SMALL_NUM) {
		// planes are parallel
		return false;
	}
	// planes intersect in a line
	// first determine max abs coordinate of cross product
	int maxc;                      // max coordinate
	if (ax > ay) {
		if (ax > az)
			maxc = 1;
		else
			maxc = 3;
	} else {
		if (ay > az)
			maxc = 2;
		else
			maxc = 3;
	}
	// next, to get a point on the intersect line
	// zero the max coord, and solve for the other two
	double    d1, d2;           // the constants in the 2 plane equations
	d1 = -( n[0]*p1[0]   +  n[1]*p1[1]   + n[2]*p1[2]);
	d2 = -(sn[0]*s.p1[0] + sn[1]*s.p1[1] + sn[2]*s.p1[2]);

	switch (maxc) {            // select max coordinate
	case 1:                    // intersect with x=0
		L1x = 0;
		if (nn[0] == 0) nn[0] = SMALL_NUM;
		L1y = (d2*n[2] - d1*sn[2]) / nn[0];
		break;
	case 2:                    // intersect with y=0
		if (nn[1] == 0) nn[0] = SMALL_NUM;
		L1x = (d1*sn[2] - d2*n[2]) / nn[1];
		L1y = 0;
		break;
	case 3:                    // intersect with z=0
		if (nn[2] == 0) nn[0] = SMALL_NUM;
		L1x = (d2*n[1] - d1*sn[1]) / nn[2];
		L1y = (d1*sn[0] - d2*n[0]) / nn[2];
	}
	L2x = L1x + nn[0];
	L2y = L1y + nn[1];

	return true;
}
void
Side::setRotation(short r) {
	rot = Rotation(r%4);
}
Rotation
Side::getRotation() const {
	return rot;
}
void
Side::setSymmetry(Symmetry sym) {
	this->sym = sym;
}
Symmetry
Side::symmetry() const {
	return sym;
}
void
Side::rotate(Rotation d) {
	rot = Rotation((rot+d)%4);
}
