/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <sal/config.h>

#include <memory>
#include <functional>
#include <epoxy/gl.h>

#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/utils/canvastools.hxx>
#include <com/sun/star/rendering/CompositeOperation.hpp>
#include <rtl/crc.h>
#include <rtl/math.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <vcl/font.hxx>
#include <vcl/kernarray.hxx>
#include <vcl/metric.hxx>
#include <vcl/virdev.hxx>

#include "ogl_canvasbitmap.hxx"
#include "ogl_canvasfont.hxx"
#include "ogl_canvastools.hxx"
#include "ogl_texturecache.hxx"
#include "ogl_tools.hxx"

#include "ogl_canvashelper.hxx"

using namespace ::com::sun::star;
using namespace std::placeholders;

namespace oglcanvas
{
    /* Concepts:
       =========

       This OpenGL canvas implementation tries to keep all render
       output as high-level as possible, i.e. geometry data and
       externally-provided bitmaps. Therefore, calls at the
       XCanvas-interfaces are not immediately transformed into colored
       pixel inside some GL buffer, but are retained simply with their
       call parameters. Only after XSpriteCanvas::updateScreen() has
       been called, this all gets transferred to the OpenGL subsystem
       and converted to a visible scene. The big advantage is, this
       makes sprite modifications practically zero-overhead, and saves
       a lot on texture memory (compared to the directx canvas, which
       immediately dumps every render call into a texture).

       The drawback, of course, is that complex images churn a lot of
       GPU cycles on every re-rendering.

       For the while, I'll be using immediate mode, i.e. transfer data
       over and over again to the OpenGL subsystem. Alternatively,
       there are display lists, which at least keep the data on the
       server, or even better, vertex buffers, which copy geometry
       data over en bloc.

       Next todo: put polygon geometry into vertex buffer (LRU cache
       necessary?) - or, rather, buffer objects! prune entries older
       than one updateScreen() call)

       Text: http://www.opengl.org/resources/features/fontsurvey/
     */

    struct CanvasHelper::Action
    {
        ::basegfx::B2DHomMatrix         maTransform;
        GLenum                          meSrcBlendMode;
        GLenum                          meDstBlendMode;
        rendering::ARGBColor            maARGBColor;
        ::basegfx::B2DPolyPolygonVector maPolyPolys;

        std::function< bool (
                            const CanvasHelper&,
                            const ::basegfx::B2DHomMatrix&,
                            GLenum,
                            GLenum,
                            const rendering::ARGBColor&,
                            const ::basegfx::B2DPolyPolygonVector&)> maFunction;
    };

    namespace
    {
        bool lcl_drawLine( const CanvasHelper&              /*rHelper*/,
                           const ::basegfx::B2DHomMatrix&   rTransform,
                           GLenum                           eSrcBlend,
                           GLenum                           eDstBlend,
                           const rendering::ARGBColor&      rColor,
                           const geometry::RealPoint2D&     rStartPoint,
                           const geometry::RealPoint2D&     rEndPoint )
        {
            TransformationPreserver aPreserver;
            setupState(rTransform, eSrcBlend, eDstBlend, rColor);

            glBegin(GL_LINES);
            glVertex2d(rStartPoint.X, rStartPoint.Y);
            glVertex2d(rEndPoint.X, rEndPoint.Y);
            glEnd();

            return true;
        }

        bool lcl_drawPolyPolygon( const CanvasHelper&                    /*rHelper*/,
                                  const ::basegfx::B2DHomMatrix&         rTransform,
                                  GLenum                                 eSrcBlend,
                                  GLenum                                 eDstBlend,
                                  const rendering::ARGBColor&            rColor,
                                  const ::basegfx::B2DPolyPolygonVector& rPolyPolygons )
        {
            TransformationPreserver aPreserver;
            setupState(rTransform, eSrcBlend, eDstBlend, rColor);

            for( const auto& rPoly : rPolyPolygons )
                renderPolyPolygon( rPoly );

            return true;
        }

        bool lcl_fillPolyPolygon( const CanvasHelper&                    /*rHelper*/,
                                  const ::basegfx::B2DHomMatrix&         rTransform,
                                  GLenum                                 eSrcBlend,
                                  GLenum                                 eDstBlend,
                                  const rendering::ARGBColor&            rColor,
                                  const ::basegfx::B2DPolyPolygonVector& rPolyPolygons )
        {
            TransformationPreserver aPreserver;
            setupState(rTransform, eSrcBlend, eDstBlend, rColor);

            for( const auto& rPoly : rPolyPolygons )
            {
                glBegin( GL_TRIANGLES );
                renderComplexPolyPolygon( rPoly );
                glEnd();
            }

            return true;
        }

