// Copyright (c) 1998-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:
//

#include <hal.h>
#include <cdsb.h>
#include <e32hal.h>

#ifdef SYMBIAN_GRAPHICS_GCE
#undef __WINS__
#include "../sgeneric/scdraw.h"
#elif defined(__WINS__)
#include "_WININC.H"
#endif


//
//	CGenericDirectScreenBitmap: Implementation of generic CDirectScreenBitmap class
//
NONSHARABLE_CLASS(CGenericDirectScreenBitmap) : public CDirectScreenBitmap
	{
public:
	 CGenericDirectScreenBitmap();
	 CGenericDirectScreenBitmap(TInt aScreenNo);
	~CGenericDirectScreenBitmap();

public:
	// Pure virtual from CDirectScreenBitmap
	virtual TInt Create(const TRect& aScreenRect, TSettingsFlags aSettingsFlags);
	virtual TInt BeginUpdate(TAcceleratedBitmapInfo& aBitmapInfo);
	virtual void EndUpdate(TRequestStatus& aComplete);
	virtual void EndUpdate(const TRect& aScreenRect, TRequestStatus& aComplete);
	virtual void Close();

public:
	enum TDsbPanic
		{
		EPanicAlreadyCreated = 1,
		EPanicNotCreated     = 2,
		EPanicInvalidMode    = 3,
		EPanicOutOfBounds	 = 4,
		EPanicNotWordAligned = 5,
		EPanicInvalidRect    = 6,
		EPanicIncompleteRequest = 7,
		EPanicHandleReturnedIncorrect = 8
		};
    static void Panic(TDsbPanic aPanic);

private:
	TInt DoCreate();

#ifdef __WINS__
	TRgb ExtractRgb(TUint8* aBuffer,TInt aPixelOffset) const;
	void WinsUpdate(const TRect& aScreenRect);
#endif

private:
#ifndef __WINS__
	TInt    iVideoAddress;
#endif
#ifdef SYMBIAN_GRAPHICS_GCE
    CScreenDeviceHelper iSurfaceUpdater;
    TUidPixelFormat iPixelFormat;
#endif
	TAcceleratedBitmapInfo iBitmapInfo;
	TUint32 iSettingsFlags;
	TRect   iUpdateRect;
	TInt    iDisplayOffsetLines;
	TInt    iBytesPerPixel;
	TInt	iDisplayOffsetPixel;
	TBool   iCreated;
    TInt    iScreenNo;
    RChunk 	iChunk;
    TBool 	iHasChunk;
	};

//
// Create a new instance of a DSB
// The default screen (with number 0) will be used
EXPORT_C CDirectScreenBitmap* CDirectScreenBitmap::NewL()
	{
	CGenericDirectScreenBitmap* pDsb=new(ELeave) CGenericDirectScreenBitmap;
	return(pDsb);
	}

//
// Create a new instance of a DSB
// The screen with aScreenNo number will be used
EXPORT_C CDirectScreenBitmap* CDirectScreenBitmap::NewL(TInt aScreenNo)
    {
	CGenericDirectScreenBitmap* pDsb = new (ELeave) CGenericDirectScreenBitmap(aScreenNo);
	return pDsb;
    }

CGenericDirectScreenBitmap::CGenericDirectScreenBitmap() :
#ifndef __WINS__
	iVideoAddress(NULL),
#endif
	iBitmapInfo(),
	iSettingsFlags(ENone),
	iUpdateRect(),
	iDisplayOffsetLines(0),
	iBytesPerPixel(0),
	iDisplayOffsetPixel(0),
	iHasChunk(EFalse)
	{
    }

CGenericDirectScreenBitmap::CGenericDirectScreenBitmap(TInt aScreenNo) :
    iScreenNo(aScreenNo)
    {
    }

CGenericDirectScreenBitmap::~CGenericDirectScreenBitmap()
	{
	Close();
	}

