#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>

#include "resource.h"
#include "DSShow3D.h"
#include "GVars.h"

#include "MainWnd.h"
#include "FInfo3D.h"
#include "FileInfo.h"
#include "LsnrInfo.h"
#include "wave.h"
#include "debug.h"

static HWND hMainWndClient;

MainWnd::MainWnd()
{
    m_fCreated = FALSE;
    m_hwnd = NULL;
    m_n3DBuffers = 0;
    ZeroMemory( &m_dscaps, sizeof(DSCAPS));
    m_dscaps.dwSize = sizeof(m_dscaps);
}


MainWnd::~MainWnd()
{
    // This situation should never really occur, but we should make sure
    // that there are no coding errors by asserting that fact.
    if( m_hwnd )
    {
	ASSERT_HWND(m_hwnd);
	::DestroyWindow( m_hwnd );
	m_hwnd = NULL;
	m_fCreated = FALSE;
    }
}


BOOL MainWnd::Create()
{
    if( m_fCreated )
	return FALSE;

    ASSERT( NULL == m_hwnd );
    m_hwnd = CreateWindowEx( WS_EX_ACCEPTFILES,
			    gszAppWndClass,
			    gszAppCaption,
			    WS_OVERLAPPEDWINDOW,
			    CW_USEDEFAULT,
			    CW_USEDEFAULT,
			    200,
			    150,
			    (HWND)NULL,
			    (HMENU)NULL,
			    (HANDLE)ghInst,
			    (LPTSTR)NULL );

    if( !m_hwnd )
	return FALSE;

    SetWindowLong( m_hwnd, GWL_USERDATA, (LONG)this );
    m_fCreated = TRUE;

    EnableMenuItem(GetMenu(m_hwnd), 2, MF_BYPOSITION |
		   (m_dlInfoList.GetElementCount() ? MF_ENABLED : MF_GRAYED));
    DrawMenuBar( m_hwnd );

    UpdateStatus();

    return TRUE;
}


int MainWnd::MessageBox( LPCSTR lpcszMessage, UINT uType )
{
    return ::MessageBox( m_hwnd, lpcszMessage, gszAppName, uType );
}

int MainWnd::MessageBox( UINT uResID, UINT uType )
{
    // TODO Make the 512 some defined constant
    TCHAR	szMessage[512];
    int	    nRet;

    LoadString( ghInst, uResID, szMessage, 512 );
    nRet = ::MessageBox( m_hwnd, szMessage, gszAppName, uType );

    return nRet;
}

BOOL MainWnd::OnTimer( WPARAM wParam, LPARAM lParam )
{
    // Move to the Head of the list
    m_dlInfoList.SetAtHead();
    
    if( NULL != gpListenerInfo )
	gpListenerInfo->UpdateUI();

    // While the current element isn't NULL, call OnTimer on each FileInfo
    for( int i = 0; i < m_dlInfoList.GetElementCount(); i++ )
    {
	m_dlInfoList.GetCurrent()->UpdateUI();
	// Overloaded increment moves to next position in the list
	m_dlInfoList++;
    }
    return TRUE;
}

void MainWnd::OnDestroy()
{
    HRESULT     hr = 0;

    if( gdwTimer != 0 )
    {
	KillTimer( m_hwnd, gdwTimer );
	gdwTimer = 0;
    }

    if( NULL != gpwfxFormat )
    {
	GlobalFree( gpwfxFormat );
	gpwfxFormat = NULL;
    }

    /* Destroy the direct sound object. */
    if( gp3DListener != NULL )
    {
	DPF( 3, "Releasing 3D Listener in MainWnd::OnDestroy()" );
	gp3DListener->Release();
	gp3DListener = NULL;
    }
    if( gpdsbPrimary )
    {
	DPF( 3, "Releasing Primary in MainWnd::OnDestroy()" );
	gpdsbPrimary->Stop();
	gpdsbPrimary->Release();
	gpdsbPrimary = NULL;
    }
    if( gpds != NULL )
    {
	DPF( 3, "Releasing DSound object in MainWnd::OnDestroy()" );
	gpds->Release();
	gpds = NULL;
    }

    if( gfCOMInitialized )
	CoUninitialize();

//    WriteProfileString( gszAppName, "EnumDrivers", gfEnumDrivers ? "1" : "0" );

    if( m_hwnd )
	m_hwnd = NULL;
    return;
}


