//////////////////////////////////////////////////////////////////////////////
//
// Asura_ServerEntity_ClockTrigger.cpp
//
// Copyright (c) 2004 Rebellion. All rights reserved.
//
// Created by Nick Davis.
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
//
// includes
//
//////////////////////////////////////////////////////////////////////////////

// core
#include "Core/Asura.h"

// header
#include "Entity/Triggers/Asura_ServerEntity_ClockTrigger.h"

// asura
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_Time_System.h"
#include "Core/Asura_Timers.h"
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Logfile.h"
#include "Entity/Asura_Entity_Classification.h"
#include "Entity/Asura_StaticMessage_System.h"
#include "Entity/PhysicalObject/Asura_ServerEntity_PhysicalObject.h"
#include "Entity/Triggers/Asura_ServerEntity_Trigger.h"


// tools
#ifdef ASURA_TOOLS
#include "EntityTemplate/Tools_EntityTemplate_System.h"
#endif

//////////////////////////////////////////////////////////////////////////////
//
// Asura_ServerEntity_ClockTrigger
//
//////////////////////////////////////////////////////////////////////////////

float Asura_ServerEntity_ClockTrigger::s_fUpdatePeriod = 1.0f;

//////////////////////////////////////////////////////////////////////////////
//
//
//
//////////////////////////////////////////////////////////////////////////////

Asura_ServerEntity_ClockTrigger::Asura_ServerEntity_ClockTrigger( const bool bInitiallyActive /*=true*/ )
: Asura_ServerEntity_Trigger( bInitiallyActive ),
  m_fUpdateTimer( 0.0f ),
  m_xPrevTime( Asura_Time_System::GetCurrentDateAndTime() ),
  m_eHourRepeatState( UNSET ),
  m_ucNumHourRepeats( 0 ),
  m_fHourRepeatTimer( 0.0f ),
  m_iTriggerType( TRIGGER_TYPE_UNSET ),
  m_bIncludeHourCount( false ),
  m_fRepeatDelay( 0.0f ),
  m_fTimeBetweenRepeats( 0.0f ),
  m_iSpecifiedTimeHour( 0 ),
  m_iSpecifiedTimeMinute( 0 ),
  m_iSpecifiedTimeSecond( 0 ),
  m_uHourHandObjectGuid( ASURA_GUID_UNREGISTERED ),
  m_uMinuteHandObjectGuid( ASURA_GUID_UNREGISTERED )
{
    Classification = AsuraEntityClass_ClockTrigger;

    for ( int i = 0; i < NUM_TRIGGER_TYPES; ++i )
    {
        m_ausTriggerMessageBlocks[ i ] = ASURA_STATICMESSAGE_EMPTYBLOCK;
    }

    SetNeverUpdateClient();
}

Asura_ServerEntity_ClockTrigger::Asura_ServerEntity_ClockTrigger( Asura_Guid uGuid )
: Asura_ServerEntity_Trigger( uGuid ),
m_fUpdateTimer( 0.0f ),
m_xPrevTime( Asura_Time_System::GetCurrentDateAndTime() ),
m_eHourRepeatState( UNSET ),
m_ucNumHourRepeats( 0 ),
m_fHourRepeatTimer( 0.0f ),
m_iTriggerType( TRIGGER_TYPE_UNSET ),
m_bIncludeHourCount( false ),
m_fRepeatDelay( 0.0f ),
m_fTimeBetweenRepeats( 0.0f ),
m_iSpecifiedTimeHour( 0 ),
m_iSpecifiedTimeMinute( 0 ),
m_iSpecifiedTimeSecond( 0 ),
m_uHourHandObjectGuid( ASURA_GUID_UNREGISTERED ),
m_uMinuteHandObjectGuid( ASURA_GUID_UNREGISTERED )
{
	Classification = AsuraEntityClass_ClockTrigger;

	for ( int i = 0; i < NUM_TRIGGER_TYPES; ++i )
	{
		m_ausTriggerMessageBlocks[ i ] = ASURA_STATICMESSAGE_EMPTYBLOCK;
	}

	SetNeverUpdateClient();
}


