/*
* Copyright (c) 2008 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 "HuiFxEngine.h"
#include "HuiFxEffectParser.h"
#include "HuiFxEffect.h"
#include "HuiFxEffectCache.h"
#include <uiacceltk/HuiEnv.h>
#include <uiacceltk/HuiStatic.h>
#include <uiacceltk/HuiUtil.h>
#include <bautils.h>
#include <uiacceltk/HuiDisplay.h>

#if 0
_LIT(cache1, "C:\\Data\\drop_shadow.fxml");
const TDesC *cache_array[]={ &cache1 };
TInt cache_array_size = sizeof(cache_array)/sizeof(cache_array[0]);
#endif

EXPORT_C void CHuiFxEngine::ConstructL(THuiFxEngineType aEngineType)
    {
    iType = aEngineType;
    iBuffersInUse = 0;
#ifdef HUIFX_RBCACHE_ENABLED
    iBuffersInCache = 0;
    // critical to be initialised to zeros: zero indicates an empty entry in array.
    memset(iBufferCache, '\0', sizeof(iBufferCache));
#endif
#ifdef HUIFX_EFFECTCACHE_ENABLED
    iCache = CHuiFxEffectCache::NewL();

#if 0
    // Preloading cache items
    for(int i=0;i<cache_array_size;i++)
        {
        const TDesC *filename = cache_array[i];
        if (!BaflUtils::FileExists( CHuiStatic::FsSession(), *filename ))
            {
            continue;
            }
        CHuiFxEffect *effect = NULL;
        TRect extRectI;
        TRect *extRect = &extRectI;
        CHuiFxEffectCacheEffectNode *node = new(ELeave) CHuiFxEffectCacheEffectNode(*filename, effect, 0, extRect, this);
        CleanupStack::PushL(node);
        iCache->FindOrCreateL(node); // moves ownership
        CleanupStack::Pop();
        //delete effect; // no need for this effect.
        }
#endif
#endif
    CHuiStatic::Env().AddMemoryLevelObserver(this);
    }

EXPORT_C TBool CHuiFxEngine::FxmlUsesInput1(const TDesC &aFileName)
    {
#ifdef HUIFX_EFFECTCACHE_ENABLED
    return iCache->FxmlUsesInput1(aFileName);
#endif
#ifndef HUIFX_EFFECTCACHE_ENABLED
    return EFalse;
#endif
    }

TBool CHuiFxEngine::FxmlUsesInput1(CHuiFxEffect& aEffect)
    {
    RArray<THuiFxVisualSrcType> array;
    aEffect.FxmlVisualInputs(array);
 
    TInt c = array.Count();
    for(TInt i = 0; i<c; i++)
        {
        THuiFxVisualSrcType val = array[i];
        if (val == EVisualSrcInput1)
            {
            array.Close();
            return ETrue;
            }
        }
    array.Close();
    return EFalse;    
    }

    
EXPORT_C void CHuiFxEngine::RegisterEffectL(const TDesC &aFileName)
    {
#ifdef HUIFX_EFFECTCACHE_ENABLED
    const TDesC *filename = &aFileName;
    if (!BaflUtils::FileExists( CHuiStatic::FsSession(), *filename ))
        {
        return;
        }
    CHuiFxEffect *effect = NULL;
    TRect extRectI;
    TRect *extRect = &extRectI;
    CHuiFxEffectCacheEffectNode *node = new(ELeave) CHuiFxEffectCacheEffectNode(*filename, effect, 0, extRect, this);
    CleanupStack::PushL(node);
    iCache->CreateL(node); // moves ownership
    CleanupStack::Pop();
#endif
    }

#ifdef HUIFX_EFFECTCACHE_ENABLED
EXPORT_C void CHuiFxEngine::UnRegisterEffectL(const TDesC &aFileName)
    {
    MHuiFxEffectCacheNode *node = iCache->FindDup(aFileName);
    if (node) 
        iCache->UnUse(node);
#else
EXPORT_C void CHuiFxEngine::UnRegisterEffectL(const TDesC &)
    {
#endif
    }

EXPORT_C void CHuiFxEngine::LoadEffectL(
    const TDesC& aFileName,
    CHuiFxEffect*& aEffect,
    MHuiEffectable* aVisual,
#ifndef HUIFX_EFFECTCACHE_ENABLED
    TRect* extRect,
#else
    TRect*,
#endif		
    MAlfGfxEffectObserver* aEffectEndObserver,
    TInt aHandle, 
    TInt aFlags )
    {
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::LoadEffectL - %S, 0x%x, 0x%x"),&aFileName, aVisual, aEffect );
#endif
    if (aFileName.Length() == 0)
    	{
    	User::Leave( KErrNotFound);
    	}
    //TODO: reuse parser?
    if( aEffect != NULL ) User::Leave( KErrGeneral );
#ifndef HUIFX_EFFECTCACHE_ENABLED
    iExtRect = TRect(); // clear the extrect
    if ( extRect )
        {
        iExtRect = *extRect;
        }
    CHuiFxEffectParser* parser = CHuiFxEffectParser::NewL( *this, aVisual );
    CleanupStack::PushL( parser );
    parser->SetEffectEndObserver( aEffectEndObserver, aHandle );
    parser->SetEffectFlags( aFlags );
    // Parsing is asychronous, but we are not waiting for it to end.
    // CHuiFxEffectParser will take care of everything, and CHuiVisual
    // will kill the parser when parsing is finished or cancelled.
    aVisual->SetEffectParser( parser ); // ownership transferred
    CleanupStack::Pop( parser );    
    parser->ParseL( aFileName, iExtRect );
#endif
#ifdef HUIFX_EFFECTCACHE_ENABLED
    CHuiFxEffectCacheEffectNode *node = new (ELeave) CHuiFxEffectCacheEffectNode(aFileName, aEffect, aVisual, &iExtRect, this);
    node->SetEffectEndObserver( aEffectEndObserver, aHandle );
    node->SetEffectFlags( aFlags );
    CleanupStack::PushL(node);
    iCache->FindOrCreateL(node); // takes ownership
    CleanupStack::Pop();
#endif
    }

EXPORT_C void CHuiFxEngine::LoadGroupEffectL(
    const TDesC& aFileName,
    CHuiFxEffect*& aEffect,
    MHuiEffectable* aVisual,
    TInt aGroup,
#ifndef HUIFX_EFFECTCACHE_ENABLED
    TRect* extRect,
#else
    TRect*,
#endif		
    MAlfGfxEffectObserver* aEffectEndObserver,
    TInt aHandle,
    TInt aFlags )
    {
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::LoadEffectL - %S, 0x%x, 0x%x"),&aFileName, aVisual, aEffect );
#endif
    if (aFileName.Length() == 0)
    	{
    	User::Leave( KErrNotFound);
    	}
    //TODO: reuse parser?
    if( aEffect != NULL ) User::Leave( KErrGeneral );
#ifndef HUIFX_EFFECTCACHE_ENABLED
    iExtRect = TRect(); // clear the extrect
    if ( extRect )
        {
        iExtRect = *extRect;
        }
    CHuiFxEffectParser* parser = CHuiFxEffectParser::NewL( *this, aVisual );
    CleanupStack::PushL( parser );
    parser->SetEffectEndObserver( aEffectEndObserver, aHandle );
    parser->SetEffectFlags( aFlags );
    // Parsing is asychronous, but we are not waiting for it to end.
    // CHuiFxEffectParser will take care of everything, and CHuiVisual
    // will kill the parser when parsing is finished or cancelled.
    aVisual->SetEffectParser( parser ); // ownership transferred
    CleanupStack::Pop( parser );    
    parser->ParseL( aFileName, iExtRect );
#endif
#ifdef HUIFX_EFFECTCACHE_ENABLED
    CHuiFxEffectCacheEffectNode *node = new (ELeave) CHuiFxEffectCacheEffectNode(aFileName, aEffect, aVisual, &iExtRect, this);
    node->SetEffectEndObserver( aEffectEndObserver, aHandle );
    if (aGroup != KErrNotFound)
    	{
		aFlags |= KHuiFxWaitGroupSyncronization;
		node->SetEffectGroup(aGroup);
    	}
    node->SetEffectFlags( aFlags );
    
    CleanupStack::PushL(node);
    iCache->FindOrCreateL(node); // takes ownership
    CleanupStack::Pop();
#endif
    }
EXPORT_C CHuiFxEngine::~CHuiFxEngine()
    {  
    iActiveEffects.ResetAndDestroy();
    iActiveEffects.Close();
#ifdef HUIFX_EFFECTCACHE_ENABLED
    delete iCache;
#endif
    CHuiStatic::Env().RemoveMemoryLevelObserver(this);
    }

EXPORT_C void CHuiFxEngine::Release()
    {
    ASSERT(iBuffersInUse == 0);
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::Release - 0x%x"),this);
#endif
#ifdef HUIFX_RBCACHE_ENABLED
    int freedBuffers = 0;
    for(int i = 0; i < CACHE_SIZE; i++)
        {
        CHuiFxRenderbuffer *entry = iBufferCache[i];
        if(entry)
            {
            ReleaseNativeRenderbuffer(entry);
            iBufferCache[i] = 0;
            freedBuffers++;
            }
        }
    // there should be as many freed buffers as there were free buffers.
    ASSERT(freedBuffers == iBuffersInCache);
#endif
    }

EXPORT_C void CHuiFxEngine::RestoreL()
    {
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::RestoreL - 0x%x "), this);
#endif
#ifdef HUIFX_RBCACHE_ENABLED
    memset(iBufferCache, '\0', sizeof(iBufferCache));
    iBuffersInCache = 0;
#endif
    iBuffersInUse = 0;
    }

void CHuiFxEngine::NotifyEffectEndObservers()
    {
    for ( TInt i = iActiveEffects.Count() - 1; i >= 0; i-- )
        {
        CHuiFxEffect* effect = iActiveEffects[i];
        if (!effect->Changed())
            {
            effect->NotifyEffectEndObserver();
            }
        }    
    }

EXPORT_C void CHuiFxEngine::AdvanceTime(TReal32 aElapsedTime)
    {
    // TODO: Can we do anything about all this polling?
    TInt i;
    TBool refreshRequired = EFalse;
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::AdvanceTime - 0x%x "), this);   
#endif
    // Go through the list in reverse order.
// If the effect has ended, and has the callback set,
// the effect will be removed, and will no longer be part of iActiveEffects set.
// We must check if the effect is still in our list, but the indices change
// if something is removed from the middle of the list
    for ( i = iActiveEffects.Count() - 1; i >= 0; i-- )
        {
        CHuiFxEffect* effect = iActiveEffects[i];
        if (effect->Changed())
            {
            refreshRequired = ETrue;
            }
        if ( i < iActiveEffects.Count() && effect == iActiveEffects[i] && !(effect->EffectFlags() & KHuiFxWaitGroupSyncronization))
            {
            // The effect is still in its place, it did not go away yet
            effect->AdvanceTime(aElapsedTime);
            }
        }
    
    if (refreshRequired)
        {
#ifdef HUIFX_TRACE    
        RDebug::Print(_L("CHuiFxEngine::AdvanceTime - manually continue refresh "));
#endif
        CHuiStatic::Env().ContinueRefresh();
        }
    }

EXPORT_C void CHuiFxEngine::AddEffectL(CHuiFxEffect* aEffect)
    {
#ifdef HUIFX_TRACE        
    RDebug::Print(_L("CHuiFxEngine::AddEffectL - 0x%x "), aEffect);
#endif // #ifdef HUIFX_TRACE    

    iActiveEffects.AppendL(aEffect);
    }

EXPORT_C void CHuiFxEngine::RemoveEffect(CHuiFxEffect* aEffect)
    {
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::RemoveEffect - 0x%x "), aEffect);
#endif    
    TInt i = iActiveEffects.Find(aEffect);
    if (i >= 0)
        {
        iActiveEffects.Remove(i);
        }
    }

EXPORT_C TReal32 CHuiFxEngine::GetReferenceValue(THuiFxReferencePoint aPoint)
    {
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::GetReferenceValue - %d "), aPoint);
#endif
    switch (aPoint)
        {
        case EReferencePointDisplayWidth:
        case EReferencePointDisplayRight:
            {
            CHuiDisplay* display = &CHuiStatic::Env().PrimaryDisplay(); 
            return display->VisibleArea().Size().iWidth; // was DefaultRenderbuffer()->Size().iWidth
            }
        case EReferencePointDisplayHeight:
        case EReferencePointDisplayBottom:
            {
            CHuiDisplay* display = &CHuiStatic::Env().PrimaryDisplay(); 
            return display->VisibleArea().Size().iHeight; // was DefaultRenderbuffer()->Size().iHeight
            }
        case EReferencePointDisplayHeightMinusVisualTop:
            {
            CHuiDisplay* display = &CHuiStatic::Env().PrimaryDisplay(); 
            TReal32 height = display->VisibleArea().Size().iHeight; // was DefaultRenderbuffer()->Size().iHeight
            return height;
            }
        default:
            break;
        }
    // display left and top return 0
    return 0.0f;
    }

EXPORT_C THuiFxEngineType CHuiFxEngine::EngineType()
    {
    return iType;
    }

EXPORT_C CHuiFxRenderbuffer* CHuiFxEngine::AcquireRenderbuffer(const TSize& aDesiredSize)
    {
    return AcquireRenderbuffer(aDesiredSize, ETrue);
    }

CHuiFxRenderbuffer* CHuiFxEngine::AcquireRenderbuffer(const TSize& aDesiredSize, TBool aAllowLarger)
    {
    CHuiFxRenderbuffer *buffer = 0;
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::AcquireRenderbuffer - w: %d h: %d "), aDesiredSize.iWidth, aDesiredSize.iHeight);
#endif    
    
#ifdef HUIFX_RBCACHE_ENABLED
    if(!IsCacheEmpty())
        {
        buffer = LookupFreeRenderbuffer(aDesiredSize, aAllowLarger);
        }

    if(buffer == 0)     // no free or no suitable buffer available --- create new
        {
#endif        
        buffer = AcquireNativeRenderbuffer(aDesiredSize);
#ifdef HUIFX_RBCACHE_ENABLED
        if(!IsCacheEmpty())
            {
#ifdef HUIFX_TRACE                
            RDebug::Print(_L("CHuiFxEngine::AcquireRenderbuffer() --- no suitable buffer found from cache even if it is populated. Needed buffer size: %dx%d"), aDesiredSize.iWidth, aDesiredSize.iHeight);
#endif
            }
        }
    else
        {
        buffer->SetPosition(TPoint(0,0));
        buffer->EnableBackground(EFalse);
        buffer->PrepareForReuse(aDesiredSize);   // clean up the buffer
        }
#endif
    iBuffersInUse++;
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::AcquireRenderbuffer - buffer 0x%x "), buffer);
#endif
    return (CHuiFxRenderbuffer *)buffer;
    }

// try to find a reusable buffer from iFreeBufferList
#ifdef HUIFX_RBCACHE_ENABLED

CHuiFxRenderbuffer* CHuiFxEngine::LookupFreeRenderbuffer(const TSize& aDesiredSize, TBool aAllowLarger)
    {
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::LookupFreeRenderbuffer - w: %d h: %d "), aDesiredSize.iWidth, aDesiredSize.iHeight);
#endif
#ifdef _DEBUG    
    const int preFreeBuffers = iBuffersInCache;
#endif
    CHuiFxRenderbuffer *buffer = 0;
    int i;

    for(i = 0; i < CACHE_SIZE; i++)
        {
        if(iBufferCache[i])
            {
            const TSize& bufferSize = iBufferCache[i]->Size();
            
            if (aAllowLarger)
                {
                if ((aDesiredSize.iHeight <= bufferSize.iHeight) && (aDesiredSize.iWidth <= bufferSize.iWidth))
                    {
                    buffer = iBufferCache[i];
                    iBufferCache[i] = 0;     // mark entry as empty
                    iBuffersInCache--;
                    break;                      // we've found a suitable buffer 
                    }                
                }
            else
                {
                if ((aDesiredSize.iHeight == bufferSize.iHeight) && (aDesiredSize.iWidth == bufferSize.iWidth))
                    {
                    buffer = iBufferCache[i];
                    iBufferCache[i] = 0;     // mark entry as empty
                    iBuffersInCache--;
                    break;                      // we've found a suitable buffer 
                    }                
                
                }
            }
        }
    
    // sanity check
    ASSERT(((buffer != 0) ? (preFreeBuffers == iBuffersInCache + 1) : (preFreeBuffers == iBuffersInCache)));
    
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::LookupFreeRenderbuffer - buffer 0x%x "), buffer);
#endif
    return buffer;
    }
#endif

EXPORT_C void CHuiFxEngine::ReleaseRenderbuffer(CHuiFxRenderbuffer* aBuffer)
    {
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::ReleaseRenderbuffer - buffer: 0x%x "), aBuffer);
#endif
    ASSERT((aBuffer) && (iBuffersInUse > 0));

#ifdef HUIFX_RBCACHE_ENABLED
    if(iLowGraphicsMemoryMode || IsCacheFull())
        {
#ifdef HUIFX_TRACE            
        RDebug::Print(_L("CHuiFxEngine::ReleaseRenderbuffer() --- Renderbuffer cache full! Suspectible for memory fragmentation! Cache size is %d entries."), CACHE_SIZE);
#endif
#endif        
        ReleaseNativeRenderbuffer(aBuffer);
        aBuffer = 0;
#ifdef HUIFX_RBCACHE_ENABLED
        }
    else
        {
        InsertFreeRenderbuffer(aBuffer);        
        }
#endif
    iBuffersInUse--;
    }

#ifdef HUIFX_RBCACHE_ENABLED
void CHuiFxEngine::InsertFreeRenderbuffer(CHuiFxRenderbuffer* aBuffer)
    {
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::InsertFreeRenderbuffer - buf: 0x%x "), aBuffer);
#endif
#ifdef _DEBUG    
	const int preiFreeBuffers = iBuffersInCache;
#endif
    
    ASSERT((aBuffer) && (!IsCacheFull()));
    
    for(int i = 0; i < CACHE_SIZE; i++)
        {
        if(iBufferCache[i] == 0)
            {
            iBufferCache[i] = aBuffer;
            iBuffersInCache++;
            aBuffer = 0;
            break;
            }
        }
    
    ASSERT((aBuffer == 0) && (preiFreeBuffers + 1 == iBuffersInCache));   // we have actually inserted the aBuffer into iFreeBufferList successfully?
    }

inline TBool CHuiFxEngine::IsCacheEmpty() const 
    {
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::IsCacheEmpty - %d "), (iBuffersInCache == 0));
#endif
    if(iBuffersInCache == 0)
        {
        return ETrue;
        }
    else
        {
        return EFalse;
        }
    }

inline TBool CHuiFxEngine::IsCacheFull() const  
    {
#ifdef HUIFX_TRACE    
    RDebug::Print(_L("CHuiFxEngine::IsCacheFull - %d "), (iBuffersInCache == CACHE_SIZE));
#endif
    if(iBuffersInCache == CACHE_SIZE)
        {
        return ETrue;
        }
    else
        {
        return EFalse;
        }
    }
#endif

EXPORT_C void CHuiFxEngine::EnableLowMemoryState(TBool /*aEnable*/)
    {
    // deprecated
    }
    