//
// Create a DSB object using a region of the screen previously
// 'claimed' via the Window Servers Direct Screen Acceass API
//
TInt CGenericDirectScreenBitmap::Create(const TRect& aScreenRect, TSettingsFlags aSettingsFlags)
	{
	__ASSERT_ALWAYS(!iCreated, Panic(EPanicAlreadyCreated));

	__ASSERT_ALWAYS((aSettingsFlags&(EDoubleBuffer|EIncrementalUpdate)) !=
					(EDoubleBuffer|EIncrementalUpdate), Panic(EPanicInvalidMode));

    //Initialize iScreenNo - its value occupies upper 5 bits of aSettingsFlags.
    //(32 - 1) is max allowed screen number
    iScreenNo = static_cast <TUint32> ((aSettingsFlags & 0xF8000000) >> 27);

	// Find the screen dimensions from the HAL and validate
	TInt screenWidth = 0;
	TInt screenHeight = 0;

	TInt r = HAL::Get(iScreenNo, HALData::EDisplayXPixels, screenWidth);
	if (r!=KErrNone)
		{
		return r;
		}

	r = HAL::Get(iScreenNo, HALData::EDisplayYPixels,screenHeight);
	if (r!=KErrNone)
		{
		return r;
		}
	
	__ASSERT_ALWAYS(aScreenRect.iTl.iX >= 0 &&
	                aScreenRect.iTl.iY >= 0 &&
	                ((aScreenRect.Width() <= screenWidth &&
	                aScreenRect.Height() <= screenHeight)
	                ||
	                (aScreenRect.Width() <= screenHeight &&
	                aScreenRect.Height() <= screenWidth)),
	                Panic(EPanicOutOfBounds));
	
	__ASSERT_ALWAYS(aScreenRect.Width() > 0 &&
					aScreenRect.Height() > 0, Panic(EPanicInvalidRect));

	iUpdateRect = aScreenRect;
	iSettingsFlags = aSettingsFlags;

	r = DoCreate();
	if(r != KErrNone)
		{
		return r;
		}

	// Check that LHS is word-aligned
	const TUint pixelStartInBytes = aScreenRect.iTl.iX*iBytesPerPixel;
	const TUint pixelEndInBytes = aScreenRect.iBr.iX*iBytesPerPixel;

	// Check the alignment
	__ASSERT_ALWAYS(!(pixelStartInBytes & 0x03), Panic(EPanicNotWordAligned));
	__ASSERT_ALWAYS(!(pixelEndInBytes & 0x03),	 Panic(EPanicNotWordAligned));

	const TUint scanlineBytes = pixelEndInBytes-pixelStartInBytes;

#ifndef __WINS__	
	iVideoAddress += pixelStartInBytes +						// X Offset
						 iUpdateRect.iTl.iY*iDisplayOffsetLines +	// Y Offset
						 iDisplayOffsetPixel;						 // Pixel (Palette) Offset
#endif 
	
	// Since this is a generic implementation, we only require one
	//  buffer for both incremental and double buffered mode
	iBitmapInfo.iAddress = (TUint8*)User::Alloc(scanlineBytes * iUpdateRect.Height());
	if(!iBitmapInfo.iAddress)
		return KErrNoMemory;

	iBitmapInfo.iSize			 = TSize(iUpdateRect.Width(), iUpdateRect.Height());
	iBitmapInfo.iLinePitch		 = scanlineBytes;
	iBitmapInfo.iPhysicalAddress = 0;

	// All done
	iCreated = ETrue;

	return KErrNone;
	}