Asura_ServerEntity_ClockTrigger::Asura_ServerEntity_ClockTrigger( Asura_Guid uGuid, const char*& pcChunkBuffer )
: Asura_ServerEntity_Trigger( uGuid, pcChunkBuffer ),
  m_fUpdateTimer( 0.0f ),
  m_xPrevTime( Asura_Time_System::GetCurrentDateAndTime() ),
  m_eHourRepeatState( UNSET ),
  m_ucNumHourRepeats( 0 ),
  m_fHourRepeatTimer( 0.0f ),
  m_iTriggerType( TRIGGER_TYPE_UNSET ),
  m_bIncludeHourCount( false ),
  m_fRepeatDelay( 0.0f ),
  m_fTimeBetweenRepeats( 0.0f ),
  m_iSpecifiedTimeHour( 0 ),
  m_iSpecifiedTimeMinute( 0 ),
  m_iSpecifiedTimeSecond( 0 ),
  m_uHourHandObjectGuid( ASURA_GUID_UNREGISTERED ),
  m_uMinuteHandObjectGuid( ASURA_GUID_UNREGISTERED )
{
    Classification = AsuraEntityClass_ClockTrigger;

    int iVersion;
    ASURA_CHUNK_EXTRACT( pcChunkBuffer, iVersion, int );

    if ( iVersion == ChunkData::CURRENT_VERSION )
    {
        ChunkData xChunkData;
        ASURA_CHUNK_EXTRACT( pcChunkBuffer, xChunkData, ChunkData );

        m_iTriggerType          = static_cast< TriggerType >( xChunkData.m_iTriggerType );
        m_bIncludeHourCount     = xChunkData.m_bIncludeHourCount;
        m_fRepeatDelay          = xChunkData.m_fRepeatDelay;
        m_fTimeBetweenRepeats   = xChunkData.m_fTimeBetweenRepeats;
        m_iSpecifiedTimeHour    = xChunkData.m_iSpecifiedTimeHour;
        m_iSpecifiedTimeMinute  = xChunkData.m_iSpecifiedTimeMinute;
        m_iSpecifiedTimeSecond  = xChunkData.m_iSpecifiedTimeSecond;
        m_uHourHandObjectGuid   = xChunkData.m_uHourHandObjectGuid;
        m_uMinuteHandObjectGuid = xChunkData.m_uMinuteHandObjectGuid;

        for ( int i = 0; i < NUM_TRIGGER_TYPES; ++i )
        {
            ASURA_CHUNK_EXTRACT( pcChunkBuffer, m_ausTriggerMessageBlocks[ i ], unsigned short );
        }
    }
    else if ( iVersion == 0 )
    {
        ChunkData_V0 xChunkData;
        ASURA_CHUNK_EXTRACT( pcChunkBuffer, xChunkData, ChunkData_V0 );

        m_iTriggerType          = static_cast< TriggerType >( xChunkData.m_iTriggerType );
        m_bIncludeHourCount     = xChunkData.m_bIncludeHourCount;
        m_fRepeatDelay          = xChunkData.m_fRepeatDelay;
        m_fTimeBetweenRepeats   = xChunkData.m_fTimeBetweenRepeats;
        m_iSpecifiedTimeHour    = xChunkData.m_iSpecifiedTimeHour;
        m_iSpecifiedTimeMinute  = xChunkData.m_iSpecifiedTimeMinute;
        m_iSpecifiedTimeSecond  = xChunkData.m_iSpecifiedTimeSecond;
        m_uHourHandObjectGuid   = ASURA_GUID_UNREGISTERED;
        m_uMinuteHandObjectGuid = ASURA_GUID_UNREGISTERED;

        for ( int i = 0; i < NUM_TRIGGER_TYPES; ++i )
        {
            ASURA_CHUNK_EXTRACT( pcChunkBuffer, m_ausTriggerMessageBlocks[ i ], unsigned short );
        }
    }
    else
    {
        Asura_Assert( false, "Asura_ServerEntity_ClockTrigger - unhandled chunk version" );
    }

    SetNeverUpdateClient();

    // make sure the hands are correct
    const Asura_Time& xCurrentTime = Asura_Time_System::GetCurrentDateAndTime();
    UpdateHourHandOrientation( xCurrentTime.m_ucHour );
    UpdateMinuteHandOrientation( xCurrentTime.m_ucMinute );
}