        bool lcl_fillGradientPolyPolygon( const CanvasHelper&                            rHelper,
                                          const ::basegfx::B2DHomMatrix&                 rTransform,
                                          GLenum                                         eSrcBlend,
                                          GLenum                                         eDstBlend,
                                          const ::canvas::ParametricPolyPolygon::Values& rValues,
                                          const rendering::Texture&                      rTexture,
                                          const ::basegfx::B2DPolyPolygonVector&         rPolyPolygons )
        {
            TransformationPreserver aPreserver;
            setupState(rTransform, eSrcBlend, eDstBlend, rendering::ARGBColor());

            // convert to weird canvas textur coordinate system (not
            // [0,1]^2, but path coordinate system)
            ::basegfx::B2DHomMatrix aTextureTransform;
            ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
                                                            rTexture.AffineTransform );
            ::basegfx::B2DRange aBounds;
            for( const auto& rPoly : rPolyPolygons )
                aBounds.expand( ::basegfx::utils::getRange( rPoly ) );
            aTextureTransform.translate(-aBounds.getMinX(), -aBounds.getMinY());
            aTextureTransform.scale(1/aBounds.getWidth(), 1/aBounds.getHeight());

            const sal_Int32 nNumCols=rValues.maColors.getLength();
            uno::Sequence< rendering::ARGBColor > aColors(nNumCols);
            rendering::ARGBColor* const pColors=aColors.getArray();
            rendering::ARGBColor* pCurrCol=pColors;
            for( sal_Int32 i=0; i<nNumCols; ++i )
                *pCurrCol++ = rHelper.getDevice()->getDeviceColorSpace()->convertToARGB(rValues.maColors[i])[0];

            OSL_ASSERT(nNumCols == rValues.maStops.getLength());

            switch( rValues.meType )
            {
                case ::canvas::ParametricPolyPolygon::GradientType::Linear:
                    rHelper.getDeviceHelper()->useLinearGradientShader(pColors,
                                                                       rValues.maStops,
                                                                       aTextureTransform);
                    break;

                case ::canvas::ParametricPolyPolygon::GradientType::Elliptical:
                    rHelper.getDeviceHelper()->useRadialGradientShader(pColors,
                                                                       rValues.maStops,
                                                                       aTextureTransform);
                    break;

                case ::canvas::ParametricPolyPolygon::GradientType::Rectangular:
                    rHelper.getDeviceHelper()->useRectangularGradientShader(pColors,
                                                                            rValues.maStops,
                                                                            aTextureTransform);
                    break;

                default:
                    ENSURE_OR_THROW( false,
                                      "CanvasHelper lcl_fillGradientPolyPolygon(): Unexpected case" );
            }


            for( const auto& rPoly : rPolyPolygons )
            {
                glBegin(GL_TRIANGLES);
                renderComplexPolyPolygon( rPoly );
                glEnd();
            }

            glUseProgram(0);
            glLoadIdentity();
            glMatrixMode(GL_MODELVIEW);

            return true;
        }

        bool lcl_drawOwnBitmap( const CanvasHelper&              /*rHelper*/,
                                const ::basegfx::B2DHomMatrix&   rTransform,
                                GLenum                           eSrcBlend,
                                GLenum                           eDstBlend,
                                const rendering::ARGBColor&      rColor,
                                const CanvasBitmap&              rBitmap )
        {
            TransformationPreserver aPreserver;
            setupState(rTransform, eSrcBlend, eDstBlend, rColor);

            return rBitmap.renderRecordedActions();
        }

