/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  plTransitionMgr - Class to handle fullscreen transitions (fades, etc)   //
//                                                                          //
//  Note: eventually, I would like to drive these transitions on material   //
//  animations (it's just a big screen-covering plate with a material,      //
//  after all). This would allow the artists to specify their own           //
//  transitions and do really cool effects. However, that would require     //
//  somehow loading the materials in, and I'm not sure exactly how to do    //
//  that....                                                                //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

#include "hsWindows.h"
#include "hsTypes.h"
#include "plTransitionMgr.h"
#include "plPlates.h"

#include "plGImage/plMipmap.h"
#include "plSurface/plLayer.h"
#include "plSurface/hsGMaterial.h"
#include "plMessage/plLayRefMsg.h"
#include "pnMessage/plRefMsg.h"
#include "plMessage/plTransitionMsg.h"
#include "pnMessage/plTimeMsg.h"
#include "pnMessage/plEventCallbackMsg.h"
#include "plMessage/plLinkToAgeMsg.h"
#include "plgDispatch.h"
#include "hsGDeviceRef.h"
#include "hsResMgr.h"
#include "hsTimer.h"

#include "plAudio/plAudioSystem.h"
#include "pnNetCommon/plNetApp.h"
#include "plNetClient/plLinkEffectsMgr.h"
#include "pnNetCommon/plNetApp.h"

#include "plStatusLog/plStatusLog.h"

//// Constructor/Destructor //////////////////////////////////////////////////

plTransitionMgr::plTransitionMgr()
{
    fEffectPlate = nil;
    fCurrentEffect = kIdle;
    fPlaying = false;
}

void    plTransitionMgr::Init( void )
{
    ICreatePlate();
    plgDispatch::Dispatch()->RegisterForExactType( plTransitionMsg::Index(), GetKey() );
    plgDispatch::Dispatch()->RegisterForExactType( plLinkEffectBCMsg::Index(), GetKey() );
}

plTransitionMgr::~plTransitionMgr()
{
    int     i;


    for( i = 0; i < fCallbacks.GetCount(); i++ )
        hsRefCnt_SafeUnRef( fCallbacks[ i ] );

    if( fEffectPlate != nil )
        plPlateManager::Instance().DestroyPlate( fEffectPlate );

    if( fRegisteredForTime )
        plgDispatch::Dispatch()->UnRegisterForExactType( plTimeMsg::Index(), GetKey() );

    plgDispatch::Dispatch()->UnRegisterForExactType( plTransitionMsg::Index(), GetKey() );
    plgDispatch::Dispatch()->UnRegisterForExactType( plLinkEffectBCMsg::Index(), GetKey() );
}

//// ICreatePlate ////////////////////////////////////////////////////////////

void    plTransitionMgr::ICreatePlate( void )
{
    int     x, y;


    fEffectPlate = nil;

    // +0.01 to deal with the half-pixel antialiasing stuff
    plPlateManager::Instance().CreatePlate( &fEffectPlate, 0, 0, 2.01, 2.01 );
    fEffectPlate->SetDepth(2);

    // hack for now--create a black layer that we will animate the opacity on
    plMipmap *ourMip = fEffectPlate->CreateMaterial( 16, 16, true );
    for( y = 0; y < ourMip->GetHeight(); y++ )
    {
        UInt32  *pixels = ourMip->GetAddr32( 0, y );
        for( x = 0; x < ourMip->GetWidth(); x++ )
            pixels[ x ] = 0xff000000;
    }

    fEffectPlate->SetVisible( false );
}

//// IStartFadeOut ///////////////////////////////////////////////////////////

void    plTransitionMgr::IStartFadeOut( hsScalar lengthInSecs, UInt8 effect )
{
    fCurrentEffect = effect; // default - kFadeOut;
    fEffectLength = lengthInSecs;

    // Special case for length 0--just jump straight to fadeout
    if( lengthInSecs == 0.f )
    {
        fCurrOpacity = 1.f;
        fLastTime = -1.f;
        fPlaying = false;
        plgAudioSys::SetGlobalFadeVolume( 0.f );
    }
    else
    {
        fCurrOpacity = 0;
        fOpacDelta = 1.f / lengthInSecs;
        fLastTime = -1.f;
        fPlaying = true;

        // Register for time message
        plgDispatch::Dispatch()->RegisterForExactType( plTimeMsg::Index(), GetKey() );
        fRegisteredForTime = true;
    }

    if( fEffectPlate == nil )
        ICreatePlate();
    fEffectPlate->SetVisible( true );

    plLayer *layer = (plLayer *)fEffectPlate->GetMaterial()->GetLayer( 0 );
    if( layer != nil )
    {
        layer->SetOpacity( fCurrOpacity );
    }
}

//// IStartFadeIn ////////////////////////////////////////////////////////////

void    plTransitionMgr::IStartFadeIn( hsScalar lengthInSecs, UInt8 effect )
{
    fCurrentEffect = effect; // default - kFadeIn;
    fEffectLength = lengthInSecs;
    fCurrOpacity = 1.f;
    fOpacDelta = -1.f / lengthInSecs;
    fLastTime = -1.f;
    fPlaying = true;

    // Register for time message
    plgDispatch::Dispatch()->RegisterForExactType( plTimeMsg::Index(), GetKey() );
    fRegisteredForTime = true;

    if( fEffectPlate == nil )
        ICreatePlate();
    fEffectPlate->SetVisible( true );

    plLayer *layer = (plLayer *)fEffectPlate->GetMaterial()->GetLayer( 0 );
    if( layer != nil )
    {
        layer->SetOpacity( fCurrOpacity );
    }
}

//// IStop ///////////////////////////////////////////////////////////////////