EXPORT_C void  CHuiFxEngine::SetMemoryLevel(THuiMemoryLevel aLevel)
    {
    iLowGraphicsMemoryMode = aLevel;
    if(iLowGraphicsMemoryMode) // != Normal
        {
        ClearCache();
        }
    }

EXPORT_C void CHuiFxEngine::Composite(CHuiGc& /*aGc*/, CHuiFxRenderbuffer& /*aSource*/, const TPoint& /*aTargetPoint*/, TBool /*aOpaque*/, TInt /*aAlpha*/)
    {
    // Default implementation does nothing
    }

TInt CHuiFxEngine::LowMemoryState()
    {
    return iLowGraphicsMemoryMode;
    }

TBool CHuiFxEngine::HasActiveEffects() const
    {
	// Don't report active effects if in SW-rendering mode
    if(iLowGraphicsMemoryMode) // != Normal
        {
        return EFalse;
        }
    return iActiveEffects.Count() > 0;
    }

void CHuiFxEngine::ClearCache()
    {
    // Release cached render targets from effects
    for (TInt j=0; j<iActiveEffects.Count();j++)
        {
        iActiveEffects[j]->ReleaseCachedRenderTarget();
        }
    
    if(IsCacheEmpty())
        {
        return;
        }
    else
        {
        //clear cache
        for(TInt i = 0; i < CACHE_SIZE; i++)
                {
                if(iBufferCache[i] != 0)
                    {
                    ReleaseNativeRenderbuffer(iBufferCache[i]);
                    iBufferCache[i] = 0;
                    iBuffersInCache--;
                    }
                }
        }
    
    ASSERT(iBuffersInCache == 0);    
    }
	