#ifndef __WINS__
//
// Attempt to initialise using the HAL
//
TInt CGenericDirectScreenBitmap::DoCreate()
	{
    // For GCE, if multiple modes are supported, only one needs to be returned.
	// On the emulator, what is returned via EDisplayMode can be set via ColorDepth setting in epoc.ini
	// On the hardware, 24/32bpp color mode is returned via EDisplayMode as pre-defined. 
	TInt displayMode = 0;
	TInt r1 = HAL::Get(iScreenNo, HALData::EDisplayMode, displayMode);
	if (r1!=KErrNone)
		{
		return r1;
		}

	TInt bitsPerPixel = displayMode;

	r1 = HAL::Get(iScreenNo, HALData::EDisplayBitsPerPixel, bitsPerPixel);
	if (r1!=KErrNone)
		{
		return r1;
		}
	iBytesPerPixel = (bitsPerPixel+7) >> 3;

	// Store the important driver parameters to avoid HAL calls when drawing
	iDisplayOffsetPixel = displayMode;
	r1 = HAL::Get(iScreenNo, HALData::EDisplayOffsetToFirstPixel, iDisplayOffsetPixel);
	if (r1!=KErrNone)
		{
		return r1;
		}
	iDisplayOffsetLines = displayMode;
	r1 = HAL::Get(iScreenNo, HALData::EDisplayOffsetBetweenLines, iDisplayOffsetLines);
	if (r1!=KErrNone)
		{
		return r1;
		}
	
	TInt val = 0;
	r1 = HAL::Get(iScreenNo, HALData::EDisplayMemoryHandle,val);
	if (r1 == KErrNotSupported)
		{
		// EDisplayMemoryHandle is not supported, use the address instead
		r1 = HAL::Get(iScreenNo, HALData::EDisplayMemoryAddress, iVideoAddress);
		if (r1!=KErrNone)
			{
			return r1;
			}
		}
	else if (r1 == KErrNone)
		{
		// EDisplayMemoryHandle is supported, use the handle base address
		__ASSERT_ALWAYS(val != 0, Panic(EPanicHandleReturnedIncorrect));
		RChunk chunk;
		r1 = chunk.SetReturnedHandle(val);
		if (r1 != KErrNone)
			{
			return r1;
			}
		iChunk = chunk;
		r1 = iChunk.Duplicate(RThread(), EOwnerProcess);
		chunk.Close();
		if (r1 != KErrNone)
			{
			return r1;
			}
		iVideoAddress = (TInt)iChunk.Base();
		iHasChunk = ETrue;
		}
	else  
		{
		return r1;
		}

	// The HAL doesn't specify explicit dislpay modes, so we need
	// to calculate the mode using bitsPerPixel and isMono
	TBool isMono = displayMode;
	r1 = HAL::Get(iScreenNo, HALData::EDisplayIsMono, isMono);
	if (r1!=KErrNone)
		{
		return r1;   
		}
     switch(bitsPerPixel)
		{
		// EGray2, EGray4, EGray16, EColor16 not supported on the hardware
		// The generic scdv support EGray2, EGray4, EGray16, EColor16 on the emulator
		// However, the low display modes should not be used.
		case 1:
			iBitmapInfo.iPixelShift = 0;
			iBitmapInfo.iDisplayMode = EGray2;
			iBytesPerPixel = 2;
#ifdef SYMBIAN_GRAPHICS_GCE
			iPixelFormat = EUidPixelFormatUnknown;
#endif
			break;
		case 2:
			iBitmapInfo.iPixelShift = 1;
			iBitmapInfo.iDisplayMode = EGray4;
			iBytesPerPixel = 2;
#ifdef SYMBIAN_GRAPHICS_GCE
			iPixelFormat = EUidPixelFormatUnknown;
#endif
			break;
		case 4:
			iBitmapInfo.iPixelShift = 2;
			iBitmapInfo.iDisplayMode = isMono ? EGray16 : EColor16;
			iBytesPerPixel = 2;
#ifdef SYMBIAN_GRAPHICS_GCE
			iPixelFormat = EUidPixelFormatUnknown;
#endif
			break;	 
		case 8:
			iBitmapInfo.iPixelShift = 3;
			iBitmapInfo.iDisplayMode = isMono ? EGray256 : EColor256;
			iBytesPerPixel = 2;
#ifdef SYMBIAN_GRAPHICS_GCE
			iPixelFormat = EUidPixelFormatUnknown;
#endif
			break;
		case 12:
			iBitmapInfo.iPixelShift = 4;
			iBitmapInfo.iDisplayMode = EColor4K;
#ifdef SYMBIAN_GRAPHICS_GCE
			iPixelFormat = EUidPixelFormatXRGB_4444;
#endif
			break;
		case 16:
			iBitmapInfo.iPixelShift = 4;
			iBitmapInfo.iDisplayMode = EColor64K;
#ifdef SYMBIAN_GRAPHICS_GCE
			iPixelFormat = EUidPixelFormatRGB_565;
#endif
			break;
		case 24:
		case 32:
			iBitmapInfo.iPixelShift = 5;
			iBitmapInfo.iDisplayMode = EColor16MA;
#ifdef SYMBIAN_GRAPHICS_GCE
			iPixelFormat = EUidPixelFormatARGB_8888;
			iBytesPerPixel = 4;
#endif
			break;
		default:
			return KErrNotSupported;
		}

#ifdef SYMBIAN_GRAPHICS_GCE
    TInt ret = iSurfaceUpdater.Construct(iScreenNo,iPixelFormat,displayMode);
 	if (ret != KErrNone)
 		{
 		return ret;
 		}
#endif

	return KErrNone;
	}