void    plTransitionMgr::IStop( hsBool aboutToStartAgain /*= false*/ )
{
    int     i;

    plgDispatch::Dispatch()->UnRegisterForExactType( plTimeMsg::Index(), GetKey() );
    fRegisteredForTime = false;

    if( fPlaying )
    {
        if( !fHoldAtEnd && fEffectPlate != nil && !aboutToStartAgain )
            fEffectPlate->SetVisible( false );

        // finish the opacity to the end opacity
        if( fEffectPlate != nil )
        {
            plLayer *layer = (plLayer *)fEffectPlate->GetMaterial()->GetLayer( 0 );
            if( layer != nil )
            {
                layer->SetOpacity( (fCurrentEffect == kFadeIn || fCurrentEffect == kTransitionFadeIn) ? 0.f : 1.f );
            }
        }

        if( !aboutToStartAgain )
        {
            if (!fNoSoundFade)
                plgAudioSys::SetGlobalFadeVolume( (fCurrentEffect == kFadeIn || fCurrentEffect == kTransitionFadeIn) ? 1.f : 0.f );
        }

        for( i = 0; i < fCallbacks.GetCount(); i++ )
        {
            fCallbacks[ i ]->SetSender( GetKey() );
            plgDispatch::MsgSend( fCallbacks[ i ] );
        }
        fCallbacks.Reset();

        fPlaying = false;
    }
}

//// MsgReceive //////////////////////////////////////////////////////////////

hsBool  plTransitionMgr::MsgReceive( plMessage* msg )
{
    int         i;


    plTimeMsg   *time = plTimeMsg::ConvertNoRef( msg );
    if( time != nil )
    {
        if( !fPlaying )
            return false;

        if (fLastTime < 0)
        {
            // Semi-hack. We trigger transitions after we've finished loading.
            // Problem is the loading all happens in one really long frame, so that
            // if we record the time we started, we'll instantly be done next frame,
            // even though we triggered just at the "end" of the last frame.
            // 
            // So instead we don't start the clock until we get our first plTimeMsg.

            
            fLastTime = (hsScalar)(time->DSeconds());
            return false;
        }

        fEffectLength -= (hsScalar)( time->DSeconds() - fLastTime );//*/time->DelSeconds();
        if( fEffectLength < 0 )
            IStop();
        else
        {
            // Grab the layer so we can set the opacity
            fCurrOpacity += (hsScalar)(fOpacDelta * ( time->DSeconds() - fLastTime ));//*/time->DelSeconds();
            if( fEffectPlate == nil )
                ICreatePlate();

            plLayer *layer = (plLayer *)fEffectPlate->GetMaterial()->GetLayer( 0 );
            if( layer != nil )
            {
                layer->SetOpacity( fCurrOpacity );
            }

            // Let the audiosystem handle fading in sounds
            if(!fNoSoundFade)
                plgAudioSys::SetGlobalFadeVolume( 1.f - fCurrOpacity );
            

            fLastTime = (hsScalar)(time->DSeconds());
        }
        
        return false;
    }
    
    plTransitionMsg *effect = plTransitionMsg::ConvertNoRef( msg );
    if( effect != nil )
    {
        if( fRegisteredForTime )
            IStop( true );

        for( i = 0; i < effect->GetNumCallbacks(); i++ )
        {
            plEventCallbackMsg *pMsg = effect->GetEventCallback( i );
            hsRefCnt_SafeRef( pMsg );
            fCallbacks.Append( pMsg );
        }
        
        fHoldAtEnd = effect->GetHoldState();

        fNoSoundFade = false;

        switch(effect->GetEffect())
        {
        case plTransitionMsg::kFadeInNoSound:
            fNoSoundFade = true;
        case plTransitionMsg::kFadeIn:
            IStartFadeIn( effect->GetLengthInSecs(), kTransitionFadeIn );
            break;
        case plTransitionMsg::kFadeOutNoSound:
            fNoSoundFade = true;
        case plTransitionMsg::kFadeOut:
            IStartFadeOut( effect->GetLengthInSecs(), kTransitionFadeOut );
            break;
        }

        return false;
    }

    plLinkEffectBCMsg *link = plLinkEffectBCMsg::ConvertNoRef( msg );
    if( link != nil )
    {
        const float kScreenFadeTime = 3.f; // seconds

        // Go ahead and auto-trigger based on link FX messages
        if( plNetClientApp::GetInstance() != nil && link->fLinkKey == plNetClientApp::GetInstance()->GetLocalPlayerKey() )
        {
            if( fRegisteredForTime )
                IStop( true );

            if( link->HasLinkFlag( plLinkEffectBCMsg::kLeavingAge ) )
            {
                plNetApp::GetInstance()->DebugMsg("Local player linking out, fading screen\n");

                fHoldAtEnd = true;
                IStartFadeOut( kScreenFadeTime );
            }
            else
            {
                plNetApp::GetInstance()->DebugMsg("Local player linking in, fading screen\n");

                fHoldAtEnd = false;
                IStartFadeIn( kScreenFadeTime );
            }   

            if (link->HasLinkFlag(plLinkEffectBCMsg::kSendCallback))
            {
                plLinkEffectsMgr *mgr;
                if( ( mgr = plLinkEffectsMgr::ConvertNoRef( link->GetSender()->ObjectIsLoaded() ) ) != nil )
                {
                    plEventCallbackMsg *cback = plEventCallbackMsg::ConvertNoRef( mgr->WaitForEffect( link->fLinkKey ) );
//                  hsRefCnt_SafeRef( cback ); // mgr has given us ownership, his ref is now ours. No need for another. -mf-
                    fCallbacks.Append( cback );
                }
            }
        }
        return true;
    }
    return hsKeyedObject::MsgReceive( msg );
}