void Asura_ServerEntity_ClockTrigger::CreateFromChunkBuffer(Asura_Guid uGuid, const char*& pcChunkBuffer)
{
#ifdef TEST_ENTITY_CHUNK_STREAM
	TestStream<Asura_ServerEntity_ClockTrigger>(uGuid, pcChunkBuffer);
#endif
	new Asura_ServerEntity_ClockTrigger(uGuid, pcChunkBuffer);
}

void Asura_ServerEntity_ClockTrigger::Update()
{
    // note that we don't call the parent update function here as we have many different triggers
    if ( IsDestroyed() )
    {
        return;
    }

    m_fUpdateTimer -= Asura_Timers::GetGameTimeSlice();
    if ( m_fUpdateTimer <= 0.0f )
    {
        m_fUpdateTimer += s_fUpdatePeriod;

        const Asura_Time& xCurrentTime = Asura_Time_System::GetCurrentDateAndTime();

        if ( m_iTriggerType & TRIGGER_ON_THE_QUARTER_HOUR )
        {
            if ( xCurrentTime.m_ucMinute == 15 && m_xPrevTime.m_ucMinute == 14 )
            {
                SendMessagesInMessageBlock( QUARTER_HOUR_MESSAGES );
            }
        }
        if ( m_iTriggerType & TRIGGER_ON_THE_HALF_HOUR )
        {
            if ( xCurrentTime.m_ucMinute == 30 && m_xPrevTime.m_ucMinute == 29 )
            {
                SendMessagesInMessageBlock( HALF_HOUR_MESSAGES );
            }
        }
        if ( m_iTriggerType & TRIGGER_ON_THE_THREE_QUARTER_HOUR )
        {
            if ( xCurrentTime.m_ucMinute == 45 && m_xPrevTime.m_ucMinute == 44 )
            {
                SendMessagesInMessageBlock( THREE_QUARTER_HOUR_MESSAGES );
            }
        }
        if ( m_iTriggerType & TRIGGER_ON_THE_HOUR )
        {
            if ( xCurrentTime.m_ucHour != m_xPrevTime.m_ucHour )
            {
                SendMessagesInMessageBlock( HOUR_MESSAGES );

                if ( m_bIncludeHourCount )
                {
                    m_eHourRepeatState = INITIAL_PAUSE;
                    m_ucNumHourRepeats = ( xCurrentTime.m_ucHour % 12 );
                    if ( m_ucNumHourRepeats == 0 )
                    {
                        m_ucNumHourRepeats = 12;
                    }
                    m_fHourRepeatTimer = m_fRepeatDelay;
                }
            }
        }
        if ( m_iTriggerType & TRIGGER_AT_SPECIFIED_TIME )
        {
            const bool bPrevTimeNotEqual = ( m_xPrevTime.m_ucHour   != m_iSpecifiedTimeHour   ||
                                             m_xPrevTime.m_ucMinute != m_iSpecifiedTimeMinute ||
                                             m_xPrevTime.m_ucSecond != m_iSpecifiedTimeSecond );
            const bool bCurrentTimeEqual = ( xCurrentTime.m_ucHour   == m_iSpecifiedTimeHour &&
                                             xCurrentTime.m_ucMinute == m_iSpecifiedTimeMinute &&
                                             xCurrentTime.m_ucSecond == m_iSpecifiedTimeSecond );

            if ( bPrevTimeNotEqual && bCurrentTimeEqual )
            {
                SendMessagesInMessageBlock( SPECIFIED_TIME_MESSAGES );
            }
        }

        if ( xCurrentTime.m_ucHour != m_xPrevTime.m_ucHour )
        {
            UpdateHourHandOrientation( xCurrentTime.m_ucHour );
        }
        if ( xCurrentTime.m_ucMinute != m_xPrevTime.m_ucMinute )
        {
            UpdateMinuteHandOrientation( xCurrentTime.m_ucMinute );
        }

        m_xPrevTime = xCurrentTime;
    }

    UpdateHourRepeat();
}


