/*
* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). 
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:   CAlfDrawer implementation.
*
*/




#include <alf/alfdrawer.h>
#include <alf/alfdirectclient.h>
#include "alfcrppluginclient.h"
#include "alflogger.h"
#include "alfdrawerinternal.h"

#include <coemain.h>
#include <w32std.h>
#include <fbs.h>

// Class to hold internal alf drawer data.
NONSHARABLE_CLASS( CAlfDrawer::TAlfDrawerData )
    {
public:
    TAlfDrawerData();
        
public:
    CAlfCrpPluginClient* iClient;
    };

// Creates CAlfCrpPluginClient instance, returns error code.
static TInt CreateAlfCrpClient(CAlfCrpPluginClient*& aClient);
// Creates CAlfCrpPluginClient instance.
static CAlfCrpPluginClient* CreateAlfCrpClientL();

NONSHARABLE_CLASS( TAlfDrawerDirectClient ) : public MAlfDrawerScreenInterface
    {
public:
    TAlfDrawerDirectClient( RAlfDirectClient& aClient );
    TInt GetSizeAndRotation(TSize& aSize, TInt& aRotation);
    TInt ReadPixels(CFbsBitmap* aBitmap);

private:
    RAlfDirectClient& iClient;
    };
   
/**
 * Misc utility methods for CAlfDrawer.
 */
NONSHARABLE_CLASS(AlfDrawerUtils)
    {
public:

    enum TRotation
        {
	    ERotationNone,
	    ERotation90,
	    ERotation180,
	    ERotation270
	    };

    /**
     * Inverse rotate.
     */
    static void RotateBack(TInt aVirtualRotation, TRect& aRect, TSize aScreenSize)
        {
        if( aVirtualRotation == ERotation90 || aVirtualRotation == ERotation270 )
            {
            RotateRect( aVirtualRotation, aRect, aScreenSize, ETrue );  
            }
        else if( aVirtualRotation == ERotation180 )
            {
            RotateRect( aVirtualRotation, aRect, aScreenSize, EFalse );  
            }    
        }

    /**
     * Rotate rect
     */
    static void RotateRect(TInt aDirection, TRect& aRect, TSize aScreenSize, TBool aMirror)
        {
        if (aMirror) aDirection = (aDirection+2)%4; // magic    
        for (;aDirection > 0; aDirection--) 
            {
            TSize rotatedSize(aRect.Size().iHeight, aRect.Size().iWidth);     
            aRect = TRect(TPoint(aRect.iTl.iY, aScreenSize.iWidth - aRect.iTl.iX - rotatedSize.iHeight ), rotatedSize);
            aScreenSize = TSize(aScreenSize.iHeight, aScreenSize.iWidth);
            }
        }
        
    /**
     * Copies screen to bitmap using 'read pixels' operation.
     */
    static void DoCopyScreenToBitmapL( 
        CWsScreenDevice& aDevice,
        CFbsBitmap* aBitmap, 
        const TRect& aRect );

    /**
     * Copies screen to bitmap using 'read pixels' operation.
     */
    static void DoCopyScreenToBitmap2L( 
        MAlfDrawerScreenInterface* aInterface,
        CFbsBitmap* aBitmap, 
        const TRect& aRect );
        
    };

// ---------------------------------------------------------------------------
// NewL
// ---------------------------------------------------------------------------
//
EXPORT_C CAlfDrawer* CAlfDrawer::NewL()
    {
    CAlfDrawer* self = new (ELeave) CAlfDrawer;
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop( self );    
    return self;
    }

// ---------------------------------------------------------------------------
// Destructor
// ---------------------------------------------------------------------------
//
EXPORT_C CAlfDrawer::~CAlfDrawer()
    {
    if ( iData )
        {
        delete iData->iClient;
        delete iData;
        }
    }

