#include "StdAfx.h"
#include "AnimatedGif.h"
#include <process.h>

using namespace Gdiplus;

#pragma comment (lib, "gdiplus.lib")

CAnimatedGif::CAnimatedGif(_In_ LPSTREAM	pStream, 
                           _In_ BOOL useEmbeddedColorManagement) : Image(pStream, useEmbeddedColorManagement)
{
	Initialize();
	TestForAnimatedGIF();
}

/*!
    Threaded function responsible for gif animation

    \param [in] pParam is pointer to CAnimatedGif object
*/
UINT WINAPI CAnimatedGif::ThreadAnimationProc(_In_ LPVOID pParam)
{
	CAnimatedGif *pGif = reinterpret_cast<CAnimatedGif *> (pParam);
	pGif->ThreadAnimation();
    return 0;
}

/*!
    Initialize the gif animation thread

    \param hWnd is the owner window 
    \param pt is the row column position where the gif animation will take place on owner window

    \return TRUE if initialized else FALSE
*/
BOOL CAnimatedGif::InitAnimation(_In_ HWND hWnd, _In_ POINT pt)
{
    m_hWnd = hWnd;
    m_pt = pt;

	if(!IsAnimatedGIF())
	{
        return FALSE;
    }
    if(m_hThread != NULL)
    {
        return TRUE;
    }
    
    unsigned int nTID = 0;
    m_hThread = reinterpret_cast<HANDLE>(::_beginthreadex( NULL, 0, ThreadAnimationProc, this, CREATE_SUSPENDED, &nTID));
	if(!m_hThread)
	{
	    return TRUE;
    }
    if(::ResumeThread(m_hThread) != -1)
	{
		//TEXT("Failed resuming thread due to windows error:") + ::GetLastError());
	}
    return TRUE;
}
/*!
    Destroy the internal instances
*/
void CAnimatedGif::Destroy(void)
{
	if(!m_hThread)
	{
        return;
    }

    SetPause(FALSE);
    ::SetEvent(m_hExitEvent);
    ::WaitForSingleObject(m_hThread, INFINITE);

    ::CloseHandle(m_hExitEvent);
    ::CloseHandle(m_hPause);
    ::CloseHandle(m_hThread);

    ::free(m_pPropertyItem);
	m_pPropertyItem = NULL;

    m_hThread = NULL;
	m_hExitEvent = NULL;
	m_hPause = NULL;
}

/*!
    Pause or resume the animation

    \param [in] bPause is set to TRUE to pause or FALSE to resume
*/
void CAnimatedGif::SetPause(_In_ BOOL bPause)
{
	if(!IsAnimatedGIF())
    {
		return;
    }

	if(bPause && !m_bPause)
	{
		::ResetEvent(m_hPause);
	}
	else
	{
        if(m_bPause && !bPause)
		{
			::SetEvent(m_hPause);
		}
	}

	m_bPause = bPause;
}

/*!
    Threaded function responsible for gif animation
*/
void CAnimatedGif::ThreadAnimation(void)
{
	m_nFramePosition = 0;

	BOOL bExit = FALSE;
	while(bExit == FALSE)
	{
		bExit = DrawFrame();
	}
}

/*!
    Draw the appropriate frame from Gif

    \return TRUE exit event is set else FALSE
*/
BOOL CAnimatedGif::DrawFrame(void)
{
	::WaitForSingleObject(m_hPause, INFINITE);

	GUID pageGuid = FrameDimensionTime;

	long hmWidth = GetWidth();
	long hmHeight = GetHeight();

    HDC hDC = GetDC(m_hWnd);
	if(!!hDC)
	{
		Graphics graphics(hDC);
		graphics.DrawImage(this, m_pt.x, m_pt.y, hmWidth, hmHeight);
		ReleaseDC(m_hWnd, hDC);
	}

    //
    // Validating a window will make sure that the complete window is 
    // not repainted. There is no need to repaint the whole window as 
    // we are ourselves redrawing the area we are interesting in
    // 
	CRect animationRect(m_pt.x, m_pt.y, m_pt.x + hmWidth, m_pt.y + hmHeight);
    ::ValidateRect(m_hWnd, &animationRect);

    //
    // Switch to the next frame before next DrawFrame call
    // 
    SelectActiveFrame(&pageGuid, m_nFramePosition++);
	if(m_nFramePosition == m_nFrameCount)
    {
		m_nFramePosition = 0;
    }

	long lPause = (reinterpret_cast<long *>(m_pPropertyItem->value))[m_nFramePosition] * 10;
    DWORD dwErr = ::WaitForSingleObject(m_hExitEvent, lPause);

    return (dwErr == WAIT_OBJECT_0) ? TRUE : FALSE;
}


/*!
    Perform some test on animated gif 

    \return TRUE if it is an animated gif else FALSE 
*/
BOOL CAnimatedGif::TestForAnimatedGIF(void)
{
    UINT count = 0;
    count = GetFrameDimensionsCount();
    GUID * pDimensionIDs = new GUID[count];

    //
    // Get the list of frame dimensions from Image
    //
    GetFrameDimensionsList(pDimensionIDs, count);
    m_nFrameCount = GetFrameCount(&pDimensionIDs[0]);
    delete pDimensionIDs;

    //
    // Retrive the frame delay property which will be 
    // used as a delay before showing each gif frame
    // 
    int nSize = GetPropertyItemSize(PropertyTagFrameDelay);
    m_pPropertyItem = (PropertyItem*) malloc(nSize);
    GetPropertyItem(PropertyTagFrameDelay, nSize, m_pPropertyItem);
    return (m_nFrameCount > 1) ? TRUE : FALSE;
}


/*!
    Initialization function
*/
void CAnimatedGif::Initialize(void)
{
	lastResult = InvalidParameter;
	m_nFramePosition = 0;
	m_nFrameCount = 0;
	m_hThread = NULL;
	m_pPropertyItem = NULL;
	m_hInst = (HINSTANCE) GetModuleHandle (NULL);
	m_bPause = FALSE;

    //
    // The exit event is used in DrawFrame function to understand that it should 
    // now stop drawing as it is exiting. Pause event is used to pause or unpause 
    // the DrawFrame from processing it's drawing activity
    // 
    m_hExitEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
    m_hPause = ::CreateEvent(NULL, TRUE, TRUE, NULL);
}

CAnimatedGif::~CAnimatedGif(void)
{
	Destroy();
}