        bool lcl_drawGenericBitmap( const CanvasHelper&              rHelper,
                                    const ::basegfx::B2DHomMatrix&   rTransform,
                                    GLenum                           eSrcBlend,
                                    GLenum                           eDstBlend,
                                    const rendering::ARGBColor&      rColor,
                                    const geometry::IntegerSize2D&   rPixelSize,
                                    const uno::Sequence<sal_Int8>&   rPixelData,
                                    sal_uInt32                       nPixelCrc32 )
        {
            TransformationPreserver aPreserver;
            setupState(rTransform, eSrcBlend, eDstBlend, rColor);

            const unsigned int nTexId=rHelper.getDeviceHelper()->getTextureCache().getTexture(
                rPixelSize, rPixelData.getConstArray(), nPixelCrc32);

            glBindTexture(GL_TEXTURE_2D, nTexId);
            glEnable(GL_TEXTURE_2D);
            glTexParameteri(GL_TEXTURE_2D,
                            GL_TEXTURE_MIN_FILTER,
                            GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D,
                            GL_TEXTURE_MAG_FILTER,
                            GL_NEAREST);
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA,
                        GL_ONE_MINUS_SRC_ALPHA);

            // blend against fixed vertex color; texture alpha is multiplied in
            glColor4f(1,1,1,1);

            glBegin(GL_TRIANGLE_STRIP);
            glTexCoord2f(0,0); glVertex2d(0,0);
            glTexCoord2f(0,1); glVertex2d(0, rPixelSize.Height);
            glTexCoord2f(1,0); glVertex2d(rPixelSize.Width,0);
            glTexCoord2f(1,1); glVertex2d(rPixelSize.Width,rPixelSize.Height);
            glEnd();

            glBindTexture(GL_TEXTURE_2D, 0);
            glDisable(GL_TEXTURE_2D);