#ifdef ASURA_TOOLS
void Asura_ServerEntity_ClockTrigger::GetCustomEventName(int iEventIndex, Asura_String& xEventName)
{
	switch(iEventIndex)
	{
		case QUARTER_HOUR_MESSAGES:			xEventName = "Trigger on the quarter hour";		return;
        case HALF_HOUR_MESSAGES:			xEventName = "Trigger on the half hour";		return;
        case THREE_QUARTER_HOUR_MESSAGES:	xEventName = "Trigger on the 3/4 hour";			return;
        case HOUR_MESSAGES:					xEventName = "Trigger on the hour";				return;
        case HOUR_COUNT_MESSAGES:			xEventName = "Trigger on the hour";				return;
        case SPECIFIED_TIME_MESSAGES:		xEventName = "Trigger at this time";			return;
	}

	Asura_Assert(FALSE, "Invalid clocktrigger custom event index");

	xEventName = "";
}
#endif // ASURA_TOOLS

#ifdef ASURA_TOOLS
void Asura_ServerEntity_ClockTrigger::ApplyChangesForTemplateInstance( Tools_EntityTemplateInstance* pxInstance )
{
    Asura_ServerEntity_Trigger::ApplyChangesForTemplateInstance( pxInstance );

    // update the message blocks
    for ( int i = 0; i < NUM_TRIGGER_TYPES; ++i )
    {
        m_ausTriggerMessageBlocks[ i ] = pxInstance->ConvertMessageBlock( m_ausTriggerMessageBlocks[ i ] );
    }
}
#endif


//////////////////////////////////////////////////////////////////////////////
//
//
//
//////////////////////////////////////////////////////////////////////////////

int Asura_ServerEntity_ClockTrigger::GetChunkBufferSize()
{
    return Asura_ServerEntity_Trigger::GetChunkBufferSize() +
           sizeof( int ) +                                      // version number
           sizeof( ChunkData ) +                                // chunk data
           ( sizeof( unsigned short ) * NUM_TRIGGER_TYPES );    // message blocks
}

void Asura_ServerEntity_ClockTrigger::FillInChunkBuffer( char*& pcChunkBuffer )
{
    Asura_ServerEntity_Trigger::FillInChunkBuffer( pcChunkBuffer );

    // version number
    ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, ChunkData::CURRENT_VERSION, int );

    // fill and verify fun with the chunk data
    ChunkData xChunkData;
	Asura_Mem::Set(&xChunkData, 0, sizeof(ChunkData));

    xChunkData.m_iTriggerType = m_iTriggerType;
    ASURA_CHUNK_VERIFY( xChunkData.m_iTriggerType, int );
    xChunkData.m_bIncludeHourCount = m_bIncludeHourCount;
    ASURA_CHUNK_VERIFY( xChunkData.m_bIncludeHourCount, bool );
    xChunkData.m_fRepeatDelay = m_fRepeatDelay;
    ASURA_CHUNK_VERIFY( xChunkData.m_fRepeatDelay, float );
    xChunkData.m_fTimeBetweenRepeats = m_fTimeBetweenRepeats;
    ASURA_CHUNK_VERIFY( xChunkData.m_fTimeBetweenRepeats, float );
    xChunkData.m_iSpecifiedTimeHour = m_iSpecifiedTimeHour;
    ASURA_CHUNK_VERIFY( xChunkData.m_iSpecifiedTimeHour, int );
    xChunkData.m_iSpecifiedTimeMinute = m_iSpecifiedTimeMinute;
    ASURA_CHUNK_VERIFY( xChunkData.m_iSpecifiedTimeMinute, int );
    xChunkData.m_iSpecifiedTimeSecond = m_iSpecifiedTimeSecond;
    ASURA_CHUNK_VERIFY( xChunkData.m_iSpecifiedTimeSecond, int );
    xChunkData.m_uHourHandObjectGuid = m_uHourHandObjectGuid;
    ASURA_CHUNK_VERIFY( xChunkData.m_uHourHandObjectGuid, Asura_Guid );
    xChunkData.m_uMinuteHandObjectGuid = m_uMinuteHandObjectGuid;
    ASURA_CHUNK_VERIFY( xChunkData.m_uMinuteHandObjectGuid, Asura_Guid );
    ASURA_CHUNK_FILL( pcChunkBuffer, xChunkData, ChunkData );

    // message blocks
    for ( int i = 0; i < NUM_TRIGGER_TYPES; ++i )
    {
        ASURA_CHUNK_FILLANDVERIFY( pcChunkBuffer, m_ausTriggerMessageBlocks[ i ], unsigned short );
    }
}