/* MainWndProc()
 *
 *    Window message handler function for the main application window.
 * For the most part, this sucker just dispatchs calls to some helper
 * functions in this module which do the actual handling.
 */
LRESULT CALLBACK MainWndProc( HWND hWnd, unsigned message, WPARAM wParam, LPARAM lParam )
{
    if( !hWnd )
	return 0L;

    // This will be a valid pointer to the class object for any message
    // after the WM_CREATE
    MainWnd *pmw = (MainWnd *)GetWindowLong( hWnd, GWL_USERDATA );
    
    switch (message)
	{
	case WM_CREATE:
	    // If we want to do anything with the class here, we'll have
	    // to modify the code to pass pmw in LPARAM since there's no
	    // other way to get it yet: the code that sets the WindowLong
	    // user data has not yet executed
	    break;

	case WM_ACTIVATE:
	    ASSERT( NULL != pmw );
	    pmw->UpdateStatus();
	    break;

	case WM_TIMER:
	    ASSERT( NULL != pmw );
	    if (!pmw->OnTimer(wParam, lParam))
		return(DefWindowProc(hWnd, message, wParam, lParam));
	    break;

	case WM_DROPFILES:
	    ASSERT( NULL != pmw );
	    if( !pmw->OnDropFiles( wParam ))
		return(DefWindowProc(hWnd, message, wParam, lParam));
	    break;

	case WM_PAINT:
	    ASSERT( NULL != pmw );
	    if( !pmw->OnPaint(wParam, lParam))
		return(DefWindowProc(hWnd, message, wParam, lParam));
	    break;

	case WM_INITMENU:
	    ASSERT( NULL != pmw );
	    if((HMENU)wParam != GetMenu( hWnd ))
		break;
	    return pmw->OnInitMenu( wParam );
	    break;

	case WM_INITMENUPOPUP:	
		// we only want to deal with the options sub-menu.
	    ASSERT( NULL != pmw );
	    if((BOOL)HIWORD(lParam) == TRUE) // it's a system menu
			break;	
		else if ((UINT)LOWORD(lParam) != 1) // it's not the options menu.
			break;
		// else handle it.
	    return pmw->OnInitMenuPopup( wParam );
	    break;

	case WM_COMMAND:
	    ASSERT( NULL != pmw );
	    if (!pmw->OnCommand(wParam, lParam))
		return DefWindowProc(hWnd, message, wParam, lParam);
	    break;

	case WM_DESTROY:
	    ASSERT( NULL != pmw );
	    pmw->OnDestroy();
	    PostQuitMessage(0);
	    break;

	default:
	    return DefWindowProc( hWnd, message, wParam, lParam );
	    break;
	}

    return 0L;
}


