/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_canvas.hxx"

#include <canvas/debug.hxx>
#include <canvas/canvastools.hxx>
#include <tools/diagnose_ex.h>

#include <vcl/virdev.hxx>
#include <vcl/metric.hxx>
#include <vcl/canvastools.hxx>

#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/tools/canvastools.hxx>

#include "cairo_canvasfont.hxx"
#include "cairo_textlayout.hxx"
#include "cairo_canvashelper.hxx"

using namespace ::cairo;
using namespace ::com::sun::star;

namespace cairocanvas
{
	enum ColorType
	{
		LINE_COLOR, FILL_COLOR, TEXT_COLOR, IGNORE_COLOR
	};

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

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

	static bool
	setupFontTransform( ::OutputDevice&				    rOutDev,
						::Point&						o_rPoint,
						::Font& 						io_rVCLFont, 
						const rendering::ViewState& 	rViewState,
						const rendering::RenderState& 	rRenderState )
	{
		::basegfx::B2DHomMatrix aMatrix;
            
		::canvas::tools::mergeViewAndRenderTransform(aMatrix,
													 rViewState,
													 rRenderState);

		::basegfx::B2DTuple aScale;
		::basegfx::B2DTuple aTranslate;
		double nRotate, nShearX;
            
		aMatrix.decompose( aScale, aTranslate, nRotate, nShearX );

		// query font metric _before_ tampering with width and height
		if( !::rtl::math::approxEqual(aScale.getX(), aScale.getY()) )
        {
			// retrieve true font width
			const sal_Int32 nFontWidth( rOutDev.GetFontMetric( io_rVCLFont ).GetWidth() );

			const sal_Int32 nScaledFontWidth( ::basegfx::fround(nFontWidth * aScale.getX()) );

			if( !nScaledFontWidth )
			{
				// scale is smaller than one pixel - disable text
				// output altogether
				return false;
			}

			io_rVCLFont.SetWidth( nScaledFontWidth );
		}

		if( !::rtl::math::approxEqual(aScale.getY(), 1.0) )
        {
			const sal_Int32 nFontHeight( io_rVCLFont.GetHeight() );
			io_rVCLFont.SetHeight( ::basegfx::fround(nFontHeight * aScale.getY()) );
		}

		io_rVCLFont.SetOrientation( static_cast< short >( ::basegfx::fround(-fmod(nRotate, 2*M_PI)*(1800.0/M_PI)) ) );

		// TODO(F2): Missing functionality in VCL: shearing
		o_rPoint.X() = ::basegfx::fround(aTranslate.getX());
		o_rPoint.Y() = ::basegfx::fround(aTranslate.getY());

		return true;
	}

