/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

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

#include <canvas/debug.hxx>

#include <comphelper/anytostring.hxx>
#include <cppuhelper/exc_hlp.hxx>

#include <com/sun/star/awt/MouseButton.hpp>
#include <com/sun/star/presentation/XSlideShowView.hpp>

#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <cppcanvas/basegfxfactory.hxx>

#include "activity.hxx"
#include "activitiesqueue.hxx"
#include "slideshowcontext.hxx"
#include "userpaintoverlay.hxx"
#include "mouseeventhandler.hxx"
#include "eventmultiplexer.hxx"
#include "screenupdater.hxx"
#include "vieweventhandler.hxx"

#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>
#include "slide.hxx"
#include "cursormanager.hxx"

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

namespace slideshow
{
    namespace internal
    { 
        class PaintOverlayHandler : public MouseEventHandler,
                                    public ViewEventHandler,
                    public UserPaintEventHandler
        {
        public:
            PaintOverlayHandler( const RGBColor&          rStrokeColor,
                                 double                   nStrokeWidth,
                                 ActivitiesQueue&         rActivitiesQueue,
                                 ScreenUpdater&           rScreenUpdater,
                                 const UnoViewContainer&  rViews,
                                 Slide&		              rSlide,
                                 const PolyPolygonVector& rPolygons,
                                 bool					  bActive ) :
                mrActivitiesQueue( rActivitiesQueue ),
                mrScreenUpdater( rScreenUpdater ),
                maViews(),
                maPolygons( rPolygons ),
                maStrokeColor( rStrokeColor ),
                mnStrokeWidth( nStrokeWidth ),
                maLastPoint(),
                maLastMouseDownPos(),
                mbIsLastPointValid( false ),
                mbIsLastMouseDownPosValid( false ),
                //handle the "remove all ink from slide" mode of erasing
                mbIsEraseAllModeActivated( false ),
                //handle the "remove stroke by stroke" mode of erasing
                mbIsEraseModeActivated( false ),
                mrSlide(rSlide),
                mnSize(100),
                mbActive( bActive )
            {
                std::for_each( rViews.begin(),
                               rViews.end(),
                               boost::bind( &PaintOverlayHandler::viewAdded,
                                            this,
                                            _1 ));
                drawPolygons();
            }

            virtual void dispose()
            {
                maViews.clear();
            }

            // ViewEventHandler methods
            virtual void viewAdded( const UnoViewSharedPtr& rView )
            {
                maViews.push_back( rView );
            }

            virtual void viewRemoved( const UnoViewSharedPtr& rView )
            {
                maViews.erase( ::std::remove( maViews.begin(),
                                              maViews.end(),
                                              rView ) );
            }

            virtual void viewChanged( const UnoViewSharedPtr& /*rView*/ )
            {
                // TODO(F2): for persistent drawings, need to store
                // polygon and repaint here.
            }
            
            virtual void viewsChanged()
            {
                // TODO(F2): for persistent drawings, need to store
                // polygon and repaint here.
            }
            
            bool colorChanged( RGBColor const& rUserColor )
            {
                mbIsLastPointValid = false;
                mbActive = true;
                this->maStrokeColor = rUserColor;
                this->mbIsEraseModeActivated = false;
                return true;
            }
            
            bool widthChanged( double nUserStrokeWidth )
            {
                this->mnStrokeWidth = nUserStrokeWidth;
                mbIsEraseModeActivated = false;
                return true;
            }
            
            void repaintWithoutPolygons()
            {
                    // must get access to the instance to erase all polygon
                    for( UnoViewVector::iterator aIter=maViews.begin(), aEnd=maViews.end();
                        aIter!=aEnd;
                        ++aIter )
                    {
                        // fully clear view content to background color
                        //(*aIter)->getCanvas()->clear();

                        //get via SlideImpl instance the bitmap of the slide unmodified to redraw it
                        SlideBitmapSharedPtr 		 pBitmap( mrSlide.getCurrentSlideBitmap( (*aIter) ) );
                        ::cppcanvas::CanvasSharedPtr pCanvas( (*aIter)->getCanvas() );
                        
                        const ::basegfx::B2DHomMatrix 	aViewTransform( (*aIter)->getTransformation() );
                        const ::basegfx::B2DPoint 		aOutPosPixel( aViewTransform * ::basegfx::B2DPoint() );
                        
                        // setup a canvas with device coordinate space, the slide
                        // bitmap already has the correct dimension.
                        ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( pCanvas->clone() );
                        
                        pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() );
                        
                        // render at given output position
                        pBitmap->move( aOutPosPixel );
                        
                        // clear clip (might have been changed, e.g. from comb
                        // transition)
                        pBitmap->clip( ::basegfx::B2DPolyPolygon() ); 
                        pBitmap->draw( pDevicePixelCanvas );
                        
                        mrScreenUpdater.notifyUpdate(*aIter,true);
                    }
            }

