/*
 * This program source code file is part of KICAD, a free EDA CAD application.
 *
 * Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
 * Copyright (C) 2012 Kicad Developers, see change_log.txt for contributors.
 *
 * CAIRO_GAL - Graphics Abstraction Layer for Cairo
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#include <wx/image.h>
#include <wx/log.h>

#include <gal/cairo/cairo_gal.h>
#include <gal/cairo/cairo_compositor.h>
#include <gal/definitions.h>

#include <limits>

using namespace KIGFX;


const float CAIRO_GAL::LAYER_ALPHA = 0.8;


CAIRO_GAL::CAIRO_GAL( wxWindow* aParent, wxEvtHandler* aMouseListener,
        wxEvtHandler* aPaintListener, const wxString& aName ) :
    wxWindow( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxEXPAND, aName )
{
    parentWindow  = aParent;
    mouseListener = aMouseListener;
    paintListener = aPaintListener;

    // Initialize the flags
    isGrouping          = false;
    isInitialized       = false;
    isDeleteSavedPixels = false;
    validCompositor     = false;
    groupCounter        = 0;

    // Connecting the event handlers
    Connect( wxEVT_PAINT,       wxPaintEventHandler( CAIRO_GAL::onPaint ) );

    // Mouse events are skipped to the parent
    Connect( wxEVT_MOTION,          wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_LEFT_DOWN,       wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_LEFT_UP,         wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_LEFT_DCLICK,     wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_MIDDLE_DOWN,     wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_MIDDLE_UP,       wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_MIDDLE_DCLICK,   wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_RIGHT_DOWN,      wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_RIGHT_UP,        wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_RIGHT_DCLICK,    wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
    Connect( wxEVT_MOUSEWHEEL,      wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
#if defined _WIN32 || defined _WIN64
    Connect( wxEVT_ENTER_WINDOW,    wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
#endif

    SetSize( aParent->GetSize() );
    screenSize = VECTOR2I( aParent->GetSize() );

    cursorPixels = NULL;
    cursorPixelsSaved = NULL;
    initCursor();

    // Grid color settings are different in Cairo and OpenGL
    SetGridColor( COLOR4D( 0.1, 0.1, 0.1, 0.8 ) );

    // Allocate memory for pixel storage
    allocateBitmaps();

    initSurface();
}


CAIRO_GAL::~CAIRO_GAL()
{
    deinitSurface();
    deleteBitmaps();

    delete cursorPixels;
    delete cursorPixelsSaved;

    ClearCache();
}


void CAIRO_GAL::BeginDrawing()
{
    initSurface();

    if( !validCompositor )
        setCompositor();

    compositor->SetMainContext( context );
    compositor->SetBuffer( mainBuffer );

    // Cairo grouping prevents display of overlapping items on the same layer in the lighter color
    cairo_push_group( currentContext );
}


void CAIRO_GAL::EndDrawing()
{
    // Force remaining objects to be drawn
    Flush();

    // Cairo grouping prevents display of overlapping items on the same layer in the lighter color
    cairo_pop_group_to_source( currentContext );
    cairo_paint_with_alpha( currentContext, LAYER_ALPHA );

    // Merge buffers on the screen
    compositor->DrawBuffer( mainBuffer );
    compositor->DrawBuffer( overlayBuffer );

    // This code was taken from the wxCairo example - it's not the most efficient one
    // Here is a good place for optimizations

    // Now translate the raw context data from the format stored
    // by cairo into a format understood by wxImage.
    unsigned char* wxOutputPtr = wxOutput;

    for( size_t count = 0; count < bufferSize; count++ )
    {
        unsigned int value = bitmapBuffer[count];
        *wxOutputPtr++ = ( value >> 16 ) & 0xff;  // Red pixel
        *wxOutputPtr++ = ( value >> 8 ) & 0xff;   // Green pixel
        *wxOutputPtr++ = value & 0xff;            // Blue pixel
    }

    wxImage      img( screenSize.x, screenSize.y, (unsigned char*) wxOutput, true );
    wxBitmap     bmp( img );
    wxClientDC   client_dc( this );
    wxBufferedDC dc;
    dc.Init( &client_dc, bmp );

    // Now it is the time to blit the mouse cursor
    blitCursor( dc );

    deinitSurface();
}


void CAIRO_GAL::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
{
    cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
    cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
    isElementAdded = true;
}


void CAIRO_GAL::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
                             double aWidth )
{
    if( isFillEnabled )
    {
        // Filled tracks mode
        SetLineWidth( aWidth );

        cairo_move_to( currentContext, (double) aStartPoint.x, (double) aStartPoint.y );
        cairo_line_to( currentContext, (double) aEndPoint.x, (double) aEndPoint.y );
    }
    else
    {
        // Outline mode for tracks
        VECTOR2D startEndVector = aEndPoint - aStartPoint;
        double   lineAngle      = atan2( startEndVector.y, startEndVector.x );
        double   lineLength     = startEndVector.EuclideanNorm();

        cairo_save( currentContext );

        cairo_translate( currentContext, aStartPoint.x, aStartPoint.y );
        cairo_rotate( currentContext, lineAngle );

        cairo_arc( currentContext, 0.0,        0.0, aWidth / 2.0,  M_PI / 2.0, 3.0 * M_PI / 2.0 );
        cairo_arc( currentContext, lineLength, 0.0, aWidth / 2.0, -M_PI / 2.0, M_PI / 2.0 );

        cairo_move_to( currentContext, 0.0,        aWidth / 2.0 );
        cairo_line_to( currentContext, lineLength, aWidth / 2.0 );

        cairo_move_to( currentContext, 0.0,        -aWidth / 2.0 );
        cairo_line_to( currentContext, lineLength, -aWidth / 2.0 );

        cairo_restore( currentContext );
    }

    isElementAdded = true;
}


void CAIRO_GAL::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
{
    // A circle is drawn using an arc
    cairo_new_sub_path( currentContext );
    cairo_arc( currentContext, aCenterPoint.x, aCenterPoint.y, aRadius, 0.0, 2 * M_PI );

    isElementAdded = true;
}


void CAIRO_GAL::DrawArc( const VECTOR2D& aCenterPoint, double aRadius, double aStartAngle,
                         double aEndAngle )
{
    SWAP( aStartAngle, >, aEndAngle );

    cairo_new_sub_path( currentContext );
    cairo_arc( currentContext, aCenterPoint.x, aCenterPoint.y, aRadius, aStartAngle, aEndAngle );

    if( isFillEnabled )
    {
        VECTOR2D startPoint( cos( aStartAngle ) * aRadius + aCenterPoint.x,
                             sin( aStartAngle ) * aRadius + aCenterPoint.y );
        VECTOR2D endPoint( cos( aEndAngle ) * aRadius + aCenterPoint.x,
                           sin( aEndAngle ) * aRadius + aCenterPoint.y );

        cairo_move_to( currentContext, aCenterPoint.x, aCenterPoint.y );
        cairo_line_to( currentContext, startPoint.x, startPoint.y );
        cairo_line_to( currentContext, endPoint.x, endPoint.y );
        cairo_close_path( currentContext );
    }

    isElementAdded = true;
}


void CAIRO_GAL::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
{
    // Calculate the diagonal points
    VECTOR2D diagonalPointA( aEndPoint.x,  aStartPoint.y );
    VECTOR2D diagonalPointB( aStartPoint.x, aEndPoint.y );

    // The path is composed from 4 segments
    cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
    cairo_line_to( currentContext, diagonalPointA.x, diagonalPointA.y );
    cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
    cairo_line_to( currentContext, diagonalPointB.x, diagonalPointB.y );
    cairo_close_path( currentContext );

    isElementAdded = true;
}


void CAIRO_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA,
                           const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint )
{
    cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
    cairo_curve_to( currentContext, aControlPointA.x, aControlPointA.y, aControlPointB.x,
                    aControlPointB.y, aEndPoint.x, aEndPoint.y );
    cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );

    isElementAdded = true;
}


void CAIRO_GAL::ResizeScreen( int aWidth, int aHeight )
{
    screenSize = VECTOR2I( aWidth, aHeight );

    // Recreate the bitmaps
    deleteBitmaps();
    allocateBitmaps();

    if( validCompositor )
        compositor->Resize( aWidth, aHeight );

    validCompositor = false;

    SetSize( wxSize( aWidth, aHeight ) );
}


bool CAIRO_GAL::Show( bool aShow )
{
    bool s = wxWindow::Show( aShow );

    if( aShow )
        wxWindow::Raise();

    return s;
}


void CAIRO_GAL::Flush()
{
    storePath();
}


void CAIRO_GAL::ClearScreen( const COLOR4D& aColor )
{
    backgroundColor = aColor;
    cairo_set_source_rgb( currentContext, aColor.r, aColor.g, aColor.b );
    cairo_rectangle( currentContext, 0.0, 0.0, screenSize.x, screenSize.y );
    cairo_fill( currentContext );
}


void CAIRO_GAL::SetIsFill( bool aIsFillEnabled )
{
    storePath();
    isFillEnabled = aIsFillEnabled;

    if( isGrouping )
    {
        GROUP_ELEMENT groupElement;
        groupElement.command = CMD_SET_FILL;
        groupElement.boolArgument = aIsFillEnabled;
        currentGroup->push_back( groupElement );
    }
}


void CAIRO_GAL::SetIsStroke( bool aIsStrokeEnabled )
{
    storePath();
    isStrokeEnabled = aIsStrokeEnabled;

    if( isGrouping )
    {
        GROUP_ELEMENT groupElement;
        groupElement.command = CMD_SET_STROKE;
        groupElement.boolArgument = aIsStrokeEnabled;
        currentGroup->push_back( groupElement );
    }
}


void CAIRO_GAL::SetStrokeColor( const COLOR4D& aColor )
{
    storePath();
    strokeColor = aColor;

    if( isGrouping )
    {
        GROUP_ELEMENT groupElement;
        groupElement.command = CMD_SET_STROKECOLOR;
        groupElement.arguments[0] = strokeColor.r;
        groupElement.arguments[1] = strokeColor.g;
        groupElement.arguments[2] = strokeColor.b;
        groupElement.arguments[3] = strokeColor.a;
        currentGroup->push_back( groupElement );
    }
}


void CAIRO_GAL::SetFillColor( const COLOR4D& aColor )
{
    storePath();
    fillColor = aColor;

    if( isGrouping )
    {
        GROUP_ELEMENT groupElement;
        groupElement.command = CMD_SET_FILLCOLOR;
        groupElement.arguments[0] = fillColor.r;
        groupElement.arguments[1] = fillColor.g;
        groupElement.arguments[2] = fillColor.b;
        groupElement.arguments[3] = fillColor.a;
        currentGroup->push_back( groupElement );
    }
}


void CAIRO_GAL::SetLineWidth( double aLineWidth )
{
    storePath();

    lineWidth = aLineWidth;

    if( isGrouping )
    {
        GROUP_ELEMENT groupElement;
        groupElement.command = CMD_SET_LINE_WIDTH;
        groupElement.arguments[0] = aLineWidth;
        currentGroup->push_back( groupElement );
    }
    else
    {
        // Make lines appear at least 1 pixel wide, no matter of zoom
        double x = 1.0, y = 1.0;
        cairo_device_to_user_distance( currentContext, &x, &y );
        double minWidth = std::min( fabs( x ), fabs( y ) );
        cairo_set_line_width( currentContext, std::max( aLineWidth, minWidth ) );
    }
}


void CAIRO_GAL::SetLayerDepth( double aLayerDepth )
{
    super::SetLayerDepth( aLayerDepth );

    if( isInitialized )
    {
        storePath();

        cairo_pop_group_to_source( currentContext );
        cairo_paint_with_alpha( currentContext, LAYER_ALPHA );

        cairo_push_group( currentContext );
    }
}


void CAIRO_GAL::Transform( const MATRIX3x3D& aTransformation )
{
    cairo_matrix_t cairoTransformation;

    cairo_matrix_init( &cairoTransformation,
                       aTransformation.m_data[0][0],
                       aTransformation.m_data[1][0],
                       aTransformation.m_data[0][1],
                       aTransformation.m_data[1][1],
                       aTransformation.m_data[0][2],
                       aTransformation.m_data[1][2] );

    cairo_transform( currentContext, &cairoTransformation );
}


void CAIRO_GAL::Rotate( double aAngle )
{
    storePath();

    if( isGrouping )
    {
        GROUP_ELEMENT groupElement;
        groupElement.command = CMD_ROTATE;
        groupElement.arguments[0] = aAngle;
        currentGroup->push_back( groupElement );
    }
    else
    {
        cairo_rotate( currentContext, aAngle );
    }
}


void CAIRO_GAL::Translate( const VECTOR2D& aTranslation )
{
    storePath();

    if( isGrouping )
    {
        GROUP_ELEMENT groupElement;
        groupElement.command = CMD_TRANSLATE;
        groupElement.arguments[0] = aTranslation.x;
        groupElement.arguments[1] = aTranslation.y;
        currentGroup->push_back( groupElement );
    }
    else
    {
        cairo_translate( currentContext, aTranslation.x, aTranslation.y );
    }
}


void CAIRO_GAL::Scale( const VECTOR2D& aScale )
{
    storePath();

    if( isGrouping )
    {
        GROUP_ELEMENT groupElement;
        groupElement.command = CMD_SCALE;
        groupElement.arguments[0] = aScale.x;
        groupElement.arguments[1] = aScale.y;
        currentGroup->push_back( groupElement );
    }
    else
    {
        cairo_scale( currentContext, aScale.x, aScale.y );
    }
}


void CAIRO_GAL::Save()
{
    storePath();

    if( isGrouping )
    {
        GROUP_ELEMENT groupElement;
        groupElement.command = CMD_SAVE;
        currentGroup->push_back( groupElement );
    }
    else
    {
        cairo_save( currentContext );
    }
}


void CAIRO_GAL::Restore()
{
    storePath();

    if( isGrouping )
    {
        GROUP_ELEMENT groupElement;
        groupElement.command = CMD_RESTORE;
        currentGroup->push_back( groupElement );
    }
    else
    {
        cairo_restore( currentContext );
    }
}


int CAIRO_GAL::BeginGroup()
{
    initSurface();

    // If the grouping is started: the actual path is stored in the group, when
    // a attribute was changed or when grouping stops with the end group method.
    storePath();

    GROUP group;
    int groupNumber = getNewGroupNumber();
    groups.insert( std::make_pair( groupNumber, group ) );
    currentGroup = &groups[groupNumber];
    isGrouping   = true;

    return groupNumber;
}


void CAIRO_GAL::EndGroup()
{
    storePath();
    isGrouping = false;

    deinitSurface();
}


void CAIRO_GAL::DrawGroup( int aGroupNumber )
{
    // This method implements a small Virtual Machine - all stored commands
    // are executed; nested calling is also possible

    storePath();

    for( GROUP::iterator it = groups[aGroupNumber].begin();
         it != groups[aGroupNumber].end(); ++it )
    {
        switch( it->command )
        {
        case CMD_SET_FILL:
            isFillEnabled = it->boolArgument;
            break;

        case CMD_SET_STROKE:
            isStrokeEnabled = it->boolArgument;
            break;

        case CMD_SET_FILLCOLOR:
            fillColor = COLOR4D( it->arguments[0], it->arguments[1], it->arguments[2],
                                 it->arguments[3] );
            break;

        case CMD_SET_STROKECOLOR:
            strokeColor = COLOR4D( it->arguments[0], it->arguments[1], it->arguments[2],
                                   it->arguments[3] );
            break;

        case CMD_SET_LINE_WIDTH:
            {
                // Make lines appear at least 1 pixel wide, no matter of zoom
                double x = 1.0, y = 1.0;
                cairo_device_to_user_distance( currentContext, &x, &y );
                double minWidth = std::min( fabs( x ), fabs( y ) );
                cairo_set_line_width( currentContext, std::max( it->arguments[0], minWidth ) );
            }
            break;


        case CMD_STROKE_PATH:
            cairo_set_source_rgb( currentContext, strokeColor.r, strokeColor.g, strokeColor.b );
            cairo_append_path( currentContext, it->cairoPath );
            cairo_stroke( currentContext );
            break;

        case CMD_FILL_PATH:
            cairo_set_source_rgb( currentContext, fillColor.r, fillColor.g, fillColor.b );
            cairo_append_path( currentContext, it->cairoPath );
            cairo_fill( currentContext );
            break;

        case CMD_TRANSFORM:
            cairo_matrix_t matrix;
            cairo_matrix_init( &matrix, it->arguments[0], it->arguments[1], it->arguments[2],
                               it->arguments[3], it->arguments[4], it->arguments[5] );
            cairo_transform( currentContext, &matrix );
            break;

        case CMD_ROTATE:
            cairo_rotate( currentContext, it->arguments[0] );
            break;

        case CMD_TRANSLATE:
            cairo_translate( currentContext, it->arguments[0], it->arguments[1] );
            break;

        case CMD_SCALE:
            cairo_scale( currentContext, it->arguments[0], it->arguments[1] );
            break;

        case CMD_SAVE:
            cairo_save( currentContext );
            break;

        case CMD_RESTORE:
            cairo_restore( currentContext );
            break;

        case CMD_CALL_GROUP:
            DrawGroup( it->intArgument );
            break;
        }
    }
}


void CAIRO_GAL::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor )
{
    storePath();

    for( GROUP::iterator it = groups[aGroupNumber].begin();
         it != groups[aGroupNumber].end(); ++it )
    {
        if( it->command == CMD_SET_FILLCOLOR || it->command == CMD_SET_STROKECOLOR )
        {
            it->arguments[0] = aNewColor.r;
            it->arguments[1] = aNewColor.g;
            it->arguments[2] = aNewColor.b;
            it->arguments[3] = aNewColor.a;
        }
    }
}


void CAIRO_GAL::ChangeGroupDepth( int aGroupNumber, int aDepth )
{
    // Cairo does not have any possibilities to change the depth coordinate of stored items,
    // it depends only on the order of drawing
}


void CAIRO_GAL::DeleteGroup( int aGroupNumber )
{
    storePath();

    // Delete the Cairo paths
    std::deque<GROUP_ELEMENT>::iterator it, end;

    for( it = groups[aGroupNumber].begin(), end = groups[aGroupNumber].end(); it != end; ++it )
    {
        if( it->command == CMD_FILL_PATH || it->command == CMD_STROKE_PATH )
        {
            cairo_path_destroy( it->cairoPath );
        }
    }

    // Delete the group
    groups.erase( aGroupNumber );
}


void CAIRO_GAL::ClearCache()
{
    for( int i = groups.size() - 1; i >= 0; --i )
    {
        DeleteGroup( i );
    }
}


void CAIRO_GAL::SaveScreen()
{
    // Copy the current bitmap to the backup buffer
    int offset = 0;

    for( int j = 0; j < screenSize.y; j++ )
    {
        for( int i = 0; i < stride; i++ )
        {
            bitmapBufferBackup[offset + i] = bitmapBuffer[offset + i];
            offset += stride;
        }
    }
}


void CAIRO_GAL::RestoreScreen()
{
    int offset = 0;

    for( int j = 0; j < screenSize.y; j++ )
    {
        for( int i = 0; i < stride; i++ )
        {
            bitmapBuffer[offset + i] = bitmapBufferBackup[offset + i];
            offset += stride;
        }
    }
}


void CAIRO_GAL::SetTarget( RENDER_TARGET aTarget )
{
    // If the compositor is not set, that means that there is a recaching process going on
    // and we do not need the compositor now
    if( !validCompositor )
        return;

    // Cairo grouping prevents display of overlapping items on the same layer in the lighter color
    if( isInitialized )
    {
        storePath();

        cairo_pop_group_to_source( currentContext );
        cairo_paint_with_alpha( currentContext, LAYER_ALPHA );
    }

    switch( aTarget )
    {
    default:
    case TARGET_CACHED:
    case TARGET_NONCACHED:
        compositor->SetBuffer( mainBuffer );
        break;

    case TARGET_OVERLAY:
        compositor->SetBuffer( overlayBuffer );
        break;
    }

    if( isInitialized )
        cairo_push_group( currentContext );

    currentTarget = aTarget;
}


RENDER_TARGET CAIRO_GAL::GetTarget() const
{
    return currentTarget;
}


void CAIRO_GAL::ClearTarget( RENDER_TARGET aTarget )
{
    // Save the current state
    unsigned int currentBuffer = compositor->GetBuffer();

    switch( aTarget )
    {
    // Cached and noncached items are rendered to the same buffer
    default:
    case TARGET_CACHED:
    case TARGET_NONCACHED:
        compositor->SetBuffer( mainBuffer );
        break;

    case TARGET_OVERLAY:
        compositor->SetBuffer( overlayBuffer );
        break;
    }

    compositor->ClearBuffer();

    // Restore the previous state
    compositor->SetBuffer( currentBuffer );
}


void CAIRO_GAL::SetCursorSize( unsigned int aCursorSize )
{
    GAL::SetCursorSize( aCursorSize );
    initCursor();
}


void CAIRO_GAL::DrawCursor( const VECTOR2D& aCursorPosition )
{
    // Now we should only store the position of the mouse cursor
    // The real drawing routines are in blitCursor()
    cursorPosition = VECTOR2D( aCursorPosition.x - cursorSize / 2,
                               aCursorPosition.y - cursorSize / 2 );
}


void CAIRO_GAL::drawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
{
    cairo_move_to( currentContext, aStartPoint.x, aStartPoint.y );
    cairo_line_to( currentContext, aEndPoint.x, aEndPoint.y );
    cairo_set_source_rgb( currentContext, gridColor.r, gridColor.g, gridColor.b );
    cairo_stroke( currentContext );
}


void CAIRO_GAL::storePath()
{
    if( isElementAdded )
    {
        isElementAdded = false;

        if( !isGrouping )
        {
            if( isFillEnabled )
            {
                cairo_set_source_rgb( currentContext, fillColor.r, fillColor.g, fillColor.b );
                cairo_fill_preserve( currentContext );
            }

            if( isStrokeEnabled )
            {
                cairo_set_source_rgb( currentContext, strokeColor.r, strokeColor.g,
                                      strokeColor.b );
                cairo_stroke_preserve( currentContext );
            }
        }
        else
        {
            // Copy the actual path, append it to the global path list
            // then check, if the path needs to be stroked/filled and
            // add this command to the group list;
            if( isStrokeEnabled )
            {
                GROUP_ELEMENT groupElement;
                groupElement.cairoPath = cairo_copy_path( currentContext );
                groupElement.command   = CMD_STROKE_PATH;
                currentGroup->push_back( groupElement );
            }

            if( isFillEnabled )
            {
                GROUP_ELEMENT groupElement;
                groupElement.cairoPath = cairo_copy_path( currentContext );
                groupElement.command   = CMD_FILL_PATH;
                currentGroup->push_back( groupElement );
            }
        }

        cairo_new_path( currentContext );
    }
}


void CAIRO_GAL::onPaint( wxPaintEvent& WXUNUSED( aEvent ) )
{
    PostPaint();
}


void CAIRO_GAL::skipMouseEvent( wxMouseEvent& aEvent )
{
    // Post the mouse event to the event listener registered in constructor, if any
    if( mouseListener )
        wxPostEvent( mouseListener, aEvent );
}


void CAIRO_GAL::initCursor()
{
    if( cursorPixels )
        delete cursorPixels;

    if( cursorPixelsSaved )
        delete cursorPixelsSaved;

    cursorPixels      = new wxBitmap( cursorSize, cursorSize );
    cursorPixelsSaved = new wxBitmap( cursorSize, cursorSize );

    wxMemoryDC cursorShape( *cursorPixels );

    cursorShape.SetBackground( *wxTRANSPARENT_BRUSH );
    wxColour color( cursorColor.r * cursorColor.a * 255, cursorColor.g * cursorColor.a * 255,
                    cursorColor.b * cursorColor.a * 255, 255 );
    wxPen pen = wxPen( color );
    cursorShape.SetPen( pen );
    cursorShape.Clear();

    cursorShape.DrawLine( 0, cursorSize / 2, cursorSize, cursorSize / 2 );
    cursorShape.DrawLine( cursorSize / 2, 0, cursorSize / 2, cursorSize );
}


void CAIRO_GAL::blitCursor( wxBufferedDC& clientDC )
{
    if( !isCursorEnabled )
        return;

    wxMemoryDC cursorSave( *cursorPixelsSaved );
    wxMemoryDC cursorShape( *cursorPixels );

    if( !isDeleteSavedPixels )
    {
        // Restore pixels that were overpainted by the previous cursor
        clientDC.Blit( savedCursorPosition.x, savedCursorPosition.y,
                       cursorSize, cursorSize, &cursorSave, 0, 0 );
    }
    else
    {
        isDeleteSavedPixels = false;
    }

    // Store pixels that are going to be overpainted
    VECTOR2D cursorScreen = ToScreen( cursorPosition ) - cursorSize / 2.0f;
    cursorSave.Blit( 0, 0, cursorSize, cursorSize, &clientDC, cursorScreen.x, cursorScreen.y );

    // Draw the cursor
    clientDC.Blit( cursorScreen.x, cursorScreen.y, cursorSize, cursorSize,
                   &cursorShape, 0, 0, wxOR );

    savedCursorPosition.x = (wxCoord) cursorScreen.x;
    savedCursorPosition.y = (wxCoord) cursorScreen.y;
}


void CAIRO_GAL::allocateBitmaps()
{
    // Create buffer, use the system independent Cairo context backend
    stride     = cairo_format_stride_for_width( GAL_FORMAT, screenSize.x );
    bufferSize = stride * screenSize.y;

    bitmapBuffer        = new unsigned int[bufferSize];
    bitmapBufferBackup  = new unsigned int[bufferSize];
    wxOutput            = new unsigned char[bufferSize * 3];
}


void CAIRO_GAL::deleteBitmaps()
{
    delete[] bitmapBuffer;
    delete[] bitmapBufferBackup;
    delete[] wxOutput;
}


void CAIRO_GAL::initSurface()
{
    if( isInitialized )
        return;

    // Create the Cairo surface
    surface = cairo_image_surface_create_for_data( (unsigned char*) bitmapBuffer, GAL_FORMAT,
                                                   screenSize.x, screenSize.y, stride );
    context = cairo_create( surface );
#ifdef __WXDEBUG__
    cairo_status_t status = cairo_status( context );
    wxASSERT_MSG( status == CAIRO_STATUS_SUCCESS, wxT( "Cairo context creation error" ) );
#endif /* __WXDEBUG__ */
    currentContext = context;

    cairo_set_antialias( context, CAIRO_ANTIALIAS_SUBPIXEL );

    // Clear the screen
    ClearScreen( backgroundColor );

    // Compute the world <-> screen transformations
    ComputeWorldScreenMatrix();

    cairo_matrix_init( &cairoWorldScreenMatrix, worldScreenMatrix.m_data[0][0],
                       worldScreenMatrix.m_data[1][0], worldScreenMatrix.m_data[0][1],
                       worldScreenMatrix.m_data[1][1], worldScreenMatrix.m_data[0][2],
                       worldScreenMatrix.m_data[1][2] );

    cairo_set_matrix( context, &cairoWorldScreenMatrix );

    // Start drawing with a new path
    cairo_new_path( context );
    isElementAdded = true;

    cairo_set_line_join( context, CAIRO_LINE_JOIN_ROUND );
    cairo_set_line_cap( context, CAIRO_LINE_CAP_ROUND );

    lineWidth = 0;

    isDeleteSavedPixels = true;
    isInitialized = true;
}