//////////////////////////////////////////////////////////////////////////////
//
//
//
//////////////////////////////////////////////////////////////////////////////

void Asura_ServerEntity_ClockTrigger::UpdateHourRepeat()
{
    switch ( m_eHourRepeatState )
    {
        case INITIAL_PAUSE:
        {
            m_fHourRepeatTimer -= Asura_Timers::GetGameTimeSlice();
            if ( m_fHourRepeatTimer <= 0.0f )
            {
                // send out the hour repeat messages
                SendMessagesInMessageBlock( HOUR_COUNT_MESSAGES );

                if ( --m_ucNumHourRepeats > 0 )
                {
                    m_eHourRepeatState  = MID_DONG_PAUSE;
                    m_fHourRepeatTimer += m_fRepeatDelay;
                }
                else
                {
                    m_eHourRepeatState = UNSET;
                }
            }
        } break;

        case MID_DONG_PAUSE:
        {
            m_fHourRepeatTimer -= Asura_Timers::GetGameTimeSlice();
            if ( m_fHourRepeatTimer <= 0.0f )
            {
                // send out the hour repeat messages
                SendMessagesInMessageBlock( HOUR_COUNT_MESSAGES );

                if ( --m_ucNumHourRepeats > 0 )
                {
                    m_fHourRepeatTimer += m_fRepeatDelay;
                }
                else
                {
                    m_eHourRepeatState = UNSET;
                }
            }
        } break;

        case UNSET:
        default:
        {
        } break;
    }
}

void Asura_ServerEntity_ClockTrigger::SendMessagesInMessageBlock( const int iMessageBlockIndex )
{
    if ( m_ausTriggerMessageBlocks[ iMessageBlockIndex ] != ASURA_STATICMESSAGE_EMPTYBLOCK )
    {
        Asura_StaticMessage_System::SendMessages( m_ausTriggerMessageBlocks[ iMessageBlockIndex ], Guid );
    }
}

void Asura_ServerEntity_ClockTrigger::UpdateHourHandOrientation( const unsigned char ucHour )
{
    if ( m_uHourHandObjectGuid == ASURA_GUID_UNREGISTERED )
    {
        return;
    }

    Asura_ServerEntity_PhysicalObject* pxHourHand = Asura_ServerEntity_PhysicalObject::FindEntity( m_uHourHandObjectGuid );
    if ( pxHourHand != NULL )
    {
        const unsigned char ucTwelveHour = ( ucHour % 12 );

        // adjust the z orientation of the clock hand
        const float fZOrient = Asura_Maths::Pi * 0.16666666666666666666f * ucTwelveHour;     // ( 2*Pi / 12 ) * hour
        Asura_Euler_3 xEuler;
        xEuler.FromMatrix( pxHourHand->GetOrientation() );
        xEuler.z = fZOrient;
        Asura_Matrix_3x3 xNewOrient;
        xEuler.ToMatrix( xNewOrient );
        pxHourHand->SetOrientation( xNewOrient );
        pxHourHand->RequestClientUpdate();
    }
}

