///////////////////////////////////////////////////////////////////////////////////////////
// BackgroundSound.cpp
//
//  Implementation of the BackgroundSound class, which extends Sound to handle DirectShow-
// compatible files, changes the DirectMusic playback mode to 'primary' (necessary to play
// General MIDI files correctly) and adds the capability to detect when the sound has
// finished playing.
//
//  BackgroundSound uses a worker thread to wait on its Win32 event object. This event
// object is signalled when a DirectShow or DirectMusic event occurs. When the event
// corresponding to the end of a sound occurs, the thread calls the delegate.
//
//  DirectMusic doesn't allow us to assign an event handle to a particular segment, so it
// is assigned to the performance when playback begins. This should be acceptable, as only
// one primary segment can play at once.
//
//  When a BackgroundSound is finished with, the application *must* call Shutdown to kill
// the worker thread, or a reference to it will still exist. Therefore, BackgroundSound
// hooks the form's Closed event (since a BackgroundSound depends on a DirectMusic, which
// requires a System.Windows.Forms.Form).

#include "StdAfx.h"
#include "BackgroundSound.h"
#include "DirectXException.h"

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

using namespace System::Runtime::InteropServices;
using namespace System::Windows::Forms;

namespace Sunlight
{
    namespace DirectX
    {
        namespace SoundMusic
        {
            BackgroundSound::BackgroundSound() :
                m_pGraphBuilder(NULL),
                m_pMediaControl(NULL),
                m_pMediaSeeking(NULL),
                m_pMediaEventEx(NULL),
                m_heventRepeat(NULL),
                m_bDirectShow(false),
                m_bPlaying(false)
            {
                // Create a thread to handle the repeat events.
                m_pRepeatThread = new Threading::Thread(new Threading::ThreadStart(this, OnEndThread));
                m_pRepeatThread->Start();
            }

            BackgroundSound::~BackgroundSound()
            {
                // Kill off the thread
                if (m_pRepeatThread != NULL)
                {
                    m_pRepeatThread->Abort();
                    m_pRepeatThread = NULL;
                }
            }

            // Load this sound object into memory.
            void BackgroundSound::Load()
            {
                HRESULT h;

                try
                {
                    Sound::Load();

                    GUID guid = GUID_NOTIFICATION_SEGMENT;
                    m_pSegment->AddNotificationType(guid);

                    m_heventRepeat = CreateEvent(NULL, FALSE, FALSE, NULL);
                    m_bDirectShow = false;
                }
                catch (Exception *)
                {
                    // DirectMusic didn't like it, so we get a go

                    // Create an IGraphBuilder object, through which 
                    //  we will create a DirectShow graph.
                    IGraphBuilder __nogc *pBuilder;
                    h = CoCreateInstance(CLSID_FilterGraph, NULL,
                                                    CLSCTX_INPROC, IID_IGraphBuilder,
                                                    (void **)&pBuilder);
                    if (FAILED(h))
                        Marshal::ThrowExceptionForHR(h);

                    m_pGraphBuilder = pBuilder;

                    // Get the IMediaControl Interface
                    IMediaControl __nogc *pMC;
                    h = m_pGraphBuilder->QueryInterface(IID_IMediaControl, (void **)&pMC);
                    if (FAILED(h))
                        Marshal::ThrowExceptionForHR(h);

                    m_pMediaControl = pMC;

                    // Get the IMediaSeeking Interface
                    IMediaSeeking __nogc *pMS;
                    h = m_pGraphBuilder->QueryInterface(IID_IMediaSeeking, (void **)&pMS);
                    if (FAILED(h))
                        Marshal::ThrowExceptionForHR(h);

                    m_pMediaSeeking = pMS;

                    // Get the IMediaEventEx Interface
                    IMediaEventEx __nogc *pME;
                    h = m_pGraphBuilder->QueryInterface(IID_IMediaEventEx, (void **)&pME);
                    if (FAILED(h))
                        Marshal::ThrowExceptionForHR(h);

                    m_pMediaEventEx = pME;

                    HANDLE hEvent;
                    h = m_pMediaEventEx->GetEventHandle((OAEVENT *)&hEvent);
                    if (FAILED(h))
                        Marshal::ThrowExceptionForHR(h);

                    m_heventRepeat = hEvent;

                    // Add the file to the graph.
                    LPWSTR wszFilename = (LPWSTR)(void *)Marshal::StringToCoTaskMemUni(m_pFilename);
                    h = m_pGraphBuilder->RenderFile(wszFilename, NULL);
                    CoTaskMemFree(wszFilename);

                    if ((h == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) || (h == VFW_E_NOT_FOUND))
                        throw new IO::FileNotFoundException(String::Format(S"Unable to load sound file {0}", m_pFilename), m_pFilename);
                    else if (FAILED(h))
                        Marshal::ThrowExceptionForHR(h);

                    m_bDirectShow = true;
                }

                // Attach to the parent window's Closed event, to shut down the thread.
                __hook(&Form::Closed, DirectMusicObject->ParentWindow, &BackgroundSound::OnFormClosed, this);
                __hook(&Form::Activated, DirectMusicObject->ParentWindow, &BackgroundSound::OnFormActivated, this);
                __hook(&Form::Deactivate, DirectMusicObject->ParentWindow, &BackgroundSound::OnFormDeactivated, this);
            }