BOOL MainWnd::OnInitMenuPopup( WPARAM wParam )
{
    int i;
	char szFormat[255];
	WAVEFORMATEX wfx, wfxActual;
	BOOL fRet = FALSE;
	DWORD dwFormatID;
	char szTemp[25] = "*";
	HRESULT hr = DS_OK;
	HMENU hSubMenu = (HMENU)wParam;

	ZeroMemory(&wfx, sizeof(WAVEFORMATEX));
	ZeroMemory(&wfxActual, sizeof(WAVEFORMATEX));

	// Get the handle to the format submenu.
	hSubMenu = GetSubMenu(hSubMenu,0); 
	ASSERT(IsMenu(hSubMenu));

	for( i = 0; i < NUM_FORMATENTRIES; i++ )
	{
		dwFormatID = CommandIDFromFormatCode(fdFormats[i].dwCode);
		// get the string representing this format.
		fRet = FormatCodeToText(fdFormats[i].dwCode, szFormat, sizeof(szFormat));
		ASSERT(fRet);
		// set this as the format.
		FormatCodeToWFX( fdFormats[i].dwCode, &wfx);
		wfx.wFormatTag = WAVE_FORMAT_PCM;
		hr = gpdsbPrimary->SetFormat(&wfx);
		if (hr != DS_OK)
		{
			DPF( 0, "SetFormat failed (%s)", TranslateDSError(hr));
			DisableFormatCode(fdFormats[i].dwCode);
			continue;
		}
		else
		{
			EnableFormatCode(fdFormats[i].dwCode);
		}

		// now get what is the actual format.
		hr = gpdsbPrimary->GetFormat(&wfxActual, sizeof(WAVEFORMATEX), NULL);
		if (hr != DS_OK)
		{
			DPF(0, "GetFormat failed with hr = %lu", hr);
		}
		// are these formats the same.
		if (!IsSameFormat(&wfx, &wfxActual))
		{
			strcat(szFormat, szTemp);
			fRet = ModifyMenu(hSubMenu, dwFormatID, MF_BYCOMMAND | MF_STRING, dwFormatID, szFormat);
		}		

		if( !fdFormats[i].fEnable )
			EnableMenuItem((HMENU)wParam, dwFormatID, MF_BYCOMMAND | MF_GRAYED );
		else
			EnableMenuItem((HMENU)wParam, dwFormatID, MF_BYCOMMAND | MF_ENABLED );
	}// for

	CheckMenuItem( (HMENU)wParam,
				   CommandIDFromFormatCode( gdwOutputFormat ),
				   MF_BYCOMMAND | MF_CHECKED );

	// set the format back.
	FormatCodeToWFX(gdwOutputFormat, &wfx);
	hr = gpdsbPrimary->SetFormat(&wfx);
	ASSERT(hr == DS_OK);

	return TRUE;
}

BOOL MainWnd::OnInitMenu( WPARAM wParam )
{
    // If there are no buffers open, then disable the buffers menu
    EnableMenuItem((HMENU)wParam, 2, MF_BYPOSITION |
		   (m_dlInfoList.GetElementCount() ? MF_ENABLED : MF_GRAYED));
    return TRUE;
}


/* OnDropFiles()
 *
 *    Handles the WM_DROPFILES message, which is a message sent when someone
 * drags and drops files onto our application window.
 */
BOOL MainWnd::OnDropFiles( WPARAM wParam )
{
    WORD    wNumFiles = DragQueryFile((HDROP)wParam, (UINT)-1, NULL, (UINT)0 );
    WORD    nFile, nPathLength;
    LPTSTR  lpszFile;

    for( nFile = 0; nFile < wNumFiles; nFile++ )
    {
	nPathLength = DragQueryFile((HDROP)wParam, nFile, NULL, 0 ) + 1;
	if(( lpszFile = (LPTSTR)GlobalAlloc(GPTR, nPathLength * sizeof(TCHAR))) == NULL )
	    continue;
	DragQueryFile((HDROP)wParam, nFile, lpszFile, nPathLength );
	
	// Open the file
	OnFileOpen( lpszFile );
	
	GlobalFree(lpszFile);
    }

    DragFinish((HDROP)wParam);
    return TRUE;
}


/* OnCommand()
 *
 *    WM_COMMAND message handler for the main window procedure.
 */