// ---------------------------------------------------------------------------
// Finish
// ---------------------------------------------------------------------------
//
EXPORT_C TInt CAlfDrawer::Finish()
    {
    // First perform RWsSession finish operation to ensure
    // that all drawing commands have been issued to renderstage.
    TInt err = CCoeEnv::Static()->WsSession().Finish();

    // Then create CRP instance (if needed).
    if ( err == KErrNone && !iData->iClient ) 
        {
        err = CreateAlfCrpClient( iData->iClient );
        }

    // Finally, wait until alf side has finished rendering.
    if ( err == KErrNone && iData->iClient )
        {
        iData->iClient->Synchronize();
        }
        
    return err;
    }

// ---------------------------------------------------------------------------
// FallbackCopyScreenToBitmap
// ---------------------------------------------------------------------------
//
EXPORT_C TInt CAlfDrawer::FallbackCopyScreenToBitmap(
        CWsScreenDevice& aDevice,
        CFbsBitmap* aBitmap, 
        const TRect& aRect)
    {
    TRAPD(err, AlfDrawerUtils::DoCopyScreenToBitmapL(aDevice, aBitmap, aRect));
    return err;    
    }

// ---------------------------------------------------------------------------
// DoCopyScreenToBitmapL
// ---------------------------------------------------------------------------
//
void AlfDrawerUtils::DoCopyScreenToBitmapL( 
    CWsScreenDevice& aDevice,
    CFbsBitmap* aBitmap, 
    const TRect& aRect )
    {
    if (aDevice.GetScreenNumber() != 0) // only screen 0 supported for now
        {
        User::Leave(KErrNotSupported);
        }
                
    if ( !aBitmap || 
         !aBitmap->Handle() || 
         aBitmap->IsCompressedInRAM() || 
         aBitmap->ExtendedBitmapType() != KNullUid )
        {
        User::Leave(KErrArgument);
        }            

    __ALFLOGSTRING("DoCopyScreenToBitmapL begin");
    
    RAlfDirectClient client;
    CleanupClosePushL(client);

    TAlfDrawerDirectClient clientWrapper(client);
    DoCopyScreenToBitmap2L(&clientWrapper, aBitmap, aRect);   
    
    CleanupStack::PopAndDestroy(); // CleanupClosePushL
    
    __ALFLOGSTRING("DoCopyScreenToBitmapL - Done");    
    }