#endif

#ifdef __WINS__
//
// Attempt to initialise for WINS
// This is intended for use if HAL initialisation fails.
//
TInt CGenericDirectScreenBitmap::DoCreate()
	{
    RWindows* window = ::WindowHandler(iScreenNo);
    if(window)
		{
		iBitmapInfo.iDisplayMode = window->iDisplayMode;
		iDisplayOffsetLines		 = window->iEpocBitmapLinePitch;
		iDisplayOffsetPixel		 = 0;

		switch(iBitmapInfo.iDisplayMode)
			{
			case EGray2:
				iBitmapInfo.iPixelShift = 0;
				iBytesPerPixel = 1;
				break;
			case EGray4:
				iBitmapInfo.iPixelShift = 1;
				iBytesPerPixel = 1;
				break;
			case EGray16:
			case EColor16:
				iBitmapInfo.iPixelShift = 2;
				iBytesPerPixel = 1;
				break;
			case EGray256:
			case EColor256:
				iBitmapInfo.iPixelShift = 3;
				iBytesPerPixel = 1;
				break;
			case EColor4K:
			case EColor64K:
				iBitmapInfo.iPixelShift = 4;
				iBytesPerPixel = 2;
				break;
			case EColor16M:
				iBitmapInfo.iPixelShift = 5;
				iBytesPerPixel = 3;
				break;
			case ERgb:
			case EColor16MU:
			case EColor16MA:
			case EColor16MAP:
				iBitmapInfo.iPixelShift = 5;
				iBytesPerPixel = 4;
				break;
			default:
				return KErrNotSupported;
			}

		return KErrNone;
		}
	else
		{
		return KErrNoMemory;
		}
	}
#endif

//
// Begin update - aBitmapinfo returns the current bitmap
//
TInt CGenericDirectScreenBitmap::BeginUpdate(TAcceleratedBitmapInfo& aBitmapInfo)
	{
	__ASSERT_ALWAYS(iCreated, Panic(EPanicNotCreated));
	aBitmapInfo = iBitmapInfo;
	return KErrNone;
	}


#ifdef __WINS__

void CGenericDirectScreenBitmap::WinsUpdate(const TRect& aScreenRect)
	{
	const TUint dS = iBitmapInfo.iLinePitch;
	const TUint offY = aScreenRect.iTl.iY - iUpdateRect.iTl.iY;
	TUint8* pS = iBitmapInfo.iAddress + (offY*dS);
	RWindows* window = ::WindowHandler(iScreenNo);
	TInt orientation=window->Orientation();
	TRect orientedRect;
	TSize orientedSize;

	orientedSize = window->GetOrientedSize();
	orientedRect = window->GetOrientedRect(aScreenRect);

	TInt BrX = iUpdateRect.iBr.iX-1;
	TInt BrY = iUpdateRect.iBr.iY-1;

	for(TInt y=aScreenRect.iTl.iY; y < aScreenRect.iBr.iY; y++)
		{

		TRgb pixelColor;

		TInt xSource = aScreenRect.iTl.iX-iUpdateRect.iTl.iX;

		for(TInt x = aScreenRect.iTl.iX; x < aScreenRect.iBr.iX; x++)
			{
			TUint8* pD;

			switch(orientation)
				{
				case 0:
					pD = window->PixelAddress(x, y);
					break;
				case 1:
					pD = window->PixelAddress(y, BrX-x);
					break;
				case 2:
					pD = window->PixelAddress(BrX-x, BrY-y);
					break;
				case 3:
					pD = window->PixelAddress(BrY-y, x);
					break;
				default:
					break;
				}

			// Convert the user colour to RGB, as required by the WINS Emulator
			pixelColor = ExtractRgb(pS, xSource++);

			pD[0] = TUint8(pixelColor.Blue());
			pD[1] = TUint8(pixelColor.Green());
			pD[2] = TUint8(pixelColor.Red());

			}
		pS += dS;
		}

	RRegion region;
	region.AddRect(orientedRect);
	window->Update(region,orientedSize);
	region.Close();
	}