BOOL MainWnd::OnCommand( WPARAM wParam, LPARAM lParam )
{
    HRESULT hr;
    DWORD   dwNewFormat;
    int	    i = 0;

    if(( dwNewFormat = FormatCodeFromCommandID( LOWORD(wParam))) != 0 )
	{
	CheckMenuItem( GetMenu( m_hwnd ),
				CommandIDFromFormatCode( gdwOutputFormat ),
				MF_BYCOMMAND | MF_UNCHECKED );
	FormatCodeToWFX( dwNewFormat, gpwfxFormat );
	DPF( 2, "Setting output format to code: %lu", dwNewFormat );
	hr = gpdsbPrimary->SetFormat( gpwfxFormat );
	if( SUCCEEDED( hr ))
	    gdwOutputFormat = dwNewFormat;
	else
	    {
	    DisableFormatCode( dwNewFormat );
	    EnableMenuItem( GetMenu( m_hwnd ),
			    CommandIDFromFormatCode( dwNewFormat ),
			    MF_BYCOMMAND | MF_GRAYED );
	    FormatCodeToWFX( gdwOutputFormat, gpwfxFormat );
	    gpdsbPrimary->SetFormat( gpwfxFormat );
	    }
	return TRUE;
	}

    switch( LOWORD(wParam))
    {
	case IDC_FILE_EXIT:
	    PostMessage(WM_CLOSE, 0, 0);
	    break;

	case IDC_FILE_OPEN:
	    OnFileOpen( NULL );
	    UpdateStatus();
	    break;

	case IDC_OPTIONS_SETTINGS:
	    DialogBox(ghInst, MAKEINTRESOURCE(IDD_SETTINGS),
					    m_hwnd, (DLGPROC)SettingsDlgProc);
	    UpdateStatus();
	    break;

	case IDC_BUFFERS_MINIMIZEALL:
	    m_dlInfoList.SetAtHead();
	    for( i = 0; i < m_dlInfoList.GetElementCount(); i++)
		{
		m_dlInfoList.GetCurrent()->MinimizeWindow();
		m_dlInfoList++;
		}
	    break;

	case IDC_BUFFERS_RESTOREALL:
	    m_dlInfoList.SetAtHead();
	    for( i = 0; i < m_dlInfoList.GetElementCount(); i++)
		{
		m_dlInfoList.GetCurrent()->RestoreWindow();
		m_dlInfoList++;
		}
	    break;

	case IDC_BUFFERS_CLOSEALL:
	    while( m_dlInfoList.GetElementCount())
		{
		m_dlInfoList.SetAtHead();
		m_dlInfoList.GetCurrent()->Close();
		// Element is removed from the list by processing elsewhere
		}
	    EnableMenuItem(GetMenu(m_hwnd), 2, MF_BYPOSITION |
			   (m_dlInfoList.GetElementCount() ? MF_ENABLED : MF_GRAYED));
	    DrawMenuBar( m_hwnd );
	    break;

	case IDC_BUFFERS_CASCADE:
	    m_dlInfoList.SetAtHead();
	    for( i = 0; i < m_dlInfoList.GetElementCount(); i++)
		{
		if( 0 == i )
		    m_dlInfoList.GetCurrent()->ResetCascade();
		m_dlInfoList.GetCurrent()->CascadeWindow();
		m_dlInfoList++;
		}
	    break;

	case IDC_HELP_ABOUT:
	    DialogBox(ghInst, MAKEINTRESOURCE(IDD_ABOUT),
					    m_hwnd, (DLGPROC)AboutDlgProc);
	    break;

	default:
	    return FALSE;
    }

    /* If we broke out of the switch, it means we handled the message
     * so we return TRUE to indicate this.
     */
    return TRUE;
}


/* DuplicateBuffer()
 *
 *    Does the work of duplicating a FileInfo object.
 */
void MainWnd::DuplicateBuffer( FileInfo *pfiSource )
{
    FileInfo	*pfiNew;

    if( NULL == pfiSource )
	return;

    if( m_dlInfoList.GetElementCount() >= MAXCONTROLS )
    {
	MessageBox( "No more controls allowed" );
        return;
    }

    if( pfiSource->Is3D())
	{
	m_n3DBuffers++;
	pfiNew = new FileInfo3D( this );
	}
    else
	pfiNew = new FileInfo( this );

    if( NULL == pfiNew )
	return;

    pfiNew->Duplicate( pfiSource );
    m_dlInfoList.Append( pfiNew );
}


/* OnFileOpen()
 *
 *    Handles the File|Open menu command.
 */