// ---------------------------------------------------------------------------
// DoCopyScreenToBitmapL
// ---------------------------------------------------------------------------
//
void AlfDrawerUtils::DoCopyScreenToBitmap2L( 
        MAlfDrawerScreenInterface* aInterface,
        CFbsBitmap* aBitmap, 
        const TRect& aRect )
    {
    // Get size & virtual rotation from ALF side.
    // GetSizeAndRotation will also create session to server.

    TInt rotation = 0;
    TSize size;
    User::LeaveIfError(aInterface->GetSizeAndRotation(size, rotation));

    // Calculate device size in pixels (same as aDevice.SizeInPixels())
    TSize deviceSize = size;
    if (rotation == AlfDrawerUtils::ERotation90 ||
        rotation == AlfDrawerUtils::ERotation270 )
        {
        deviceSize.iWidth = size.iHeight;
        deviceSize.iHeight = size.iWidth;
        }

    // Clip aRect to screen boundaries
    TRect actualRect(deviceSize);
    actualRect.Intersection(aRect);
    
    __ALFLOGSTRING2("DoCopyScreenToBitmapL - requested rect origin %d x %d", actualRect.iTl.iX, actualRect.iTl.iY ) ;
    __ALFLOGSTRING2("DoCopyScreenToBitmapL - requested rect %d x %d", actualRect.Size().iWidth, actualRect.Size().iHeight ) ;
    __ALFLOGSTRING2("DoCopyScreenToBitmapL - bitmap rect %d x %d", aBitmap->SizeInPixels().iWidth, aBitmap->SizeInPixels().iHeight );
    __ALFLOGSTRING1("DoCopyScreenToBitmapL - bitmap mode %d", aBitmap->DisplayMode() );

    if ( TRect( aBitmap->SizeInPixels() ).IsEmpty() || 
         actualRect.IsEmpty() )
        {
        __ALFLOGSTRING("DoCopyScreenToBitmapL - empty rect or zero bitmap size");
        return;
        }

    // Select display mode.
    
    TDisplayMode surfaceDisplayMode = EColor64K;
    TDisplayMode bitmapDisplayMode = aBitmap->DisplayMode();
    if ( bitmapDisplayMode == EColor16MU || 
         bitmapDisplayMode == EColor16MA || 
         bitmapDisplayMode == EColor16MAP )
        {
        surfaceDisplayMode = bitmapDisplayMode;
        }

    __ALFLOGSTRING3("DoCopyScreenToBitmapL - surface size %d x %d, mode %d", 
        size.iWidth, size.iHeight, surfaceDisplayMode ) ;

    // Read pixels to temporary bitmap
    CFbsBitmap* surfaceBitmap = new (ELeave) CFbsBitmap;
    CleanupStack::PushL( surfaceBitmap );
    User::LeaveIfError( surfaceBitmap->Create( size, surfaceDisplayMode ) );
    
    TInt err = aInterface->ReadPixels( surfaceBitmap );              
    __ALFLOGSTRING1("DoCopyScreenToBitmapL - ReadPixels returned %d", err);
    User::LeaveIfError( err );  

    // Copy data
    TInt surfaceStride = CFbsBitmap::ScanLineLength(size.iWidth, surfaceDisplayMode);
    __ALFLOGSTRING2("DoCopyScreenToBitmapL - DisplayMode %d, Stride %d", surfaceDisplayMode, surfaceStride);  

    TDisplayMode displayMode = surfaceDisplayMode;
    
    TInt8 bytesPerPixel = 2;
    switch( displayMode )
        {
        case EColor64K:
            {
            bytesPerPixel = 2;
            break;
            }
        case EColor16MU:
        case EColor16MA:
        case EColor16MAP:
            {
            bytesPerPixel = 4;
            break;
            }
        
        default:
            {
            __ALFLOGSTRING1("DoCopyScreenToBitmapL - display mode not supported %d", displayMode);
            User::Leave( KErrNotSupported );
            }
        }
    
    // actualRect (/aRect) must be converted to correct coordinate system.
    TRect surfaceRect(actualRect);
    AlfDrawerUtils::RotateBack(rotation, surfaceRect, deviceSize);
    surfaceRect.Intersection(TRect(size));
    
    __ALFLOGSTRING2("DoCopyScreenToBitmapL - surface rect tl %d x %d", surfaceRect.iTl.iX, surfaceRect.iTl.iY ) ;
    __ALFLOGSTRING2("DoCopyScreenToBitmapL - surface rect size %d x %d", surfaceRect.Size().iWidth, surfaceRect.Size().iHeight ) ;

    CFbsBitmap* bitmap = NULL;
    TBool needTempBitmap = ETrue;

    if( aBitmap->DisplayMode() == displayMode && actualRect == aRect && aBitmap->SizeInPixels() == aRect.Size() )
        {
        // Copy data direcly to given bitmap
        bitmap = aBitmap;
        needTempBitmap = EFalse;
        }
    else 
        {
        // Create temporary bitmap which maches the sufrace's pixel format and source rect size
        bitmap = new (ELeave) CFbsBitmap;
        CleanupStack::PushL( bitmap );
        TInt err = bitmap->Create( actualRect.Size(), displayMode );       
        __ALFLOGSTRING1("DoCopyScreenToBitmapL - Create returned %d", err );
        User::LeaveIfError( err );
        __ALFLOGSTRING("DoCopyScreenToBitmapL - display mode or size don't mach -> using temp bitmap" );            
        }

    surfaceBitmap->BeginDataAccess();
    bitmap->BeginDataAccess();
    
    TUint8* surfacePtr = (TUint8*)surfaceBitmap->DataAddress() + surfaceStride * surfaceRect.iTl.iY;
    
    TUint8* bitmapPtr = (TUint8*)bitmap->DataAddress();
    TInt rowBegin = surfaceRect.iTl.iX * bytesPerPixel;
    TInt captureBitmapWidth = surfaceRect.Width() * bytesPerPixel;              

    const TSize bitmapSize = bitmap->SizeInPixels();
    
    // Initialize with direct copy case parameters
    TInt bitmapPtrColumnDelta = bytesPerPixel;
    TInt bitmapPtrRowDelta = bitmap->DataStride();
    
    const TSize surfaceRectSize(surfaceRect.Size());
    if ( rotation == AlfDrawerUtils::ERotationNone )
        {
        __ALFLOGSTRING("DoCopyScreenToBitmapL - direct copy") ;
        
        // Direct copy case
        for ( TInt row = surfaceRectSize.iHeight - 1 ; row >= 0  ; --row )
            {
            // Copy only the desired part of the bitmap
            memcpy( bitmapPtr, 
                    surfacePtr + rowBegin, 
                    captureBitmapWidth );
            surfacePtr += surfaceStride;
            bitmapPtr += bitmap->DataStride();
            }
        }
    else
        {
        // Handle rotation cases
        //
        // 0 degree case
        //    A B C
        //    1 2 3
        // 90 degrees anti-clockwise
        //    C 3
        //    B 2 
        //    A 1
        // 180 degrees anti-clockwise
        //    3 2 1
        //    C B A
        // 270 degrees anti-clockwise
        //    1 A
        //    2 B
        //    3 C
        // 
        switch (rotation)
            {
            case AlfDrawerUtils::ERotation90:
                bitmapPtrColumnDelta = -bitmap->DataStride();
                bitmapPtrRowDelta = bytesPerPixel;
                bitmapPtr += ( bitmapSize.iHeight - 1 ) * bitmap->DataStride();
                break;
            case AlfDrawerUtils::ERotation180:
                bitmapPtrColumnDelta = -bytesPerPixel;
                bitmapPtrRowDelta = -bitmap->DataStride();
                bitmapPtr += ( bitmapSize.iHeight - 1 ) * bitmap->DataStride() + ( bitmapSize.iWidth - 1 ) * bytesPerPixel;
                break;
            case AlfDrawerUtils::ERotation270:
                bitmapPtrColumnDelta = bitmap->DataStride();
                bitmapPtrRowDelta = -bytesPerPixel;
                bitmapPtr += ( bitmapSize.iWidth - 1 ) * bytesPerPixel;
                break;
            default:
                break;
            }
    
        // We go through surface row by row, column by column and
        // copy pixel data to appropriate place to bitmap
        if ( bytesPerPixel == 4 )
            {
            __ALFLOGSTRING("DoCopyScreenToBitmapL - four bytes per pixel, rotated copy") ;
            
            for ( TInt row = surfaceRectSize.iHeight - 1 ; row >= 0 ; --row )
                {
                TUint8* rowBitmapPtr = bitmapPtr;
                TUint8* rowSurfacePtr = surfacePtr + rowBegin;
                for ( TInt column = surfaceRectSize.iWidth - 1 ; column >= 0 ; --column )
                    {
                    *((TUint32*)rowBitmapPtr) = *((TUint32*)rowSurfacePtr);
                    
                    rowBitmapPtr += bitmapPtrColumnDelta;
                    rowSurfacePtr += bytesPerPixel;
                    }
        
                surfacePtr += surfaceStride;
                bitmapPtr += bitmapPtrRowDelta;
                }
            }
        else
            {
            // Now bytesPerPixel == 2
            __ALFLOGSTRING("DoCopyScreenToBitmapL - two bytes per pixel, rotated copy") ;
            for ( TInt row = surfaceRectSize.iHeight - 1 ; row >= 0 ; --row )
                {
                TUint8* rowBitmapPtr = bitmapPtr;
                TUint8* rowSurfacePtr = surfacePtr + rowBegin;
                for ( TInt column = surfaceRectSize.iWidth - 1 ; column >= 0 ; --column )
                    {
                    *((TUint16*)rowBitmapPtr) = *((TUint16*)rowSurfacePtr);
                    
                    rowBitmapPtr += bitmapPtrColumnDelta;
                    rowSurfacePtr += bytesPerPixel;
                    }
        
                surfacePtr += surfaceStride;
                bitmapPtr += bitmapPtrRowDelta;
                } 
            }
        }
    __ALFLOGSTRING("DoCopyScreenToBitmapL - copy finished" );
    surfaceBitmap->EndDataAccess(ETrue);
    bitmap->EndDataAccess();

    if( !needTempBitmap )
        {
        bitmap = NULL;
        }
    else // bitblit the temporary bitmap to given bitmap
        {
        CFbsBitmapDevice* tempBitmapDevice = CFbsBitmapDevice::NewL( aBitmap );
        CleanupStack::PushL( tempBitmapDevice );
        CFbsBitGc* tempBitmapGc = NULL;
        User::LeaveIfError( tempBitmapDevice->CreateContext( tempBitmapGc ) );             
        CleanupStack::PushL( tempBitmapGc );

        tempBitmapGc->SetDrawMode( CGraphicsContext::EDrawModeWriteAlpha );
        tempBitmapGc->BitBlt( TPoint(), bitmap );
        
        CleanupStack::PopAndDestroy( tempBitmapGc );
        CleanupStack::PopAndDestroy( tempBitmapDevice );
        CleanupStack::PopAndDestroy( bitmap );
        }
    
    CleanupStack::PopAndDestroy( surfaceBitmap );
    }

// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
//
CAlfDrawer::CAlfDrawer()
    {
    }
    
// ---------------------------------------------------------------------------
// ConstructL
// ---------------------------------------------------------------------------
//
void CAlfDrawer::ConstructL()
    {
    iData = new (ELeave) TAlfDrawerData;
    CreateAlfCrpClient( iData->iClient );
    }

// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
//
CAlfDrawer::TAlfDrawerData::TAlfDrawerData()
    : iClient( NULL )
    {
    }

// ---------------------------------------------------------------------------
// CopyScreenToBitmap
// ---------------------------------------------------------------------------
//
EXPORT_C TInt AlfDrawerInternal::CopyScreenToBitmap(
        MAlfDrawerScreenInterface* aInterface, 
        CFbsBitmap* aBitmap, 
        const TRect& aRect )
    {
    TRAPD(err, AlfDrawerUtils::DoCopyScreenToBitmap2L(aInterface, aBitmap, aRect));
    return err;
    }

// ---------------------------------------------------------------------------
// CreateAlfCrpClient
// ---------------------------------------------------------------------------
//
static TInt CreateAlfCrpClient(CAlfCrpPluginClient*& aClient)
    {
    TRAPD(err, aClient = CreateAlfCrpClientL());
    return err;
    }

// ---------------------------------------------------------------------------
// CreateAlfCrpClientL
// ---------------------------------------------------------------------------
//
static CAlfCrpPluginClient* CreateAlfCrpClientL()
    {
    CAlfCrpPluginClient* client = new (ELeave) CAlfCrpPluginClient;
    CleanupStack::PushL( client );
    client->ConstructL();
    CleanupStack::Pop( client );
    return client;
    }

// ---------------------------------------------------------------------------
// TAlfDrawerDirectClient
// ---------------------------------------------------------------------------
//
TAlfDrawerDirectClient::TAlfDrawerDirectClient( RAlfDirectClient& aClient )
    : iClient( aClient )
    {
    }
    
// ---------------------------------------------------------------------------
// GetSizeAndRotation
// ---------------------------------------------------------------------------
//
TInt TAlfDrawerDirectClient::GetSizeAndRotation(TSize& aSize, TInt& aRotation)
    {
    return iClient.GetSizeAndRotation(aSize, aRotation);
    }
    
// ---------------------------------------------------------------------------
// ReadPixels
// ---------------------------------------------------------------------------
//
TInt TAlfDrawerDirectClient::ReadPixels(CFbsBitmap* aBitmap)
    {
    return iClient.ReadPixels(aBitmap->Handle());
    }

