/**********************************************************************
** Copyright (C) 2000 Trolltech AS.  All rights reserved.
**
** This file is part of Qtopia Environment.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/

#include "calibrate.h"

#include <qpe/resource.h>
#include <qpe/qcopenvelope_qws.h>
#include <qapplication.h>
#include <qfile.h>
#include <qtextstream.h>

#include <qpainter.h>
#include <qwindowsystem_qws.h>
#include <qgfx_qws.h>


Calibrate::Calibrate( QWidget* parent, const char * name, WFlags wf ) :
		QDialog( parent, name, TRUE, wf | WStyle_Tool | WStyle_Customize | WStyle_StaysOnTop )
{
	showCross = true;
	const int offset = 50;
	QRect desk = qApp->desktop() ->geometry();
	setGeometry( 0, 0, desk.width(), desk.height() );
	if ( desk.height() < 250 ) {
		int w = desk.height() / 3;
		logo.convertFromImage( Resource::loadImage( "logo/opielogo" ).smoothScale( w, w ) );
	} else {
		logo = Resource::loadPixmap( "logo/opielogo" );
	}

	cal.xfb[0] = offset;
        cal.yfb[0] = offset;
	cal.xfb[1] = desk.width()-offset;
        cal.yfb[1] = offset;
	cal.xfb[2] = desk.width()-offset;
        cal.yfb[2] = desk.height()-offset;
	cal.xfb[3] = offset;
        cal.yfb[3] = desk.height()-offset;
	cal.xfb[4] = desk.width()/2;
        cal.yfb[4] = desk.height()/2;

	reset();
}

Calibrate::~Calibrate()
{
	store();
}

void Calibrate::reset()
{
	penPos = QPoint();
        location = 0;
	samples = 0;
	crossPos = QPoint(cal.xfb[0], cal.yfb[0]);
}


void Calibrate::show()
{
	grabMouse();
	QWSServer::mouseHandler() ->clearCalibration();
	QDialog::show();
}

void Calibrate::store()
{
#ifndef QT_NO_TEXTSTREAM
    QFile file("/etc/pointercal");
    if ( file.open( IO_WriteOnly ) ) {
	QTextStream t( &file );
	t << cal.a[1] << " " << cal.a[2] << " " << cal.a[0] << " ";
	t << cal.a[4] << " " << cal.a[5] << " " << cal.a[3] << " ";
	t << cal.a[6] << endl;
    } else	
#endif
    {
	qDebug("Could not save calibration");
    }
}

void Calibrate::hide()
{
	if ( isVisible() )
		store();
	// hack - calibrate is a launcher dialog, but treated like a standalone app
	{
		QCopEnvelope e( "QPE/System", "closing(QString)" );
		e << QString ( "calibrate" );
	}
	QDialog::hide();
}

QPoint Calibrate::fromDevice( const QPoint &p )
{

	QPoint np = qt_screen->mapFromDevice ( p, QSize( qt_screen->deviceWidth ( ), qt_screen->deviceHeight() ) );
	qDebug("Calibrate::fromDevice(%d,%d) -> %d,%d", p.x(), p.y(), np.x(), np.y());
	return np;
}

bool Calibrate::performCalculation(void)
{
	int j;
	float n, x, y, x2, y2, xy, z, zx, zy;
	float det, a, b, c, e, f, i;
	float scaling = 65536.0;

	//qDebug("Top left : X = %4d Y = %4d", cal.x[0], cal.y[0]);
	//qDebug("Top right: X = %4d Y = %4d", cal.x[1], cal.y[1]);
	//qDebug("Bot left : X = %4d Y = %4d", cal.x[2], cal.y[2]);
	//qDebug("Bot right: X = %4d Y = %4d", cal.x[3], cal.y[3]);
	//qDebug("Middle: X = %4d Y = %4d", cal.x[4], cal.y[4]);

	// Get sums for matrix
	n = x = y = x2 = y2 = xy = 0;
	for(j=0;j<5;j++) {
		n += 1.0;
		x += (float)cal.x[j];
		y += (float)cal.y[j];
		x2 += (float)(cal.x[j]*cal.x[j]);
		y2 += (float)(cal.y[j]*cal.y[j]);
		xy += (float)(cal.x[j]*cal.y[j]);
	}

	// Get determinant of matrix -- check if determinant is too small
	det = n*(x2*y2 - xy*xy) + x*(xy*y - x*y2) + y*(x*xy - y*x2);
	if(det < 0.1 && det > -0.1) {
		qDebug("determinant is too small -- %f",det);
		return false;
	}

	// Get elements of inverse matrix
	a = (x2*y2 - xy*xy)/det;
	b = (xy*y - x*y2)/det;
	c = (x*xy - y*x2)/det;
	e = (n*y2 - y*y)/det;
	f = (x*y - n*xy)/det;
	i = (n*x2 - x*x)/det;

	// Get sums for x calibration
	z = zx = zy = 0;
	for(j=0;j<5;j++) {
		z += (float)cal.xfb[j];
		zx += (float)(cal.xfb[j]*cal.x[j]);
		zy += (float)(cal.xfb[j]*cal.y[j]);
	}

	// Now multiply out to get the calibration for framebuffer x coord
	cal.a[0] = (int)((a*z + b*zx + c*zy)*(scaling));
	cal.a[1] = (int)((b*z + e*zx + f*zy)*(scaling));
	cal.a[2] = (int)((c*z + f*zx + i*zy)*(scaling));

	qDebug("%f %f %f",(a*z + b*zx + c*zy), (b*z + e*zx + f*zy), (c*z + f*zx + i*zy));

	// Get sums for y calibration
	z = zx = zy = 0;
	for (j=0;j<5;j++) {
		z += (float)cal.yfb[j];
		zx += (float)(cal.yfb[j]*cal.x[j]);
		zy += (float)(cal.yfb[j]*cal.y[j]);
	}

	// Now multiply out to get the calibration for framebuffer y coord
	cal.a[3] = (int)((a*z + b*zx + c*zy)*(scaling));
	cal.a[4] = (int)((b*z + e*zx + f*zy)*(scaling));
	cal.a[5] = (int)((c*z + f*zx + i*zy)*(scaling));

	qDebug("%f %f %f",(a*z + b*zx + c*zy), (b*z + e*zx + f*zy), (c*z + f*zx + i*zy));


	// If we got here, we're OK, so assign scaling to a[6] and return
	cal.a[6] = (int) scaling;

	qDebug("Calibration constants: %d %d %d %d %d %d %d",
		cal.a[0], cal.a[1], cal.a[2],
		cal.a[3], cal.a[4], cal.a[5],
		cal.a[6]);

	return true;
}	

void Calibrate::moveCrosshair( QPoint pt )
{
	showCross = FALSE;
	repaint( crossPos.x() - 8, crossPos.y() - 8, 16, 16 );
	showCross = TRUE;
	crossPos = pt;
	repaint( crossPos.x() - 8, crossPos.y() - 8, 16, 16 );
}

void Calibrate::paintEvent( QPaintEvent * )
{
	QPainter p( this );

	int y;

	if ( !logo.isNull() ) {
		p.drawPixmap( ( width() - logo.width() ) / 2, 0, logo );
	}

	y = height() / 2 + 15;

	p.drawText( 0, y + height() / 8, width(), height() - y, AlignHCenter,
	            tr( "Um den Touchscreen zu kalibrieren bitte\n"
	                "mit dem Stift auf die Fadenkreuze tippen." ) );

	QFont f = p.font();
	f.setBold( TRUE );
	p.setFont( f );
	p.drawText( 0, y, width(), height() - y, AlignHCenter | WordBreak,
	            tr( "Willkommen beim Ramses" ) );

	if ( showCross ) {
		p.drawRect( crossPos.x() - 1, crossPos.y() - 8, 2, 7 );
		p.drawRect( crossPos.x() - 1, crossPos.y() + 1, 2, 7 );
		p.drawRect( crossPos.x() - 8, crossPos.y() - 1, 7, 2 );
		p.drawRect( crossPos.x() + 1, crossPos.y() - 1, 7, 2 );
	}
}

void Calibrate::mouseMoveEvent( QMouseEvent *e)
{
qDebug("Calibrate::mouseMoveEvent(%d,%d)", e->pos().x(), e->pos().y());
	penPos += e->pos();
	samples++;
}

void Calibrate::mouseReleaseEvent( QMouseEvent *e )
{
qDebug("Calibrate::mouseReleaseEvent(%d,%d)", e->pos().x(), e->pos().y());
	penPos += e->pos();
	samples++;
	penPos /= samples;

	if (location < 5) {
qDebug(" entering %d samples as penPos %d,%d", samples, penPos.x(), penPos.y() );
		cal.x[location] = e->pos().x();
		cal.y[location] = e->pos().y();
		location++;
		penPos  = QPoint();
		samples = 0;
	}
	if (location < 5) {
		moveCrosshair(QPoint(cal.xfb[location], cal.yfb[location]));
	} else {
		showCross = FALSE;
		repaint( crossPos.x() - 8, crossPos.y() - 8, 16, 16 );
		if (performCalculation()) {
			hide();
			emit accept();
		} else {
			qDebug("nochmal");
			reset();
		}
	}
}

#if 0

# ts_calibrate
xres = 240, yres = 320
Top left : X =  822 Y =  750
Top right: X =  806 Y =  242
Bot right: X =  209 Y =  255
Bot left : X =  216 Y =  754
Middle: X =  518 Y =  504
261.248383 -0.003901 -0.277929
343.781494 -0.365597 0.008400
Calibration constants: 17121174 -255 -18214 22530064 -23959 550 65536
# cat /etc/pointercal
-255 -18214 17121174 -23959 550 22530064 65536


# calibrate -qws
xres = 240, yres = 320
Top left : X =  814 Y =  759
Top right: X =  817 Y =  247
Bot left : X =  205 Y =  270
Bot right: X =  200 Y =  760
Middle: X =  511 Y =  507
264.857605 -0.005462 -0.279346
344.296326 -0.358942 -0.002853
Calibration constants: 17357708 -357 -18307 22563804 -23523 -186 65536
# cat /etc/pointercal
-357 -18307 17357708 -23523 -186 22563804 65536

#endif