void CAIRO_GAL::deinitSurface()
{
    if( !isInitialized )
        return;

    // Destroy Cairo objects
    cairo_destroy( context );
    cairo_surface_destroy( surface );

    isInitialized = false;
}


void CAIRO_GAL::setCompositor()
{
    // Recreate the compositor with the new Cairo context
    compositor.reset( new CAIRO_COMPOSITOR( &currentContext ) );
    compositor->Resize( screenSize.x, screenSize.y );

    // Prepare buffers
    mainBuffer = compositor->CreateBuffer();
    overlayBuffer = compositor->CreateBuffer();

    validCompositor = true;
}


void CAIRO_GAL::drawPoly( const std::deque<VECTOR2D>& aPointList )
{
    // Iterate over the point list and draw the segments
    std::deque<VECTOR2D>::const_iterator it = aPointList.begin();

    cairo_move_to( currentContext, it->x, it->y );

    for( ++it; it != aPointList.end(); ++it )
    {
        cairo_line_to( currentContext, it->x, it->y );
    }

    isElementAdded = true;
}


void CAIRO_GAL::drawPoly( const VECTOR2D aPointList[], int aListSize )
{
    // Iterate over the point list and draw the segments
    const VECTOR2D* ptr = aPointList;

    cairo_move_to( currentContext, ptr->x, ptr->y );

    for( int i = 0; i < aListSize; ++i )
    {
        ++ptr;
        cairo_line_to( currentContext, ptr->x, ptr->y );
    }

    isElementAdded = true;
}


unsigned int CAIRO_GAL::getNewGroupNumber()
{
    wxASSERT_MSG( groups.size() < std::numeric_limits<unsigned int>::max(),
                  wxT( "There are no free slots to store a group" ) );

    while( groups.find( groupCounter ) != groups.end() )
    {
        groupCounter++;
    }

    return groupCounter++;
}