            bool eraseAllInkChanged( bool const& rEraseAllInk )
            {
                this->mbIsEraseAllModeActivated= rEraseAllInk;
                // if the erase all mode is activated it will remove all ink from slide, 
                // therefor destroy all the polygons stored
                if(mbIsEraseAllModeActivated)
                {
                    // The Erase Mode should be desactivated
                    mbIsEraseModeActivated = false;
                    repaintWithoutPolygons();
                    maPolygons.clear();
                }
            mbIsEraseAllModeActivated=false;
            return true;
            }
            
            bool eraseInkWidthChanged( sal_Int32 rEraseInkSize )
            {
                // Change the size
                this->mnSize=rEraseInkSize;
                // Changed to mode Erase
                this->mbIsEraseModeActivated = true;
                return true;
            }

            bool switchPenMode()
            {
                mbIsLastPointValid = false;
                mbActive = true;
                this->mbIsEraseModeActivated = false;
                return true;
            }

            bool switchEraserMode()
            {
                mbIsLastPointValid = false;
                mbActive = true;
                this->mbIsEraseModeActivated = true;
                return true;
            }

            bool disable()
            {
                mbIsLastPointValid = false;
                mbIsLastMouseDownPosValid = false;
                mbActive = false;
                return true;
            }
            
            //Draw all registered polygons.
            void drawPolygons()
            {		
                for( PolyPolygonVector::iterator aIter=maPolygons.begin(), aEnd=maPolygons.end();
                                     aIter!=aEnd;
                                     ++aIter )
                {
                    (*aIter)->draw();
                }
                // screen update necessary to show painting
                mrScreenUpdater.notifyUpdate();	
            }
                       
            //Retrieve all registered polygons.
            PolyPolygonVector getPolygons()
            {
                return maPolygons;	
            }
            
            // MouseEventHandler methods
            virtual bool handleMousePressed( const awt::MouseEvent& e )
            {
                if( !mbActive )
                    return false;

                if (e.Buttons == awt::MouseButton::RIGHT)
                {
                    mbIsLastPointValid = false;
                    return false;
                }
                
                if (e.Buttons != awt::MouseButton::LEFT)
                    return false;
                
                maLastMouseDownPos.setX( e.X );
                maLastMouseDownPos.setY( e.Y );
                mbIsLastMouseDownPosValid = true;

                // eat mouse click (though we don't process it
                // _directly_, it enables the drag mode
                return true;
            }

            virtual bool handleMouseReleased( const awt::MouseEvent& e )
            {
                if( !mbActive )
                    return false;

                if (e.Buttons == awt::MouseButton::RIGHT)
                {
                    mbIsLastPointValid = false;
                    return false;
                }
                
                if (e.Buttons != awt::MouseButton::LEFT)
                    return false;
                
                // check, whether up- and down press are on exactly
                // the same pixel. If that's the case, ignore the
                // click, and pass on the event to low-prio
                // handlers. This effectively permits effect
                // advancements via clicks also when user paint is
                // enabled.
                if( mbIsLastMouseDownPosValid &&
                    ::basegfx::B2DPoint( e.X,
                                         e.Y ) == maLastMouseDownPos )
                {
                    mbIsLastMouseDownPosValid = false;
                    return false;
                }

                // invalidate, next downpress will have to start a new
                // polygon.
                mbIsLastPointValid = false;

                // eat mouse click (though we don't process it
                // _directly_, it enables the drag mode
                return true;
            }

            virtual bool handleMouseEntered( const awt::MouseEvent& e )
            {
                if( !mbActive )
                    return false;

                mbIsLastPointValid = true;
                maLastPoint.setX( e.X );
                maLastPoint.setY( e.Y );

                return true;
            }