    static int
	setupOutDevState( OutputDevice&                 rOutDev,
                      const rendering::XCanvas*     pOwner,
                      const rendering::ViewState& 	viewState,
                      const rendering::RenderState& renderState,
                      ColorType						eColorType )
    {
        ::canvas::tools::verifyInput( renderState,
                                      BOOST_CURRENT_FUNCTION,
                                      const_cast<rendering::XCanvas*>(pOwner), // only for refcount
                                      2,
                                      eColorType == IGNORE_COLOR ? 0 : 3 );

        int nTransparency(0);

        // TODO(P2): Don't change clipping all the time, maintain current clip
        // state and change only when update is necessary

        // accumulate non-empty clips into one region
        // ==========================================

        Region aClipRegion;

        if( viewState.Clip.is() )
        {
            ::basegfx::B2DPolyPolygon aClipPoly( 
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(
                    viewState.Clip) );

            if( aClipPoly.count() )
            {
                // setup non-empty clipping
                ::basegfx::B2DHomMatrix aMatrix;
                aClipPoly.transform(
                    ::basegfx::unotools::homMatrixFromAffineMatrix( aMatrix, 
                                                                    viewState.AffineTransform ) );
                
                aClipRegion = Region::GetRegionFromPolyPolygon( ::PolyPolygon( aClipPoly ) );
            }
        }

        if( renderState.Clip.is() )
        {
            ::basegfx::B2DPolyPolygon aClipPoly( 
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(
                    renderState.Clip) );

            ::basegfx::B2DHomMatrix aMatrix;
            aClipPoly.transform(
                ::canvas::tools::mergeViewAndRenderTransform( aMatrix, 
                                                              viewState,
                                                              renderState ) );

            if( aClipPoly.count() )
            {
                // setup non-empty clipping
                Region aRegion = Region::GetRegionFromPolyPolygon( ::PolyPolygon( aClipPoly ) );

                if( aClipRegion.IsEmpty() )
                    aClipRegion = aRegion;
                else
                    aClipRegion.Intersect( aRegion );
            }
            else
            {
                // clip polygon is empty
                aClipRegion.SetEmpty();
            }
        }

        // setup accumulated clip region. Note that setting an
        // empty clip region denotes "clip everything" on the
        // OutputDevice (which is why we translate that into
        // SetClipRegion() here). When both view and render clip
        // are empty, aClipRegion remains default-constructed,
        // i.e. empty, too.
        if( aClipRegion.IsEmpty() )
        {
            rOutDev.SetClipRegion();
        }
        else
        {
            rOutDev.SetClipRegion( aClipRegion );
        }

        if( eColorType != IGNORE_COLOR )
        {
            Color aColor( COL_WHITE );

            if( renderState.DeviceColor.getLength() > 2 )
            {
                aColor = ::vcl::unotools::stdColorSpaceSequenceToColor( renderState.DeviceColor );
            }

            // extract alpha, and make color opaque
            // afterwards. Otherwise, OutputDevice won't draw anything
            nTransparency = aColor.GetTransparency();
            aColor.SetTransparency(0);

            switch( eColorType )
            {
                case LINE_COLOR:
                    rOutDev.SetLineColor( aColor );
                    rOutDev.SetFillColor();

                    break;

                case FILL_COLOR:
                    rOutDev.SetFillColor( aColor );
                    rOutDev.SetLineColor();

                    break;

                case TEXT_COLOR:
                    rOutDev.SetTextColor( aColor );

                    break;

                default:
                    ENSURE_OR_THROW( false, 
                                      "CanvasHelper::setupOutDevState(): Unexpected color type");
                    break;
            }
        }

        return nTransparency;
    }