FileInfo *MainWnd::OnFileOpen( LPTSTR lpszForcePath )
{
    TCHAR           szFileName[MAX_PATH];
    FileInfo        *pFileInfo = NULL;
    LPTSTR	    lpszFileTitle;
    int             nFileIndex;
    DWORD	    dwFlags;

    if( m_dlInfoList.GetElementCount() >= MAXCONTROLS )
    {
	MessageBox( "No more controls allowed" );
        return NULL;
    }

    if( NULL != lpszForcePath )
    {
	dwFlags = OPENFILENAME_F_GETPOS2;
	if( grs.dwDefaultFocusFlag & DSBCAPS_STICKYFOCUS )
	    dwFlags |= OPENFILENAME_F_STICKYFOCUS;
	else if( grs.dwDefaultFocusFlag & DSBCAPS_GLOBALFOCUS )
	    dwFlags |= OPENFILENAME_F_GLOBALFOCUS;

	if( grs.fOpen3D )
	    dwFlags |= OPENFILENAME_F_3D;
	lpszFileTitle = strrchr( lpszForcePath, '\\' );
	if( NULL == lpszFileTitle )
	    nFileIndex = 0;
	else
	    nFileIndex =  lpszFileTitle - lpszForcePath + sizeof(TCHAR);
	lstrcpy( szFileName, lpszForcePath );
    }
    else
    {
	// Open the file, and check its format, etc.
	if( !OpenFileDialog( m_hwnd, szFileName, &nFileIndex, &dwFlags ))
	    return NULL;
    }

    // Allocate the memory for the structure.
    if( dwFlags & OPENFILENAME_F_3D )
    {
	pFileInfo = new FileInfo3D( this );
    }
    else
	pFileInfo = new FileInfo( this );

    if( NULL == pFileInfo )
    {
	MessageBox( "Cannot add this file -- out of memory",
						    MB_OK|MB_ICONSTOP );
	goto ERROR_DONE_ROUTINE;
    }

    if( m_dlInfoList.GetElementCount() == 0 )
	pFileInfo->ResetCascade();

    m_dlInfoList.Append( pFileInfo );

    pFileInfo->SetSticky((dwFlags & OPENFILENAME_F_STICKYFOCUS)
							? TRUE : FALSE );
    pFileInfo->SetGlobal((dwFlags & OPENFILENAME_F_GLOBALFOCUS)
							? TRUE : FALSE );
    pFileInfo->Set3D((dwFlags & OPENFILENAME_F_3D) ? TRUE : FALSE );
    pFileInfo->SetNotify((dwFlags & OPENFILENAME_F_NOTIFY) ? TRUE : FALSE );
    pFileInfo->SetUseGetPos2((dwFlags & OPENFILENAME_F_GETPOS2)
							? TRUE : FALSE );
    pFileInfo->SetMuteAtMax((dwFlags & OPENFILENAME_F_MUTEMAX) ? TRUE : FALSE );

    if( pFileInfo->LoadWave( szFileName, nFileIndex ) != 0 )
    {
	    m_dlInfoList.Remove( pFileInfo );
	    goto ERROR_DONE_ROUTINE;
    }

    // If we fail after this, make sure to update the list!!!
    if( pFileInfo->Is3D())
	{
	if( m_n3DBuffers++ == 0 )
	    {
	    // We only want to create this dialog once, and if this is the
	    // first 3D buffer opened, then there should be no listener DLG yet
	    ASSERT( NULL == ghwndListener );

	    gpListenerInfo = new ListenerInfo;
	    ghwndListener = CreateDialogParam( ghInst,
						MAKEINTRESOURCE(IDD_3DLISTENER),
						AppWnd.GetHwnd(),
						(DLGPROC)ListenerInfoDlgProc,
						(LPARAM)(gpListenerInfo));
	    ::ShowWindow( ghwndListener, SW_SHOW );
	    }
	}

    // If there are no buffers open, then disable the buffers menu
    EnableMenuItem(GetMenu(m_hwnd), 2, MF_BYPOSITION |
		   (m_dlInfoList.GetElementCount() ? MF_ENABLED : MF_GRAYED));
    DrawMenuBar( m_hwnd );

    return pFileInfo;


ERROR_DONE_ROUTINE:
    if( pFileInfo != NULL )
    {
	delete pFileInfo;
	pFileInfo = NULL;
    }
    return NULL;
}


