#include "calibrate.h"

#include <cstdio>
#include <cerrno>
using namespace std;

#include <evas.h>
#include <ecore.h>

#include <Ecore_Fb.h>
#include <Ecore.h>

const unsigned int offset = 32;

static Ecore_Event_Filter* filter = 0;

static void* calibration_event_filter_start( void* data )
{
    return 0;
}

static int calibration_event_filter_event( void* loop_data, void *data, int type, void* event )
{
    if ( type == ECORE_FB_EVENT_MOUSE_BUTTON_UP )
    {
        Ecore_Fb_Event_Mouse_Button_Up* ev = static_cast<Ecore_Fb_Event_Mouse_Button_Up*>( event );
        cout << " - data = " << data << endl;
        cout << " - mouse button up at position = " << ev->x << ", " << ev->y << endl;
        CalibrationRectangle* cr = reinterpret_cast<CalibrationRectangle*>( data );
        cr->nextPoint( ev->x, ev->y );
        return 0; // swallow event
    }
    else
    {
        return 1; // keep event
    }
}

static void calibration_event_filter_end( void* data, void *loop_data )
{
}

CalibrationRectangle::CalibrationRectangle( int x, int y, int width, int height, EvasCanvas* evas )
    :EvasRectangle( x, y, width, height, evas, "CalibrationRectangle" )
{
    setLayer( 255 );
    setColor( 255, 255, 255, 0 ); // white, fully transparent

    // setup the five calibration points
    Size s = evas->size();
    cd.canvasPoints[TopLeft].set( offset, offset );
    cd.canvasPoints[BottomLeft].set( offset, s.height()-offset );
    cd.canvasPoints[BottomRight].set( s.width()-offset, s.height()-offset );
    cd.canvasPoints[TopRight].set( s.width()-offset, offset );
    cd.canvasPoints[Center].set( s.width()/2, s.height()/2 );

    switch ( eApp->mainWindow()->rotation() )
    {
        case 0:
            cout << "ROT 0" << endl;
            cd.screenPoints[TopLeft] = cd.canvasPoints[TopLeft];
            cd.screenPoints[BottomLeft] = cd.canvasPoints[BottomLeft];
            cd.screenPoints[BottomRight] = cd.canvasPoints[BottomRight];
            cd.screenPoints[TopRight] = cd.canvasPoints[TopRight];
            cd.screenPoints[Center] = cd.canvasPoints[Center];
            break;
        case 90:
            cd.screenPoints[TopLeft] = cd.canvasPoints[TopLeft];
            cd.screenPoints[BottomLeft] = cd.canvasPoints[BottomLeft];
            cd.screenPoints[BottomRight] = cd.canvasPoints[BottomRight];
            cd.screenPoints[TopRight] = cd.canvasPoints[TopRight];
            cd.screenPoints[Center] = cd.canvasPoints[Center];
            break;
        case 180:
            cd.screenPoints[TopLeft] = cd.canvasPoints[TopLeft];
            cd.screenPoints[BottomLeft] = cd.canvasPoints[BottomLeft];
            cd.screenPoints[BottomRight] = cd.canvasPoints[BottomRight];
            cd.screenPoints[TopRight] = cd.canvasPoints[TopRight];
            cd.screenPoints[Center] = cd.canvasPoints[Center];
            break;
        case 270:
            cout << "ROT 270" << endl;
            cd.screenPoints[TopLeft].set( s.height()-offset, offset );
            cd.screenPoints[BottomLeft].set( offset, offset );
            cd.screenPoints[BottomRight].set( offset, s.width()-offset );
            cd.screenPoints[TopRight].set( s.height()-offset, s.width()-offset );
            cd.screenPoints[Center].set( s.height()/2, s.width()/2 );
            break;
    }

    // setup background
    background = new EvasGradient( 0, 0, s.width(), s.height(), evas );
    background->setLayer( 21 );
    background->setAngle( 15 );
    background->addColor( 255, 255, 255, 255, 10 );
    background->addColor( 200, 200, 200, 255, 10 );
    background->addColor( 255, 255, 255, 255, 10 );
    background->show();

    // setup text
    text = new EvasText( DATADIR "/fonts/Vera.ttf", 12, 0, s.height()/2, "foo", evas );
    text->setColor( 0, 0, 0, 255 );
    text->setLayer( 22 );
    text->setText( "Click on crosshair to calibrate screen" );
    text->show();

    // setup crosshair
#if 0
    crosshair = new EvasImage( DATADIR "/images/crosshair.png", evas );
    crosshair->setLayer( 23 );
#else
    crosshairhorz = new EvasLine( evas );
    crosshairvert = new EvasLine( evas );
    crosshairhorz->setLayer( 23 );
    crosshairvert->setLayer( 23 );
    crosshairhorz->setColor( 0, 0, 0, 255 );
    crosshairvert->setColor( 0, 0, 0, 255 );
    crosshairhorz->show();
    crosshairvert->show();
#endif
}

CalibrationRectangle::~CalibrationRectangle()
{
}

bool CalibrationRectangle::handleShow()
{
    ecore_fb_touch_screen_calibrate_set( 1, 0, 0, 0, 0 );
    cout << "this = " << this << endl;
    filter = ecore_event_filter_add( &calibration_event_filter_start,
                            &calibration_event_filter_event,
                            &calibration_event_filter_end, this );
    position = TopLeft;
    moveCrossHair();
}