            // Unload this object from memory.
            void BackgroundSound::Unload()
            {
                // Unhook all the events.
                __unhook(&Form::Closed, DirectMusicObject->ParentWindow, &BackgroundSound::OnFormClosed, this);
                __unhook(&Form::Activated, DirectMusicObject->ParentWindow, &BackgroundSound::OnFormActivated, this);
                __unhook(&Form::Deactivate, DirectMusicObject->ParentWindow, &BackgroundSound::OnFormDeactivated, this);

                if (m_bDirectShow)
                {
                    if (m_pMediaEventEx != NULL)
                    {
                        m_pMediaEventEx->Release();
                        m_pMediaEventEx = NULL;
                    }
                    if (m_pMediaSeeking != NULL)
                    {
                        m_pMediaSeeking->Release();
                        m_pMediaSeeking = NULL;
                    }
                    if (m_pMediaControl != NULL)
                    {
                        m_pMediaControl->Release();
                        m_pMediaControl = NULL;
                    }
                    if (m_pGraphBuilder != NULL)
                    {
                        m_pGraphBuilder->Release();
                        m_pGraphBuilder = NULL;
                    }
                }
                else
                {
                    Sound::Unload();
                    CloseHandle(m_heventRepeat);
                }
                m_heventRepeat = NULL;
            }

            // Called when the ParentWindow is activated.
            void BackgroundSound::OnFormActivated(Object *sender, EventArgs *e)
            {
                if (m_bPlaying)
                    Play();
            }

            // Called when the ParentWindow is deactivated.
            void BackgroundSound::OnFormDeactivated(Object *sender, EventArgs *e)
            {
                if (m_bPlaying)
                {
                    Pause();
                    m_bPlaying = true;      // keep this flag true, so we can reactivate
                }
            }

            // Play this object from the beginning.
            void BackgroundSound::Play()
            {
                Prepare();

                if (m_bDirectShow)
                {
                    // Start the graph
                    m_pMediaControl->Run();
                }
                else
                {
                    // Must reset notification handle every time, since user may have played another BackgroundMusic
                    DirectMusicObject->GetPerformance()->SetNotificationHandle(m_heventRepeat, 0);

                    DirectMusicObject->GetPerformance()->PlaySegment(m_pSegment, 0, 0, NULL);
                }

                m_bPlaying = true;
            }

            // Stop the playback of this object and return to the beginning.
            void BackgroundSound::Stop()
            {
                if (m_bDirectShow)
                {
                    m_pMediaControl->Stop();
                    
                    // Re-seek the graph to the beginning
                    LONGLONG llPos = 0;
                    m_pMediaSeeking->SetPositions(&llPos, AM_SEEKING_AbsolutePositioning,
                                                &llPos, AM_SEEKING_NoPositioning);
                }
                else
                    Sound::Stop();

                m_bPlaying = false;
            }

            // Stop the playback of this object.
            void BackgroundSound::Pause()
            {
                if (m_bDirectShow)
                    m_pMediaControl->Stop();
                else
                    Sound::Stop();              // can't pause DirectMusic objects

                m_bPlaying = false;
            }

            void BackgroundSound::OnFormClosed(Object *sender, EventArgs* e)
            {
                if (m_bPlaying)
                    Stop();

                Unload();

                if (m_pRepeatThread != NULL)
                    m_pRepeatThread->Abort();
            }

            void BackgroundSound::OnEndThread()
            {
                long    evCode, param1, param2;
                HRESULT h;

                for (;;)
                {
                    DWORD dwResult = WaitForSingleObject(m_heventRepeat, 200);
                    if (dwResult == WAIT_OBJECT_0)
                    {
                        if (Finished != NULL)
                        {
                            if (m_bDirectShow)
                            {
                                for (;;)
                                {
                                    h = m_pMediaEventEx->GetEvent(&evCode, &param1, &param2, 0);
                                    if (FAILED(h))
                                        break;

                                    // Check notification type and do something in response.
                                    if ((evCode == EC_COMPLETE) || (evCode == EC_USERABORT) || (evCode == EC_ERRORABORT))
                                    {
                                        Stop();
                                        __raise Finished(this, new EventArgs());
                                    }

                                    m_pMediaEventEx->FreeEventParams(evCode, param1, param2);
                                }
                            }
                            else
                            {
                                DMUS_NOTIFICATION_PMSG  *pPmsg;

                                while (DirectMusicObject->GetPerformance()->GetNotificationPMsg(&pPmsg) == S_OK)
                                {
                                    // Check notification type and do something in response.
                                    if (pPmsg->dwNotificationOption == DMUS_NOTIFICATION_SEGEND)
                                    {
                                        Stop();
                                        __raise Finished(this, new EventArgs());
                                    }
                                    DirectMusicObject->GetPerformance()->FreePMsg((DMUS_PMSG*)pPmsg); 
                                }
                            }
                        }
                    }
                    else if (dwResult != WAIT_TIMEOUT)
                        return;
                }
            }
        }
    }
}