EXPORT_C void CHuiFxEngine::BeginGroupEffect(TInt aGroup)
	{
	TInt index = iActiveEffectGroups.Find(aGroup);
	if (index == KErrNotFound)
		{
		iActiveEffectGroups.Append(aGroup);
		}
	else
		{
		// group already exists
		}
	}

EXPORT_C TInt CHuiFxEngine::ActiveGroupEffect()
	{
	if (iActiveEffectGroups.Count() > 0)
		{
		return iActiveEffectGroups[iActiveEffectGroups.Count()-1];
		}
	else
		{
		return KErrNotFound;
		}
	}

EXPORT_C void CHuiFxEngine::StartGroupEffect(TInt aGroup)
	{
	TInt index = iActiveEffectGroups.Find(aGroup);
	if (index != KErrNotFound)
		{
		iActiveEffectGroups.Remove(index);
	
		for ( TInt i = iActiveEffects.Count() - 1; i >= 0; i-- )
			{
			CHuiFxEffect* effect = iActiveEffects[i];
			TInt flags = effect->EffectFlags();
			if ((flags & KHuiFxWaitGroupSyncronization) && (effect->GroupId() == aGroup))
				{
				flags &= ~KHuiFxWaitGroupSyncronization;
				effect->SetEffectFlags(flags);
				}
			}
		}
	}