#endif

//
// End Update - Commit changes to the Frame Buffer
//
void CGenericDirectScreenBitmap::EndUpdate(TRequestStatus& aComplete)
	{
	__ASSERT_ALWAYS(iCreated, Panic(EPanicNotCreated));

	TUint8* pS = iBitmapInfo.iAddress;
	const TUint dS = iBitmapInfo.iLinePitch;
	
#ifdef __WINS__

	WinsUpdate(iUpdateRect);

#else
	TUint8* pD = (TUint8*)iVideoAddress;
	const TUint dD = iDisplayOffsetLines;

	for(TInt y=iUpdateRect.Height(); y>0; y--)
		{
		Mem::Move((void *)pD, (void *)pS, dS);
		pS += dS;
		pD += dD;
		}
#endif
	
#ifdef SYMBIAN_GRAPHICS_GCE
	// update the region and complete notification on the client's TRequestStatus
	// This means the backbuffer is held up until the surface update has completed
	// notification.
	iSurfaceUpdater.UpdateRegion(iUpdateRect);
	iSurfaceUpdater.Update(aComplete);
#else
	// In the generic implementation, complete the request immediately to allow the client
	// to render to the back buffer
	TRequestStatus* pComplete=&aComplete;
	User::RequestComplete(pComplete,KErrNone);
#endif

 	}

//
// End Update - Commit changes within the given area to the Frame Buffer
//
void CGenericDirectScreenBitmap::EndUpdate(const TRect& aScreenRect, TRequestStatus& aComplete)
	{
	__ASSERT_ALWAYS(iCreated, Panic(EPanicNotCreated));

	__ASSERT_ALWAYS(aScreenRect.iTl.iX >= iUpdateRect.iTl.iX &&
					aScreenRect.iTl.iY >= iUpdateRect.iTl.iY &&
					aScreenRect.iBr.iX <= iUpdateRect.iBr.iX &&
					aScreenRect.iBr.iY <= iUpdateRect.iBr.iY, Panic(EPanicOutOfBounds));

	__ASSERT_ALWAYS(aScreenRect.Width() > 0 &&
					aScreenRect.Height() > 0, Panic(EPanicInvalidRect));

	// We can only use this functionality in Incremental Update mode
	__ASSERT_ALWAYS(iSettingsFlags == CDirectScreenBitmap::EIncrementalUpdate, Panic(EPanicInvalidMode));

	const TUint offY = aScreenRect.iTl.iY - iUpdateRect.iTl.iY;

	const TUint dS = iBitmapInfo.iLinePitch;

#ifdef __WINS__

	WinsUpdate(aScreenRect);

#else
	
	TInt xLeft  = aScreenRect.iTl.iX*iBytesPerPixel;
	TInt xRight = aScreenRect.iBr.iX*iBytesPerPixel;

	// We have already tested the limits against iUpdateRect, which is
	// word aligned, so we are safe to perform a round up/down here
	// without fear of overrunning the buffer.

	xLeft &= ~0x03;
	xRight = (xRight+0x03)&~0x03;

	const TUint offX = xLeft - iUpdateRect.iTl.iX*iBytesPerPixel;

	const TUint bytesToCopy = xRight-xLeft;

	const TUint dD = iDisplayOffsetLines;

	TUint8* pS = iBitmapInfo.iAddress + offX + (offY*dS);
	TUint8* pD = (TUint8*)iVideoAddress + offX + (offY*dD);

	for(TInt y=aScreenRect.Height(); y>0; y--)
		{
		Mem::Move((void *)pD, (void *)pS, bytesToCopy);
		pS += dS;
		pD += dD;
		}
#endif

#ifdef SYMBIAN_GRAPHICS_GCE
	// update the region and complete notification on the client's TRequestStatus
	// This means the backbuffer is held up until the surface update has completed
	// notification.
	iSurfaceUpdater.UpdateRegion(iUpdateRect);
	iSurfaceUpdater.Update(aComplete);
#else
	// In the generic implementation, complete the request immediately to allow the client
	// to render to the back buffer
	TRequestStatus* pComplete=&aComplete;
	User::RequestComplete(pComplete,KErrNone);
#endif	

	}