void Asura_ServerEntity_ClockTrigger::UpdateMinuteHandOrientation( const unsigned char ucMinute )
{
    if ( m_uMinuteHandObjectGuid == ASURA_GUID_UNREGISTERED )
    {
        return;
    }

    Asura_ServerEntity_PhysicalObject* pxMinuteHand = Asura_ServerEntity_PhysicalObject::FindEntity( m_uMinuteHandObjectGuid );
    if ( pxMinuteHand != NULL )
    {
        // adjust the z orientation of the clock hand
        const float fZOrient = Asura_Maths::Pi * 0.0333333333333333f * ucMinute;     // ( 2*Pi / 60 ) * minute
        Asura_Euler_3 xEuler;
        xEuler.FromMatrix( pxMinuteHand->GetOrientation() );
        xEuler.z = fZOrient;
        Asura_Matrix_3x3 xNewOrient;
        xEuler.ToMatrix( xNewOrient );
        pxMinuteHand->SetOrientation( xNewOrient );
        pxMinuteHand->RequestClientUpdate();
    }
}

void Asura_ServerEntity_ClockTrigger::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	// Incorrect order - version number should be first

	// Parent...
	Asura_ServerEntity_Trigger::WriteToChunkStream( xStream );

	// Version...
	int iVersion = ChunkData::CURRENT_VERSION;
	xStream << iVersion;

	// Child...
	xStream << m_iTriggerType;
	xStream << m_bIncludeHourCount;
	// Padding required due to alignment of original chunk buffer.
	const u_int uZero = 0;
	xStream.Write(3,&uZero);
	xStream << m_fRepeatDelay;
	xStream << m_fTimeBetweenRepeats;
	xStream << m_iSpecifiedTimeHour;
	xStream << m_iSpecifiedTimeMinute;
	xStream << m_iSpecifiedTimeSecond;
	xStream << m_uHourHandObjectGuid;
	xStream << m_uMinuteHandObjectGuid;
	
	for ( int i = 0; i < NUM_TRIGGER_TYPES; ++i )
	{
		xStream << m_ausTriggerMessageBlocks[ i ];
	}
}

void Asura_ServerEntity_ClockTrigger::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
	// Incorrect order - version number should be first

	// Parent...
	Asura_ServerEntity_Trigger::ReadFromChunkStream( xStream );

	// Version...
	int iVersion = 0;
	xStream >> iVersion;
	Asura_Assert(iVersion <= ChunkData::CURRENT_VERSION, "FATAL ERROR: unknown version number for Asura_ServerEntity_ClockTrigger");

	// Child...
	if ( iVersion == ChunkData::CURRENT_VERSION )
	{
		xStream >> m_iTriggerType;
		xStream >> m_bIncludeHourCount;
		xStream.Skip(3);
		xStream >> m_fRepeatDelay;
		xStream >> m_fTimeBetweenRepeats;
		xStream >> m_iSpecifiedTimeHour;
		xStream >> m_iSpecifiedTimeMinute;
		xStream >> m_iSpecifiedTimeSecond;
		xStream >> m_uHourHandObjectGuid;
		xStream >> m_uMinuteHandObjectGuid;

		for ( int i = 0; i < NUM_TRIGGER_TYPES; ++i )
		{
			xStream >> m_ausTriggerMessageBlocks[ i ];
		}
	}
	else if( iVersion == 0 )
	{
		xStream >> m_iTriggerType;
		xStream >> m_bIncludeHourCount;
		xStream >> m_fRepeatDelay;
		xStream >> m_fTimeBetweenRepeats;
		xStream >> m_iSpecifiedTimeHour;
		xStream >> m_iSpecifiedTimeMinute;
		xStream >> m_iSpecifiedTimeSecond;
		m_uHourHandObjectGuid	= ASURA_GUID_UNREGISTERED;
		m_uMinuteHandObjectGuid = ASURA_GUID_UNREGISTERED;

		for ( int i = 0; i < NUM_TRIGGER_TYPES; ++i )
		{
			xStream >> m_ausTriggerMessageBlocks[ i ];
		}
	}
}

//////////////////////////////////////////////////////////////////////////////
//
//
//
//////////////////////////////////////////////////////////////////////////////