/* DestroyFileInfo()
 *
 *    Destroys a FileInfo structure and removes it from the list.
 */
void MainWnd::DestroyFileInfo( FileInfo *pfi )
{
    if( NULL == pfi )
	return;

    if( pfi->Is3D() )
	{
	if( --m_n3DBuffers == 0 )
	    {
	    ::DestroyWindow( ghwndListener );
	    ghwndListener = NULL;
	    delete gpListenerInfo;
	    gpListenerInfo = NULL;
	    }
	}

    m_dlInfoList.Remove( pfi );
    delete pfi;
    UpdateStatus();
}


BOOL MainWnd::OnPaint( WPARAM wParam, LPARAM lParam )
    {
    PAINTSTRUCT	ps;
    RECT	rcClient;
    SIZE	sizeExtent;
    TCHAR	szText[128];

    if( !BeginPaint( m_hwnd, &ps ))
	return FALSE;

    GetClientRect( m_hwnd, &rcClient );
    
    SetBkMode( ps.hdc, TRANSPARENT );

    wsprintf( szText, "Free HW Mem: %luKb", m_dscaps.dwFreeHwMemBytes / 1024 );
    GetTextExtentPoint32( ps.hdc, szText, lstrlen(szText), &sizeExtent );
    
    DrawText( ps.hdc, szText, -1, &rcClient, DT_TOP | DT_LEFT );

    wsprintf( szText, "Free HW Buffers: %lu", m_dscaps.dwFreeHwMixingStaticBuffers );
    rcClient.top += sizeExtent.cy;
    DrawText( ps.hdc, szText, -1, &rcClient, DT_TOP | DT_LEFT );

    EndPaint( m_hwnd, &ps );
    return TRUE;
    }


void MainWnd::UpdateStatus( void )
    {
    if( gpds )
	{
	// Get updated statistics on the DSound device
	gpds->GetCaps( &m_dscaps );
	
	// Force a window client area repaint
	InvalidateRect( m_hwnd, NULL, TRUE );
	UpdateWindow();
	}
    }

////////////////////////////////////////////////////////////////////////////
// BatchOpenFiles()
//
//    Takes an array of string pointers and tries to open each as a file to
// playback.  If fPlay is TRUE, the files will be played as they are being
// opened.  If fLoop is TRUE, they will also be set to loop.
//
// Returns: FALSE in the event of catastrophic failure, otherwise TRUE.
//
BOOL MainWnd::BatchOpenFiles( PSTR *ppszFiles, int nFiles, BOOL fPlay, BOOL fLoop )
{
    FileInfo	*pfi;
    int		i;

    // Cap the number of files we can load out of the given set if we'd load
    // too many otherwise
    if( m_dlInfoList.GetElementCount() >= MAXCONTROLS )
    {
	MessageBox( "No more controls allowed" );
        return FALSE;
    }

    for( i = 0; i < nFiles; i++ )
    {
	if(( pfi = OnFileOpen( ppszFiles[i] )) == NULL )
	    continue;

	// LOOP is only obeyed if PLAY was also specified
	if( fPlay )
	{
	    if( fLoop )
		{
		pfi->SetLooped( TRUE );
		}
	    pfi->PlayBuffer();
	}

	pfi->UpdateUI();
    }

    return TRUE;
}

// =====================================
// IsSameFormat
// =====================================
BOOL IsSameFormat(LPWAVEFORMATEX pwfx1, LPWAVEFORMATEX pwfx2)
{
//	if (pwfx1->wFormatTag != pwfx2->wFormatTag)
//		return FALSE;
	if (pwfx1->nChannels != pwfx2->nChannels)
		return FALSE;
	if (pwfx1->nSamplesPerSec != pwfx2->nSamplesPerSec)
		return FALSE;
	if (pwfx1->wBitsPerSample != pwfx2->wBitsPerSample)
		return FALSE;

	return TRUE;
}