void CalibrationRectangle::nextPoint( int x, int y )
{
    cd.devicePoints[position].set( x, y );

    if ( ++position <= LastPosition )
    {
        moveCrossHair();
    }
    else
    {
        //FIXME: Don't delete or hide, dissolve, and after that, hide :D
        //hide();
        hide();
        delete background;
#if 0
        delete crosshair;
#else
        delete crosshairhorz;
        delete crosshairvert;
#endif
        delete text;
        //crosshair->hide();
        //text->hide();
        ecore_fb_touch_screen_calibrate_set( 0, 0, 0, 0, 0 );
        ecore_event_filter_del( filter );
        calibrate();
        done.emit();
    }
}

void CalibrationRectangle::moveCrossHair()
{
#if 0
    crosshair->move( cd.canvasPoints[position] );
    crosshair->show();
#else
    int x = cd.canvasPoints[position].x();
    int y = cd.canvasPoints[position].y();
    crosshairhorz->setGeometry( x-offset/2, y, offset, 0 );
    crosshairvert->setGeometry( x, y-offset/2, 0, offset );
#endif
}

bool CalibrationRectangle::calibrate()
{
    // calibration code based on ts_calibrate.c (C) Russell King

    int j;
    float n, x, y, x2, y2, xy, z, zx, zy;
    float det, cal_a, cal_b, cal_c, cal_d, cal_e, cal_f, cal_i;
    float scaling = 65536.0;
    int cal_x[5], cal_xfb[5], cal_y[5], cal_yfb[5], cal_o[7];

    cal_x[0]=cd.devicePoints[ TopLeft ].x();
    cal_y[0]=cd.devicePoints[ TopLeft ].y();
    cal_x[1]=cd.devicePoints[ TopRight ].x();
    cal_y[1]=cd.devicePoints[ TopRight ].y();
    cal_x[2]=cd.devicePoints[ BottomLeft ].x();
    cal_y[2]=cd.devicePoints[ BottomLeft ].y();
    cal_x[3]=cd.devicePoints[ BottomRight ].x();
    cal_y[3]=cd.devicePoints[ BottomRight ].y();
    cal_x[4]=cd.devicePoints[ Center ].x();
    cal_y[4]=cd.devicePoints[ Center ].y();

    cal_xfb[0]=cd.screenPoints[ TopLeft ].x();
    cal_yfb[0]=cd.screenPoints[ TopLeft ].y();
    cal_xfb[1]=cd.screenPoints[ TopRight ].x();
    cal_yfb[1]=cd.screenPoints[ TopRight ].y();
    cal_xfb[2]=cd.screenPoints[ BottomLeft ].x();
    cal_yfb[2]=cd.screenPoints[ BottomLeft ].y();
    cal_xfb[3]=cd.screenPoints[ BottomRight ].x();
    cal_yfb[3]=cd.screenPoints[ BottomRight ].y();
    cal_xfb[4]=cd.screenPoints[ Center ].x();
    cal_yfb[4]=cd.screenPoints[ Center ].y();

    // 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 )
    {
        cout << "CalibrationRectangle::calibrate() - determinant " << det << " is too small - aborting." << endl;
        return false;
    }

    // Get elements of inverse matrix
    cal_a = (x2*y2 - xy*xy)/det;
    cal_b = (xy*y - x*y2)/det;
    cal_c = (x*xy - y*x2)/det;
    cal_e = (n*y2 - y*y)/det;
    cal_f = (x*y - n*xy)/det;
    cal_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_o[0] = (int)((cal_a*z + cal_b*zx + cal_c*zy)*(scaling));
    cal_o[1] = (int)((cal_b*z + cal_e*zx + cal_f*zy)*(scaling));
    cal_o[2] = (int)((cal_c*z + cal_f*zx + cal_i*zy)*(scaling));

    cout << "CAL: " << (cal_a*z + cal_b*zx + cal_c*zy) << " "
                    << (cal_b*z + cal_e*zx + cal_f*zy) << " "
                    << (cal_c*z + cal_f*zx + cal_i*zy) << endl;

    // 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_o[3] = (int)((cal_a*z + cal_b*zx + cal_c*zy)*(scaling));
    cal_o[4] = (int)((cal_b*z + cal_e*zx + cal_f*zy)*(scaling));
    cal_o[5] = (int)((cal_c*z + cal_f*zx + cal_i*zy)*(scaling));

    cout << "CAL: " << (cal_a*z + cal_b*zx + cal_c*zy) << " "
                    << (cal_b*z + cal_e*zx + cal_f*zy) << " "
                    << (cal_c*z + cal_f*zx + cal_i*zy) << endl;


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

    cout << "CAL constants: " << cal_o[0] << " " << cal_o[1] << " " << cal_o[2] << " "
                              << cal_o[3] << " " << cal_o[4] << " " << cal_o[5] << " " << cal_o[6] << endl;

    FILE* stream = fopen( "/etc/pointercal", "w" );
    if ( stream == NULL )
    {
        cout << "CalibrationRectangle::calibrate() - couldn't open /etc/pointercal (" << strerror( errno ) << ")" << endl;
        return false;
    }

    fprintf( stream, "%d %d %d %d %d %d %d\n", cal_o[1], cal_o[2], cal_o[0], cal_o[4], cal_o[5], cal_o[3], cal_o[6] );
    fclose( stream );

    return true;
}