#ifdef __WINS__
//
// Extracts the RGB value of the specidied pixel from the buffer
//
TRgb CGenericDirectScreenBitmap::ExtractRgb(TUint8* aBuffer,TInt aPixelOffset) const
	{
	switch (iBitmapInfo.iDisplayMode)
		{
	case EGray2:
		{
		TUint8 byte = *(aBuffer + (aPixelOffset >> 3));
		if (byte & (1 << (aPixelOffset & 7)))
			return KRgbWhite;
		return KRgbBlack;
		}
	case EGray4:
		{
		TUint8 byte = *(aBuffer + (aPixelOffset >> 2));
		byte >>= ((aPixelOffset & 3) << 1);
		return TRgb::_Gray4(byte & 3);
		}
	case EGray16:
		{
		TUint8 byte = *(aBuffer + (aPixelOffset >> 1));
		if (aPixelOffset & 1)
			byte >>= 4;
		return TRgb::_Gray16(byte & 0xf);
		}
	case EGray256:
		return TRgb::_Gray256(*(aBuffer + aPixelOffset));
	case EColor16:
		{
		TUint8 byte = *(aBuffer + (aPixelOffset >> 1));
		if (aPixelOffset & 1)
			byte >>= 4;
		return TRgb::Color16(byte & 0xf);
		}
	case EColor256:
		return TRgb::Color256(*(aBuffer + aPixelOffset));
	case EColor4K:
		{
		TUint16 doubleByte = *(((TUint16*)aBuffer) + aPixelOffset);
		return TRgb::_Color4K(doubleByte & 0xfff);
		}
	case EColor64K:
		{
		TUint16 doubleByte = *(((TUint16*)aBuffer) + aPixelOffset);
		return TRgb::_Color64K(doubleByte);
		}
	case EColor16M:
		{
		aBuffer += aPixelOffset * 3;
		TInt value = *aBuffer++;
		value |= *aBuffer++ << 8;
		value |= *aBuffer << 16;
		return TRgb::_Color16M(value);
		}
	case ERgb:
		return *(((TRgb*)aBuffer) + aPixelOffset);
	case EColor16MU:
		{
		aBuffer += aPixelOffset * 4;
		TInt value = *aBuffer++;
		value |= *aBuffer++ << 8;
		value |= *aBuffer << 16;
		return TRgb::_Color16MU(value);
		}
	case EColor16MA:
		{
		aBuffer += aPixelOffset * 4;
		TInt value = *aBuffer++;
		value |= *aBuffer++ << 8;
		value |= *aBuffer++ << 16;
		value |= *aBuffer << 24;
		return TRgb::_Color16MA(value);
		}
	case EColor16MAP:
		{
		aBuffer += aPixelOffset * 4;
		TInt value = *aBuffer++;
		value |= *aBuffer++ << 8;
		value |= *aBuffer++ << 16;
		value |= *aBuffer << 24;
		return TRgb::_Color16MAP(value);
		}	
	default:
		break;
		};
	return KRgbBlack;
	}
#endif


//
// Close - Deallocate resources and cancel outstanding updates
//
void CGenericDirectScreenBitmap::Close()
	{
	if(iCreated)
		{
#ifdef SYMBIAN_GRAPHICS_GCE
		iSurfaceUpdater.CancelUpdateNotification();
#endif
		User::Free(iBitmapInfo.iAddress);
		iBitmapInfo.iAddress = NULL;
		iCreated = EFalse;
		}
	if (iHasChunk)
		{
		iChunk.Close();
		}
	}

//
// Panic the user if a serious error occurs
//
void CGenericDirectScreenBitmap::Panic(TDsbPanic aPanic)
	{
	_LIT( KPanicString, "CGenericDirectScreenBitmap" );
	User::Panic( KPanicString, aPanic );
	}