    bool setupTextOutput( OutputDevice&                                     rOutDev,
                          const rendering::XCanvas*                         pOwner,
						  ::Point&										    o_rOutPos,
						  const rendering::ViewState& 					    viewState,
						  const rendering::RenderState& 					renderState,
						  const uno::Reference< rendering::XCanvasFont >&	xFont	)
    {
        setupOutDevState( rOutDev, pOwner, viewState, renderState, TEXT_COLOR );

        ::Font aVCLFont;

        CanvasFont* pFont = dynamic_cast< CanvasFont* >( xFont.get() );
        
        ENSURE_ARG_OR_THROW( pFont,
                         "CanvasHelper::setupTextOutput(): Font not compatible with this canvas" );

        aVCLFont = pFont->getVCLFont();

        Color aColor( COL_BLACK );

        if( renderState.DeviceColor.getLength() > 2 )
        {
            aColor = ::vcl::unotools::stdColorSpaceSequenceToColor(renderState.DeviceColor );
        }

        // setup font color
        aVCLFont.SetColor( aColor );
        aVCLFont.SetFillColor( aColor );

        // no need to replicate this for mp2ndOutDev, we're modifying only aVCLFont here.
        if( !setupFontTransform( rOutDev, o_rOutPos, aVCLFont, viewState, renderState ) )
            return false;

        rOutDev.SetFont( aVCLFont );


        return true;
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* 						pOwner, 
                                                                          const rendering::StringContext& 					text, 
                                                                          const uno::Reference< rendering::XCanvasFont >& 	xFont, 
                                                                          const rendering::ViewState& 						viewState, 
                                                                          const rendering::RenderState& 					renderState, 
                                                                          sal_Int8				 							textDirection )
    {
#ifdef CAIRO_CANVAS_PERF_TRACE
		struct timespec aTimer;
		mxDevice->startPerfTrace( &aTimer );
#endif

        ENSURE_ARG_OR_THROW( xFont.is(),
                         "CanvasHelper::drawText(): font is NULL");

		if( !mpVirtualDevice )
			mpVirtualDevice = mpSurface->createVirtualDevice();

        if( mpVirtualDevice )
		{
#if defined CAIRO_HAS_WIN32_SURFACE
            // FIXME: Some kind of work-araound...
            cairo_rectangle (mpSurface->getCairo().get(), 0, 0, 0, 0);
            cairo_fill(mpSurface->getCairo().get());
#endif
            ::Point aOutpos;
			if( !setupTextOutput( *mpVirtualDevice, pOwner, aOutpos, viewState, renderState, xFont ) )
				return uno::Reference< rendering::XCachedPrimitive >(NULL); // no output necessary

				// change text direction and layout mode
			sal_uLong nLayoutMode(0);
			switch( textDirection )
				{
				case rendering::TextDirection::WEAK_LEFT_TO_RIGHT:
					nLayoutMode |= TEXT_LAYOUT_BIDI_LTR;
					// FALLTHROUGH intended
				case rendering::TextDirection::STRONG_LEFT_TO_RIGHT:
					nLayoutMode |= TEXT_LAYOUT_BIDI_LTR | TEXT_LAYOUT_BIDI_STRONG;
					nLayoutMode |= TEXT_LAYOUT_TEXTORIGIN_LEFT;
					break;

				case rendering::TextDirection::WEAK_RIGHT_TO_LEFT:
					nLayoutMode |= TEXT_LAYOUT_BIDI_RTL;
					// FALLTHROUGH intended
				case rendering::TextDirection::STRONG_RIGHT_TO_LEFT:
					nLayoutMode |= TEXT_LAYOUT_BIDI_RTL | TEXT_LAYOUT_BIDI_STRONG;
					nLayoutMode |= TEXT_LAYOUT_TEXTORIGIN_RIGHT;
					break;
				}

			// TODO(F2): alpha
			mpVirtualDevice->SetLayoutMode( nLayoutMode );

            OSL_TRACE(":cairocanvas::CanvasHelper::drawText(O,t,f,v,r,d): %s", ::rtl::OUStringToOString( text.Text.copy( text.StartPosition, text.Length ),
                                                                                                         RTL_TEXTENCODING_UTF8 ).getStr());

            TextLayout* pTextLayout = new TextLayout(text, textDirection, 0, CanvasFont::Reference(dynamic_cast< CanvasFont* >( xFont.get() )), mpSurfaceProvider);
            pTextLayout->draw( mpSurface, *mpVirtualDevice, aOutpos, viewState, renderState );
		}

        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* 						pOwner, 
                                                                                const uno::Reference< rendering::XTextLayout >& xLayoutedText, 
                                                                                const rendering::ViewState& 					viewState, 
                                                                                const rendering::RenderState& 					renderState )
    {
        ENSURE_ARG_OR_THROW( xLayoutedText.is(),
                         "CanvasHelper::drawTextLayout(): layout is NULL");

        TextLayout* pTextLayout = dynamic_cast< TextLayout* >( xLayoutedText.get() );

        if( pTextLayout )
        {
			if( !mpVirtualDevice )
				mpVirtualDevice = mpSurface->createVirtualDevice();

            if( mpVirtualDevice )
            {
#if defined CAIRO_HAS_WIN32_SURFACE
                // FIXME: Some kind of work-araound...
                cairo_rectangle( mpSurface->getCairo().get(), 0, 0, 0, 0);
                cairo_fill(mpSurface->getCairo().get());
#endif
                // TODO(T3): Race condition. We're taking the font
                // from xLayoutedText, and then calling draw() at it,
                // without exclusive access. Move setupTextOutput(),
                // e.g. to impltools?

                ::Point aOutpos;
                if( !setupTextOutput( *mpVirtualDevice, pOwner, aOutpos, viewState, renderState, xLayoutedText->getFont() ) )
                    return uno::Reference< rendering::XCachedPrimitive >(NULL); // no output necessary

                // TODO(F2): What about the offset scalings?
                pTextLayout->draw( mpSurface, *mpVirtualDevice, aOutpos, viewState, renderState );
            }
        }
        else
        {
            ENSURE_ARG_OR_THROW( false,
                             "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" );
        }

        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

}