            return true;
        }

        bool lcl_fillTexturedPolyPolygon( const CanvasHelper&                    rHelper,
                                          const ::basegfx::B2DHomMatrix&         rTransform,
                                          GLenum                                 eSrcBlend,
                                          GLenum                                 eDstBlend,
                                          const rendering::Texture&              rTexture,
                                          const geometry::IntegerSize2D&         rPixelSize,
                                          const uno::Sequence<sal_Int8>&         rPixelData,
                                          sal_uInt32                             nPixelCrc32,
                                          const ::basegfx::B2DPolyPolygonVector& rPolyPolygons )
        {
            TransformationPreserver aPreserver;
            setupState(rTransform, eSrcBlend, eDstBlend, rendering::ARGBColor());

            const unsigned int nTexId=rHelper.getDeviceHelper()->getTextureCache().getTexture(
                rPixelSize, rPixelData.getConstArray(), nPixelCrc32);

            glBindTexture(GL_TEXTURE_2D, nTexId);
            glEnable(GL_TEXTURE_2D);
            glTexParameteri(GL_TEXTURE_2D,
                            GL_TEXTURE_MIN_FILTER,
                            GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D,
                            GL_TEXTURE_MAG_FILTER,
                            GL_NEAREST);
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA,
                        GL_ONE_MINUS_SRC_ALPHA);

            // convert to weird canvas textur coordinate system (not
            // [0,1]^2, but path coordinate system)
            ::basegfx::B2DHomMatrix aTextureTransform;
            ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
                                                            rTexture.AffineTransform );
            ::basegfx::B2DRange aBounds;
            for( const auto& rPolyPolygon : rPolyPolygons )
                aBounds.expand( ::basegfx::utils::getRange( rPolyPolygon ) );
            aTextureTransform.translate(-aBounds.getMinX(), -aBounds.getMinY());
            aTextureTransform.scale(1/aBounds.getWidth(), 1/aBounds.getHeight());
            aTextureTransform.invert();

            glMatrixMode(GL_TEXTURE);
            double aTexTransform[] =
                {
                    aTextureTransform.get(0,0), aTextureTransform.get(1,0), 0, 0,
                    aTextureTransform.get(0,1), aTextureTransform.get(1,1), 0, 0,
                    0,                          0,                          1, 0,
                    aTextureTransform.get(0,2), aTextureTransform.get(1,2), 0, 1
                };
            glLoadMatrixd(aTexTransform);

            // blend against fixed vertex color; texture alpha is multiplied in
            glColor4f(1,1,1,rTexture.Alpha);

            for( const auto& rPolyPolygon : rPolyPolygons )
            {
                glBegin(GL_TRIANGLES);
                renderComplexPolyPolygon( rPolyPolygon );
                glEnd();
            }

            glLoadIdentity();
            glMatrixMode(GL_MODELVIEW);

            glBindTexture(GL_TEXTURE_2D, 0);
            glDisable(GL_TEXTURE_2D);

            return true;
        }
    }

    CanvasHelper::CanvasHelper() :
        mpDevice( nullptr ),
        mpDeviceHelper( nullptr )
    {}

    CanvasHelper::~CanvasHelper()
    {}

    CanvasHelper& CanvasHelper::operator=( const CanvasHelper& rSrc )
    {
        mpDevice = rSrc.mpDevice;
        mpDeviceHelper = rSrc.mpDeviceHelper;
        mpRecordedActions = rSrc.mpRecordedActions;
        return *this;
    }

    void CanvasHelper::disposing()
    {
        RecordVectorT aThrowaway;
        mpRecordedActions.swap( aThrowaway );
        mpDevice = nullptr;
        mpDeviceHelper = nullptr;
    }

    void CanvasHelper::init( rendering::XGraphicDevice& rDevice,
                             SpriteDeviceHelper& rDeviceHelper )
    {
        mpDevice = &rDevice;
        mpDeviceHelper = &rDeviceHelper;
    }

    void CanvasHelper::clear()
    {
        mpRecordedActions->clear();
    }

    void CanvasHelper::drawLine( const rendering::XCanvas*      /*pCanvas*/,
                                 const geometry::RealPoint2D&   aStartPoint,
                                 const geometry::RealPoint2D&   aEndPoint,
                                 const rendering::ViewState&    viewState,
                                 const rendering::RenderState&  renderState )
    {
        if( mpDevice )
        {
            mpRecordedActions->push_back( Action() );
            Action& rAct=mpRecordedActions->back();

            setupGraphicsState( rAct, viewState, renderState );
            rAct.maFunction = std::bind(&lcl_drawLine,
                                          std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5,
                                          aStartPoint, aEndPoint);
        }
    }

    void CanvasHelper::drawBezier( const rendering::XCanvas*            /*pCanvas*/,
                                   const geometry::RealBezierSegment2D& aBezierSegment,
                                   const geometry::RealPoint2D&         aEndPoint,
                                   const rendering::ViewState&          viewState,
                                   const rendering::RenderState&        renderState )
    {
        if( !mpDevice )
            return;

        mpRecordedActions->push_back( Action() );
        Action& rAct=mpRecordedActions->back();

        setupGraphicsState( rAct, viewState, renderState );

        // TODO(F2): subdivide&render whole curve
        rAct.maFunction = std::bind(&lcl_drawLine,
                                        std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5,
                                        geometry::RealPoint2D(
                                            aBezierSegment.Px,
                                            aBezierSegment.Py),
                                        aEndPoint);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas*                          /*pCanvas*/,
                                                                                 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
                                                                                 const rendering::ViewState&                        viewState,
                                                                                 const rendering::RenderState&                      renderState )
    {
        ENSURE_OR_THROW( xPolyPolygon.is(),
                          "CanvasHelper::drawPolyPolygon: polygon is NULL");

        if( mpDevice )
        {
            mpRecordedActions->push_back( Action() );
            Action& rAct=mpRecordedActions->back();

            setupGraphicsState( rAct, viewState, renderState );
            rAct.maPolyPolys.push_back(
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon));
            rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety

            rAct.maFunction = &lcl_drawPolyPolygon;
        }

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas*                            /*pCanvas*/,
                                                                                   const uno::Reference< rendering::XPolyPolygon2D >&   xPolyPolygon,
                                                                                   const rendering::ViewState&                          viewState,
                                                                                   const rendering::RenderState&                        renderState,
                                                                                   const rendering::StrokeAttributes&                   /*strokeAttributes*/ )
    {
        ENSURE_OR_THROW( xPolyPolygon.is(),
                          "CanvasHelper::strokePolyPolygon: polygon is NULL");

        if( mpDevice )
        {
            mpRecordedActions->push_back( Action() );
            Action& rAct=mpRecordedActions->back();

            setupGraphicsState( rAct, viewState, renderState );
            rAct.maPolyPolys.push_back(
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon));
            rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety

            // TODO(F3): fallback to drawPolyPolygon currently
            rAct.maFunction = &lcl_drawPolyPolygon;
        }

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas*                            /*pCanvas*/,
                                                                                           const uno::Reference< rendering::XPolyPolygon2D >&   /*xPolyPolygon*/,
                                                                                           const rendering::ViewState&                          /*viewState*/,
                                                                                           const rendering::RenderState&                        /*renderState*/,
                                                                                           const uno::Sequence< rendering::Texture >&           /*textures*/,
                                                                                           const rendering::StrokeAttributes&                   /*strokeAttributes*/ )
    {
        // TODO
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas*                           /*pCanvas*/,
                                                                                                const uno::Reference< rendering::XPolyPolygon2D >&  /*xPolyPolygon*/,
                                                                                                const rendering::ViewState&                         /*viewState*/,
                                                                                                const rendering::RenderState&                       /*renderState*/,
                                                                                                const uno::Sequence< rendering::Texture >&          /*textures*/,
                                                                                                const uno::Reference< geometry::XMapping2D >&       /*xMapping*/,
                                                                                                const rendering::StrokeAttributes&                  /*strokeAttributes*/ )
    {
        // TODO
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XPolyPolygon2D >   CanvasHelper::queryStrokeShapes( const rendering::XCanvas*                            /*pCanvas*/,
                                                                                   const uno::Reference< rendering::XPolyPolygon2D >&   /*xPolyPolygon*/,
                                                                                   const rendering::ViewState&                          /*viewState*/,
                                                                                   const rendering::RenderState&                        /*renderState*/,
                                                                                   const rendering::StrokeAttributes&                   /*strokeAttributes*/ )
    {
        // TODO
        return uno::Reference< rendering::XPolyPolygon2D >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas*                          /*pCanvas*/,
                                                                                 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
                                                                                 const rendering::ViewState&                        viewState,
                                                                                 const rendering::RenderState&                      renderState )
    {
        ENSURE_OR_THROW( xPolyPolygon.is(),
                          "CanvasHelper::fillPolyPolygon: polygon is NULL");

        if( mpDevice )
        {
            mpRecordedActions->push_back( Action() );
            Action& rAct=mpRecordedActions->back();

            setupGraphicsState( rAct, viewState, renderState );
            rAct.maPolyPolys.push_back(
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon));
            rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety

            rAct.maFunction = &lcl_fillPolyPolygon;
        }

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas*                          /*pCanvas*/,
                                                                                         const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
                                                                                         const rendering::ViewState&                        viewState,
                                                                                         const rendering::RenderState&                      renderState,
                                                                                         const uno::Sequence< rendering::Texture >&         textures )
    {
        ENSURE_OR_THROW( xPolyPolygon.is(),
                          "CanvasHelper::fillPolyPolygon: polygon is NULL");

        if( mpDevice )
        {
            mpRecordedActions->push_back( Action() );
            Action& rAct=mpRecordedActions->back();

            setupGraphicsState( rAct, viewState, renderState );
            rAct.maPolyPolys.push_back(
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon));
            rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety

            // TODO(F1): Multi-texturing
            if( textures[0].Gradient.is() )
            {
                // try to cast XParametricPolyPolygon2D reference to
                // our implementation class.
                ::canvas::ParametricPolyPolygon* pGradient =
                      dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() );

                if( pGradient )
                {
                    // copy state from Gradient polypoly locally
                    // (given object might change!)
                    const ::canvas::ParametricPolyPolygon::Values aValues(
                        pGradient->getValues() );

                    rAct.maFunction = std::bind(&lcl_fillGradientPolyPolygon,
                                                    std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4,
                                                    aValues,
                                                    textures[0],
                                                    std::placeholders::_6);
                }
                else
                {
                    // TODO(F1): The generic case is missing here
                    ENSURE_OR_THROW( false,
                                      "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" );
                }
            }
            else if( textures[0].Bitmap.is() )
            {
                // own bitmap?
                CanvasBitmap* pOwnBitmap=dynamic_cast<CanvasBitmap*>(textures[0].Bitmap.get());
                if( pOwnBitmap )
                {
                    // TODO(F2): own texture bitmap
                }
                else
                {
                    // TODO(P3): Highly inefficient - simply copies pixel data

                    uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntegerBitmap(
                        textures[0].Bitmap,
                        uno::UNO_QUERY);
                    if( xIntegerBitmap.is() )
                    {
                        const geometry::IntegerSize2D aSize=xIntegerBitmap->getSize();
                        rendering::IntegerBitmapLayout aLayout;
                        uno::Sequence<sal_Int8> aPixelData=
                            xIntegerBitmap->getData(
                                aLayout,
                                geometry::IntegerRectangle2D(0,0,aSize.Width,aSize.Height));

                        // force-convert color to ARGB8888 int color space
                        uno::Sequence<sal_Int8> aARGBBytes(
                            aLayout.ColorSpace->convertToIntegerColorSpace(
                                aPixelData,
                                canvas::tools::getStdColorSpace()));

                        rAct.maFunction = std::bind(&lcl_fillTexturedPolyPolygon,
                                                        std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4,
                                                        textures[0],
                                                        aSize,
                                                        aARGBBytes,
                                                        rtl_crc32(0,
                                                                  aARGBBytes.getConstArray(),
                                                                  aARGBBytes.getLength()),
                                                        std::placeholders::_6);
                    }
                    // TODO(F1): handle non-integer case
                }
            }
        }

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas*                             /*pCanvas*/,
                                                                                              const uno::Reference< rendering::XPolyPolygon2D >&    /*xPolyPolygon*/,
                                                                                              const rendering::ViewState&                           /*viewState*/,
                                                                                              const rendering::RenderState&                         /*renderState*/,
                                                                                              const uno::Sequence< rendering::Texture >&            /*textures*/,
                                                                                              const uno::Reference< geometry::XMapping2D >&         /*xMapping*/ )
    {
        // TODO
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas*                    /*pCanvas*/,
                                                                       const rendering::FontRequest&                fontRequest,
                                                                       const uno::Sequence< beans::PropertyValue >& extraFontProperties,
                                                                       const geometry::Matrix2D&                    fontMatrix )
    {
        if( mpDevice )
            return uno::Reference< rendering::XCanvasFont >(
                    new CanvasFont(fontRequest, extraFontProperties, fontMatrix ) );

        return uno::Reference< rendering::XCanvasFont >();
    }

    uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas*                       /*pCanvas*/,
                                                                            const rendering::FontInfo&                      /*aFilter*/,
                                                                            const uno::Sequence< beans::PropertyValue >&    /*aFontProperties*/ )
    {
        // TODO
        return uno::Sequence< rendering::FontInfo >();
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas*                         /*pCanvas*/,
                                                                          const rendering::StringContext&                   /*text*/,
                                                                          const uno::Reference< rendering::XCanvasFont >&   /*xFont*/,
                                                                          const rendering::ViewState&                       /*viewState*/,
                                                                          const rendering::RenderState&                     /*renderState*/,
                                                                          sal_Int8                                          /*textDirection*/ )
    {
        // TODO - but not used from slideshow
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas*                       /*pCanvas*/,
                                                                                const uno::Reference< rendering::XTextLayout >& xLayoutetText,
                                                                                const rendering::ViewState&                     viewState,
                                                                                const rendering::RenderState&                   renderState )
    {
        ENSURE_OR_THROW( xLayoutetText.is(),
                          "CanvasHelper::drawTextLayout: text is NULL");

        if( mpDevice )
        {
            ScopedVclPtrInstance< VirtualDevice > pVDev;
            pVDev->EnableOutput(false);

            auto pLayoutFont = xLayoutetText->getFont();
            CanvasFont* pFont=dynamic_cast<CanvasFont*>(pLayoutFont.get());
            const rendering::StringContext aTxt=xLayoutetText->getText();
            if( pFont && aTxt.Length )
            {
                // create the font
                const rendering::FontRequest  aFontRequest = pFont->getFontRequest();
                const geometry::Matrix2D&     rFontMatrix = pFont->getFontMatrix();
                vcl::Font aFont(
                    aFontRequest.FontDescription.FamilyName,
                    aFontRequest.FontDescription.StyleName,
                    Size( 0, ::basegfx::fround<tools::Long>(aFontRequest.CellSize)));

                aFont.SetAlignment( ALIGN_BASELINE );
                aFont.SetCharSet( (aFontRequest.FontDescription.IsSymbolFont==util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE );
                aFont.SetVertical( aFontRequest.FontDescription.IsVertical==util::TriState_YES );
                aFont.SetWeight( static_cast<FontWeight>(aFontRequest.FontDescription.FontDescription.Weight) );
                aFont.SetItalic( (aFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL );

                if (pFont->getEmphasisMark())
                    aFont.SetEmphasisMark(FontEmphasisMark(pFont->getEmphasisMark()));

                // adjust to stretched font
                if(!::rtl::math::approxEqual(rFontMatrix.m00, rFontMatrix.m11))
                {
                    const Size aSize = pVDev->GetFontMetric( aFont ).GetFontSize();
                    const double fDividend( rFontMatrix.m10 + rFontMatrix.m11 );
                    double fStretch = rFontMatrix.m00 + rFontMatrix.m01;

                    if( !::basegfx::fTools::equalZero( fDividend) )
                        fStretch /= fDividend;

                    const sal_Int32 nNewWidth = ::basegfx::fround( aSize.Width() * fStretch );

                    aFont.SetAverageFontWidth( nNewWidth );
                }

                // set font
                pVDev->SetFont(aFont);

                mpRecordedActions->push_back( Action() );
                Action& rAct=mpRecordedActions->back();

                setupGraphicsState( rAct, viewState, renderState );

                // handle custom spacing, if there
                uno::Sequence<double> aLogicalAdvancements=xLayoutetText->queryLogicalAdvancements();
                if( aLogicalAdvancements.hasElements() )
                {
                    KernArraySpan aDXArray(aLogicalAdvancements.getConstArray(), aLogicalAdvancements.getLength());

                    uno::Sequence<sal_Bool> aKashidaPositions=xLayoutetText->queryKashidaPositions();
                    std::span<const sal_Bool> aKashidaArray(aKashidaPositions.getConstArray(), aKashidaPositions.getLength());

                    // get the glyphs
                    pVDev->GetTextOutlines(rAct.maPolyPolys,
                                          aTxt.Text,
                                          0,
                                          aTxt.StartPosition,
                                          aTxt.Length,
                                          0,
                                          aDXArray,
                                          aKashidaArray);
                }
                else
                {
                    // get the glyphs
                    pVDev->GetTextOutlines(rAct.maPolyPolys,
                                          aTxt.Text,
                                          0,
                                          aTxt.StartPosition,
                                          aTxt.Length );
                }

                // own copy, for thread safety
                for( auto& rPoly : rAct.maPolyPolys )
                    rPoly.makeUnique();

                rAct.maFunction = &lcl_fillPolyPolygon;
            }
        }

        // TODO
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas*                   /*pCanvas*/,
                                                                            const uno::Reference< rendering::XBitmap >& xBitmap,
                                                                            const rendering::ViewState&                 viewState,
                                                                            const rendering::RenderState&               renderState )
    {
        ENSURE_OR_THROW( xBitmap.is(),
                          "CanvasHelper::drawBitmap: bitmap is NULL");

        if( mpDevice )
        {
            // own bitmap?
            CanvasBitmap* pOwnBitmap=dynamic_cast<CanvasBitmap*>(xBitmap.get());
            if( pOwnBitmap )
            {
                // insert as transformed copy of bitmap action vector -
                // during rendering, this gets rendered into a temporary
                // buffer, and then composited to the front
                mpRecordedActions->push_back( Action() );
                Action& rAct=mpRecordedActions->back();

                setupGraphicsState( rAct, viewState, renderState );
                rAct.maFunction = std::bind(&lcl_drawOwnBitmap,
                                                std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5,
                                                *pOwnBitmap);
            }
            else
            {
                // TODO(P3): Highly inefficient - simply copies pixel data

                uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntegerBitmap(
                    xBitmap, uno::UNO_QUERY);
                if( xIntegerBitmap.is() )
                {
                    const geometry::IntegerSize2D aSize=xBitmap->getSize();
                    rendering::IntegerBitmapLayout aLayout;
                    uno::Sequence<sal_Int8> aPixelData=
                        xIntegerBitmap->getData(
                            aLayout,
                            geometry::IntegerRectangle2D(0,0,aSize.Width,aSize.Height));

                    // force-convert color to ARGB8888 int color space
                    uno::Sequence<sal_Int8> aARGBBytes(
                        aLayout.ColorSpace->convertToIntegerColorSpace(
                            aPixelData,
                            canvas::tools::getStdColorSpace()));

                    mpRecordedActions->push_back( Action() );
                    Action& rAct=mpRecordedActions->back();

                    setupGraphicsState( rAct, viewState, renderState );
                    rAct.maFunction = std::bind(&lcl_drawGenericBitmap,
                                                    std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5,
                                                    aSize, aARGBBytes,
                                                    rtl_crc32(0,
                                                              aARGBBytes.getConstArray(),
                                                              aARGBBytes.getLength()));
                }
                // TODO(F1): handle non-integer case
            }
        }

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas*                      pCanvas,
                                                                                     const uno::Reference< rendering::XBitmap >&    xBitmap,
                                                                                     const rendering::ViewState&                    viewState,
                                                                                     const rendering::RenderState&                  renderState )
    {
        // TODO(F3): remove this wart altogether
        return drawBitmap(pCanvas, xBitmap, viewState, renderState);
    }


    void CanvasHelper::setupGraphicsState( Action&                       o_action,
                                           const rendering::ViewState&   viewState,
                                           const rendering::RenderState& renderState )
    {
        ENSURE_OR_THROW( mpDevice,
                          "CanvasHelper::setupGraphicsState: reference device invalid" );

        // TODO(F3): clipping
        // TODO(P2): think about caching transformations between canvas calls

        // setup overall transform only now. View clip above was
        // relative to view transform
        ::canvas::tools::mergeViewAndRenderTransform(o_action.maTransform,
                                                     viewState,
                                                     renderState);
        // setup compositing - mapping courtesy David Reveman
        // (glitz_operator.c)
        switch( renderState.CompositeOperation )
        {
            case rendering::CompositeOperation::OVER:
                o_action.meSrcBlendMode=GL_ONE;
                o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA;
                break;
            case rendering::CompositeOperation::CLEAR:
                o_action.meSrcBlendMode=GL_ZERO;
                o_action.meDstBlendMode=GL_ZERO;
                break;
            case rendering::CompositeOperation::SOURCE:
                o_action.meSrcBlendMode=GL_ONE;
                o_action.meDstBlendMode=GL_ZERO;
                break;
            case rendering::CompositeOperation::UNDER:
            case rendering::CompositeOperation::DESTINATION:
                o_action.meSrcBlendMode=GL_ZERO;
                o_action.meDstBlendMode=GL_ONE;
                break;
            case rendering::CompositeOperation::INSIDE:
                o_action.meSrcBlendMode=GL_DST_ALPHA;
                o_action.meDstBlendMode=GL_ZERO;
                break;
            case rendering::CompositeOperation::INSIDE_REVERSE:
                o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA;
                o_action.meDstBlendMode=GL_ZERO;
                break;
            case rendering::CompositeOperation::OUTSIDE:
                o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA;
                o_action.meDstBlendMode=GL_ONE;
                break;
            case rendering::CompositeOperation::OUTSIDE_REVERSE:
                o_action.meSrcBlendMode=GL_ZERO;
                o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA;
                break;
            case rendering::CompositeOperation::ATOP:
                o_action.meSrcBlendMode=GL_DST_ALPHA;
                o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA;
                break;
            case rendering::CompositeOperation::ATOP_REVERSE:
                o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA;
                o_action.meDstBlendMode=GL_SRC_ALPHA;
                break;
            case rendering::CompositeOperation::XOR:
                o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA;
                o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA;
                break;
            case rendering::CompositeOperation::ADD:
                o_action.meSrcBlendMode=GL_ONE;
                o_action.meDstBlendMode=GL_ONE;
                break;
            case rendering::CompositeOperation::SATURATE:
                o_action.meSrcBlendMode=GL_SRC_ALPHA_SATURATE;
                o_action.meDstBlendMode=GL_SRC_ALPHA_SATURATE;
                break;

            default:
                ENSURE_OR_THROW( false, "CanvasHelper::setupGraphicsState: unexpected mode" );
                break;
        }

        if (renderState.DeviceColor.hasElements())
            o_action.maARGBColor =
                mpDevice->getDeviceColorSpace()->convertToARGB(renderState.DeviceColor)[0];
    }

    bool CanvasHelper::renderRecordedActions() const
    {
        for( const auto& rRecordedAction : *mpRecordedActions )
        {
            if( !rRecordedAction.maFunction( *this,
                                             rRecordedAction.maTransform,
                                             rRecordedAction.meSrcBlendMode,
                                             rRecordedAction.meDstBlendMode,
                                             rRecordedAction.maARGBColor,
                                             rRecordedAction.maPolyPolys ) )
                return false;
        }

        return true;
    }

    size_t CanvasHelper::getRecordedActionCount() const
    {
        return mpRecordedActions->size();
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