            virtual bool handleMouseExited( const awt::MouseEvent& )
            {
                if( !mbActive )
                    return false;

                mbIsLastPointValid = false;
                mbIsLastMouseDownPosValid = false;

                return true;
            }

            virtual bool handleMouseDragged( const awt::MouseEvent& e )
            {
                if( !mbActive )
                    return false;

                if (e.Buttons == awt::MouseButton::RIGHT)
                {
                    mbIsLastPointValid = false;
                    return false;
                }

                if(mbIsEraseModeActivated)
                {
                    //define the last point as an object
                    //we suppose that there's no way this point could be valid
                    ::basegfx::B2DPolygon aPoly;
                    
                    maLastPoint.setX( e.X-mnSize );
                    maLastPoint.setY( e.Y-mnSize );
                                        
                    aPoly.append( maLastPoint );
                                        
                    maLastPoint.setX( e.X-mnSize );
                    maLastPoint.setY( e.Y+mnSize );
                    
                    aPoly.append( maLastPoint );
                    maLastPoint.setX( e.X+mnSize );
                    maLastPoint.setY( e.Y+mnSize );
                    
                    aPoly.append( maLastPoint );
                    maLastPoint.setX( e.X+mnSize );
                    maLastPoint.setY( e.Y-mnSize );
                    
                    aPoly.append( maLastPoint );
                    maLastPoint.setX( e.X-mnSize );
                    maLastPoint.setY( e.Y-mnSize );
                    
                    aPoly.append( maLastPoint );
                    
                    //now we have defined a Polygon that is closed
                    
                    //The point is to redraw the LastPoint the way it was originally on the bitmap, 
                    //of the slide
            for( UnoViewVector::iterator aIter=maViews.begin(), aEnd=maViews.end();
                        aIter!=aEnd;
                        ++aIter )
                    {
                        
                        //get via SlideImpl instance the bitmap of the slide unmodified to redraw it
                        SlideBitmapSharedPtr 		 pBitmap( mrSlide.getCurrentSlideBitmap( (*aIter) ) );
                        ::cppcanvas::CanvasSharedPtr pCanvas( (*aIter)->getCanvas() );
                        
                        ::basegfx::B2DHomMatrix 	aViewTransform( (*aIter)->getTransformation() );
                        const ::basegfx::B2DPoint 		aOutPosPixel( aViewTransform * ::basegfx::B2DPoint() );
                        
                        // setup a canvas with device coordinate space, the slide
                        // bitmap already has the correct dimension.
                        ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( pCanvas->clone() );
                        
                        pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() );
                        
                        // render at given output position
                        pBitmap->move( aOutPosPixel );
                        
                        ::basegfx::B2DPolyPolygon aPolyPoly=::basegfx::B2DPolyPolygon(aPoly);
                        aViewTransform.translate(-aOutPosPixel.getX(), -aOutPosPixel.getY());
                        aPolyPoly.transform(aViewTransform);
                        // set clip so that we just redraw a part of the canvas
                        pBitmap->clip(aPolyPoly); 
                        pBitmap->draw( pDevicePixelCanvas );
                        
                        mrScreenUpdater.notifyUpdate(*aIter,true);
                    }
                    
        } 
                else 
                {
                    if( !mbIsLastPointValid )
                    {
                        mbIsLastPointValid = true;
                        maLastPoint.setX( e.X );
                        maLastPoint.setY( e.Y );
                    }
                    else
                    {
                        ::basegfx::B2DPolygon aPoly;
                        aPoly.append( maLastPoint );

                        maLastPoint.setX( e.X );
                        maLastPoint.setY( e.Y );

                        aPoly.append( maLastPoint );

                        // paint to all views
                        for( UnoViewVector::iterator aIter=maViews.begin(), aEnd=maViews.end();
                             aIter!=aEnd;
                             ++aIter )
                        {
                            ::cppcanvas::PolyPolygonSharedPtr pPolyPoly( 
                                ::cppcanvas::BaseGfxFactory::getInstance().createPolyPolygon( (*aIter)->getCanvas(), 
                                                                                              aPoly ) );
                            
                            if( pPolyPoly )
                            {
                                pPolyPoly->setStrokeWidth(mnStrokeWidth);
                                pPolyPoly->setRGBALineColor( maStrokeColor.getIntegerColor() );
                                pPolyPoly->draw();
                                maPolygons.push_back(pPolyPoly);
                            }
                        }

                        // screen update necessary to show painting
                        mrScreenUpdater.notifyUpdate();
                    }
                }
                // mouse events captured
                return true;
            }

            virtual bool handleMouseMoved( const awt::MouseEvent& /*e*/ )
            {
                // not used here
                return false; // did not handle the event
            }
            
            
            void update_settings( bool bUserPaintEnabled, RGBColor const& aUserPaintColor, double dUserPaintStrokeWidth )
            {
                maStrokeColor = aUserPaintColor;
                mnStrokeWidth = dUserPaintStrokeWidth;
                mbActive = bUserPaintEnabled;
                if( !mbActive )
                    disable();
            }

        private:
            ActivitiesQueue&        mrActivitiesQueue;
            ScreenUpdater&          mrScreenUpdater;
            UnoViewVector           maViews;
            PolyPolygonVector 	    maPolygons;
            RGBColor                maStrokeColor;
            double                  mnStrokeWidth;
            basegfx::B2DPoint       maLastPoint;
            basegfx::B2DPoint       maLastMouseDownPos;
            bool                    mbIsLastPointValid;
            bool                    mbIsLastMouseDownPosValid;
            // added bool for erasing purpose :
            bool					mbIsEraseAllModeActivated;
            bool					mbIsEraseModeActivated;
            Slide&					mrSlide;
            sal_Int32               mnSize;
            bool					mbActive;
        };

        UserPaintOverlaySharedPtr UserPaintOverlay::create( const RGBColor&          rStrokeColor,
                                                            double                   nStrokeWidth,
                                                            const SlideShowContext&  rContext,
                                                            const PolyPolygonVector& rPolygons,
                                                            bool					 bActive )
        {
            UserPaintOverlaySharedPtr pRet( new UserPaintOverlay( rStrokeColor,
                                                                  nStrokeWidth,
                                                                  rContext,
                                                                  rPolygons,
                                                                  bActive));

            return pRet;
        }

        UserPaintOverlay::UserPaintOverlay( const RGBColor&          rStrokeColor,
                                            double                   nStrokeWidth,
                                            const SlideShowContext&  rContext,
                                            const PolyPolygonVector& rPolygons,
                                            bool					 bActive ) : 
            mpHandler( new PaintOverlayHandler( rStrokeColor, 
                                                nStrokeWidth,
                                                rContext.mrActivitiesQueue,
                                                rContext.mrScreenUpdater,
                                                rContext.mrViewContainer,
                                                //adding a link to Slide
                                                dynamic_cast<Slide&>(rContext.mrCursorManager),
                                                rPolygons, bActive )),
            mrMultiplexer( rContext.mrEventMultiplexer )
        {
            mrMultiplexer.addClickHandler( mpHandler, 3.0 );
            mrMultiplexer.addMouseMoveHandler( mpHandler, 3.0 );
            mrMultiplexer.addViewHandler( mpHandler );
            mrMultiplexer.addUserPaintHandler(mpHandler);
        }
        
        PolyPolygonVector UserPaintOverlay::getPolygons()
        {
            return mpHandler->getPolygons();
        }
                
        void UserPaintOverlay::drawPolygons()
        {
            mpHandler->drawPolygons();
        }
        
        void UserPaintOverlay::update_settings( bool bUserPaintEnabled, RGBColor const& aUserPaintColor, double dUserPaintStrokeWidth )
        {
            mpHandler->update_settings( bUserPaintEnabled, aUserPaintColor, dUserPaintStrokeWidth );
        }


        UserPaintOverlay::~UserPaintOverlay()
        {
            try
            {
                mrMultiplexer.removeMouseMoveHandler( mpHandler );
                mrMultiplexer.removeClickHandler( mpHandler );
                mrMultiplexer.removeViewHandler( mpHandler );
                mpHandler->dispose();
            }
            catch (uno::Exception &) 
            {
                OSL_FAIL( rtl::OUStringToOString(
                                comphelper::anyToString(
                                    cppu::getCaughtException() ),
                                RTL_TEXTENCODING_UTF8 ).getStr() );
            }
        }
    }
}

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