//////////////////////////////////////////////////////////////////////////////
// YogaPlayDlg.cpp
//
// Yoga MPEG Audio Player
//
// 09/11/1999	fOSSiL		Initial version
//

#include "stdafx.h"
#include "YogaPlay.h"
#include "YogaPlayDlg.h"
#include "playw32.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
	CAboutDlg();

// Dialog Data
	//{{AFX_DATA(CAboutDlg)
	enum { IDD = IDD_ABOUTBOX };
	//}}AFX_DATA

	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CAboutDlg)
	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
	//}}AFX_VIRTUAL

// Implementation
protected:
	//{{AFX_MSG(CAboutDlg)
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
	//{{AFX_DATA_INIT(CAboutDlg)
	//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CAboutDlg)
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
	//{{AFX_MSG_MAP(CAboutDlg)
		// No message handlers
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CStereoDisplay

CStereoDisplay::CStereoDisplay()
{
	HDC hDC = ::GetDC(NULL);

	LOGFONT logFont;
	memset(&logFont, 0, sizeof(LOGFONT));

	logFont.lfCharSet = DEFAULT_CHARSET;
	lstrcpyn(logFont.lfFaceName, "MS Sans Serif", sizeof(logFont.lfFaceName));

	POINT pt;
	pt.y = GetDeviceCaps(hDC, LOGPIXELSY) * 180;
	pt.y /= 720;    // 72 points/inch, 10 decipoints/point
	DPtoLP(hDC, &pt, 1);
	POINT ptOrg = {0, 0};
	DPtoLP(hDC, &ptOrg, 1);
	logFont.lfHeight = -abs(pt.y - ptOrg.y);

	Font.Attach(CreateFontIndirect(&logFont));
	
	::ReleaseDC(NULL, hDC);
}

CStereoDisplay::~CStereoDisplay()
{
	Font.DeleteObject();
}


BEGIN_MESSAGE_MAP(CStereoDisplay, CStatic)
	//{{AFX_MSG_MAP(CStereoDisplay)
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CStereoDisplay message handlers

void CStereoDisplay::OnPaint() 
{
	// Do not call CStatic::OnPaint() for painting messages
	CPaintDC dc(this); // device context for painting
	Redraw(&dc);
}

BOOL CStereoDisplay::OnEraseBkgnd(CDC* pDC) 
{
	RECT WinRect;
	GetClientRect(&WinRect);
	return FillRect(pDC->m_hDC, &WinRect, (HBRUSH)GetStockObject(BLACK_BRUSH));
}

void CStereoDisplay::SetText(LPCTSTR Text)
{
	sText = Text;
	Redraw();
}

void CStereoDisplay::Redraw(CDC* pDC)
{
	BOOL bCustDC = FALSE;
	if (pDC == NULL) {
		bCustDC = TRUE;
		pDC = GetDC();
	}
	
	pDC->SaveDC();

	CRect WinRect;
	GetClientRect(&WinRect);

	pDC->SetTextColor(0x00E000);
	pDC->SetBkColor(0);
	pDC->SetTextAlign(TA_CENTER | TA_BASELINE);

	// select our font
	pDC->SelectObject(&Font);

	pDC->ExtTextOut(WinRect.Width() / 2, WinRect.Height() / 2 + 4, ETO_OPAQUE, WinRect, sText, NULL);

	pDC->RestoreDC(-1);
	
	if (bCustDC)
		ReleaseDC(pDC);
}

/////////////////////////////////////////////////////////////////////////////
// CYogaPlayDlg dialog

void CYogaPlayDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CYogaPlayDlg)
	DDX_Control(pDX, IDC_VOLUME, Volume);
	DDX_Control(pDX, IDC_SONGPOS, SongPos);
	DDX_Control(pDX, IDC_EJECT, btnEject);
	DDX_Control(pDX, IDC_RW, btnRewind);
	DDX_Control(pDX, IDC_FFW, btnFastFwd);
	DDX_Control(pDX, IDC_PAUSE, btnPause);
	DDX_Control(pDX, IDC_STOP, btnStop);
	DDX_Control(pDX, IDC_PLAY, btnPlay);
	DDX_Control(pDX, IDC_DISPLAY, Display);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CYogaPlayDlg, CDialog)
	//{{AFX_MSG_MAP(CYogaPlayDlg)
	ON_COMMAND(ID_HELP_ABOUT, OnHelpAbout)
	ON_COMMAND(IDHELP, OnHelp)
	ON_COMMAND(ID_OPTIONS_REMAIN, OnTrackTimeWay)
	ON_COMMAND(ID_OPTIONS_CONTPLAY, OnContPlay)
	ON_COMMAND(ID_FILE_PLAYMPX, OnFileOpen)
	ON_COMMAND(ID_COMMANDS_EJECT, OnEject)
	ON_COMMAND(ID_COMMANDS_STOP, OnStop)
	ON_COMMAND(ID_COMMANDS_PAUSE, OnPause)
	ON_COMMAND(ID_COMMANDS_PLAY, OnPlay)
	ON_COMMAND(ID_COMMANDS_RW, OnSkipBackward)
	ON_COMMAND(ID_COMMANDS_FFW, OnSkipForward)
	ON_WM_KEYDOWN()
	ON_WM_HELPINFO()
	ON_WM_SYSCOMMAND()
	ON_WM_TIMER()
	ON_WM_HSCROLL()
	ON_WM_ACTIVATE()
	ON_BN_CLICKED(IDC_EJECT, OnEject)
	ON_BN_CLICKED(IDC_STOP, OnStop)
	ON_BN_CLICKED(IDC_PAUSE, OnPause)
	ON_BN_CLICKED(IDC_PLAY, OnPlay)
	ON_BN_CLICKED(IDC_FFW, OnSkipForward)
	ON_BN_CLICKED(IDC_RW, OnSkipBackward)
	ON_WM_VSCROLL()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CYogaPlayDlg::CYogaPlayDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CYogaPlayDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CYogaPlayDlg)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32

	bTimeFwd = TRUE;
	bContPlay = FALSE;
	bPaused = FALSE;
	bPlaying = FALSE;
	bUpdatePos = TRUE;
	bPlayerBusy = FALSE;

	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_hAccel = LoadAccelerators(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_MAINACCEL));
	Menu.LoadMenu(IDR_MAINMENU);
	BtnImages.Create(IDB_BUTTONS, 16, 1, 0x808000);
}

UINT DlgBtnIDs[] = {IDC_PLAY, IDC_PAUSE, IDC_STOP, IDC_RW, IDC_FFW, IDC_EJECT, 0};
UINT DlgBtnIcons[] = {0, 1, 2, 3, 6, 7};
MPEGPLAYLIST* pList = (MPEGPLAYLIST*) new long(0);
UINT ListSize = 0;
UINT nCurFile = (DWORD)-1;
MPEGSTREAMINFO Info;
HMIXEROBJ hMixer = NULL;
DWORD MixerLine = 0;
MIXERCONTROL VolCtrl;

int InitMixer()
{
	if (mixerGetNumDevs() < 1 || MMSYSERR_NOERROR != mixerOpen((HMIXER*)&hMixer, 0, NULL, 0, MIXER_OBJECTF_MIXER))
		return FALSE;

	MIXERLINE ml;
	ml.cbStruct = sizeof(ml);
	ml.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
	if (MMSYSERR_NOERROR != mixerGetLineInfo(hMixer, &ml, MIXER_GETLINEINFOF_COMPONENTTYPE)) {
		mixerClose((HMIXER)hMixer);
		hMixer = NULL;
		return FALSE;
	}
	MixerLine = ml.dwLineID;

	MIXERLINECONTROLS mlcs;
	mlcs.cbStruct = sizeof(mlcs);
	mlcs.dwLineID = ml.dwLineID;
	mlcs.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
	mlcs.cbmxctrl = sizeof(MIXERCONTROL);
	mlcs.pamxctrl = &VolCtrl;
	if (MMSYSERR_NOERROR != mixerGetLineControls(hMixer, &mlcs, MIXER_GETLINECONTROLSF_ONEBYTYPE)) {
		mixerClose((HMIXER)hMixer);
		hMixer = NULL;
		return FALSE;
	}

	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CYogaPlayDlg message handlers

BOOL CYogaPlayDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	if (!MpegOpenDevice()) {
		AfxMessageBox("MPEG Device did not initialize", MB_OK | MB_ICONSTOP);
//		EndDialog(1);
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);		// set both icons
	SetMenu(&Menu);

	SetTimer(1, 500, NULL);

	for (int i = 0; DlgBtnIDs[i] != 0; i++)
		GetDlgItem(DlgBtnIDs[i])->SendMessage(BM_SETIMAGE, IMAGE_ICON, (LPARAM)BtnImages.ExtractIcon(DlgBtnIcons[i]));

	// init song position slider
	SongPos.SetRangeMin(0);
	SongPos.SetRangeMax(1000000);
	SongPos.SetPageSize(1000000/10);
	SongPos.SetLineSize(1000000/200);

	// init volume control
	if (InitMixer()) {
		Volume.SetRangeMin(VolCtrl.Bounds.dwMinimum);
		Volume.SetRangeMax(VolCtrl.Bounds.dwMaximum);
		int Range = VolCtrl.Bounds.dwMaximum - VolCtrl.Bounds.dwMinimum;
		Volume.SetPageSize(Range/10);
		Volume.SetLineSize(Range/200);
		RefreshVolume();
	} else {
		Volume.EnableWindow(FALSE);
	}
		
	RefreshDisplay();

	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CYogaPlayDlg::OnHelpAbout()
{
	CAboutDlg Dlg;
	Dlg.DoModal();
}

void CYogaPlayDlg::OnTrackTimeWay()
{
	bTimeFwd = !bTimeFwd;
	Menu.CheckMenuItem(ID_OPTIONS_REMAIN, bTimeFwd ? MF_UNCHECKED : MF_CHECKED);
}


void CYogaPlayDlg::OnFileOpen()
{
	if (AskFiles()) {
		OnStop();
		MpegSetPlayList(pList, ListSize);
		OnPlay();
	}
}

int CYogaPlayDlg::AskFiles()
{
	CHAR Files[4096] = "";

	OPENFILENAME ofn;
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = m_hWnd;
	// add "Playlists (*.m3u;*.pls)\0*.m3u;*.pls\0" when playlist support is coded
	ofn.lpstrFilter = "MPEG Audio (*.mp3;*.mp2)\0*.mp2;*.mp3\0All Files (*.*)\0*.*\0\0";
	ofn.lpstrCustomFilter = NULL;
	ofn.nFilterIndex = 1;
	ofn.lpstrFile = Files;
	ofn.nMaxFile = sizeof(Files);
	ofn.lpstrFileTitle = NULL;
	ofn.lpstrInitialDir = NULL;
	ofn.lpstrTitle = NULL;
	ofn.Flags = OFN_EXPLORER | OFN_ALLOWMULTISELECT | OFN_FILEMUSTEXIST;
	ofn.lpstrDefExt = NULL;
	
	BOOL Ret = GetOpenFileName(&ofn);

	if (Ret) {
		// form device play-list
		if (pList)
			delete pList;

		int NamesSize = 0, cFiles = 0;
		
		if (Files[ofn.nFileOffset - 1] == 0) {
			// it's multi-select
			UINT len;
			char* pName = Files + ofn.nFileOffset;
			while (*pName != 0) {
				len = lstrlen(pName) + 1;
				NamesSize += ofn.nFileOffset + len;
				pName += len;
				cFiles++;
			}
		} else {
			// single selection
			cFiles = 1;
			NamesSize = lstrlen(Files) + 1;
			Files[ofn.nFileOffset - 1] = 0;
		}

		ListSize = MpegReqListSize(cFiles, NamesSize);
		pList = (MPEGPLAYLIST*) new char[ListSize];

		pList->Count = cFiles;
		char* pName = Files + ofn.nFileOffset;
		char* pNameBuf = MpegListStringBuf(pList);
		
		for (int i = 0; i < cFiles; i++) {
			pList->NameOfs[i] = MpegListNameOfs(pList, pNameBuf);
			lstrcpy(pNameBuf, Files);
			lstrcat(pNameBuf, "\\");
			lstrcat(pNameBuf, pName);
			pName += lstrlen(pName) + 1;
			pNameBuf += lstrlen(pNameBuf) + 1;
		}
	
		nCurFile = 0;
	}

	return Ret;
}

void CYogaPlayDlg::OnEject() 
{
	OnStop();

	if (AskFiles()) {
		MpegSetPlayList(pList, ListSize);
		OnPlay();
	}
}

void CYogaPlayDlg::OnStop() 
{
	bPaused = FALSE;
	MpegStopPlay();
	bPlaying = FALSE;
}

void CYogaPlayDlg::OnPause() 
{
	if (bPlaying && btnPause.IsWindowEnabled()) {
		bPaused = !bPaused;
		if (!(bPaused ? MpegPausePlay() : MpegResumePlay())) {
			bPaused = FALSE;
			btnPause.EnableWindow(FALSE);
		}
	}
}

void CYogaPlayDlg::OnPlay() 
{
	if (bPlaying) {
		if (bPaused) {
			bPaused = FALSE;
			MpegResumePlay();
		}
	} else if (nCurFile != -1) {
		if (MpegPlayFile(nCurFile))
			bPlaying = TRUE;
		else
			AfxMessageBox("Cannot play selected file (not enough mem?)", MB_OK | MB_ICONSTOP);
	}
}

void CYogaPlayDlg::OnCancel()
{	// just swallow it
}

void CYogaPlayDlg::OnOK()
{
	mixerClose((HMIXER)hMixer);

	if (bPlaying)
		MpegStopPlay();
	
	MpegCloseDevice();
	
	EndDialog(IDOK);
}

void CYogaPlayDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	// swallow Esc
	if (nChar != VK_ESCAPE)
		CDialog::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CYogaPlayDlg::OnHelp()
{
	AfxMessageBox("If you cannot figure out this brain-dead GUI,\nwe doubt that you need the Yoga Player", MB_OK | MB_ICONINFORMATION);
}

BOOL CYogaPlayDlg::OnHelpInfo(HELPINFO* pHelpInfo) 
{
	OnHelp();
	return TRUE;
}

void CYogaPlayDlg::OnSysCommand(UINT nID, LPARAM lParam) 
{
	if (nID == SC_CLOSE)	// translate Close->OK
		SendMessage(WM_COMMAND, IDOK, 0);
	else
		CDialog::OnSysCommand(nID, lParam);
}

BOOL CYogaPlayDlg::PreTranslateMessage(MSG* pMsg) 
{
	if (pMsg->message >= WM_KEYFIRST && pMsg->message <= WM_KEYLAST && pMsg->wParam != VK_TAB && m_hAccel != NULL)
		return TranslateAccelerator(m_hWnd, m_hAccel, pMsg);
	else
		return CDialog::PreTranslateMessage(pMsg);
}

void CYogaPlayDlg::UpdateStatus()
{
	MPEGSTATUSINFO Info;
	if (!MpegGetStatusInfo(&Info)) {
		bPlaying = FALSE;
		bPaused = FALSE;
		bPlayerBusy = TRUE;
		return;
	}

	nCurFile = Info.Track;
	bPlaying = Info.IsPlaying;
	bPlayerBusy	= Info.IsBusy;
}

void CYogaPlayDlg::RefreshDisplay()
{
	UpdateStatus();

	int Secs = 0;

	if (bPlaying) {
		MpegGetStreamInfo(&Info);
		int Pos = MpegGetStreamPtr();
		int Bps = Info.BitRate * 128;
		Secs = bTimeFwd ? Pos / Bps : (Info.StreamSize - Pos) / Bps;
		if (bUpdatePos)
			SongPos.SetPos((int)(((double)Pos) / Info.StreamSize * 1000000));
	}

	char buf[20];
	wsprintf(buf, "[%0.2d]%c%0.2d:%0.2d", nCurFile + 1, bTimeFwd ? ' ' : '-', Secs / 60, Secs % 60);
	
	Display.SetText(buf);
}

void CYogaPlayDlg::OnTimer(UINT nIDEvent) 
{
	RefreshDisplay();

	if (bPlaying && !bPlayerBusy) {
		UINT Pos = MpegGetStreamPtr();
		if (Pos >= Info.StreamSize) {
			nCurFile++;
			if (nCurFile >= pList->Count) {
				nCurFile = 0;
				if (!bContPlay) {
					MpegStopPlay();
					bPlaying = FALSE;
				}
			}

			if (bPlaying) {
				MpegPlayFile(nCurFile);
				RefreshDisplay();
			}
		}
	}
}

void CYogaPlayDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	// called when Slider is moved
	OnSongPosChange(nSBCode);
}

void CYogaPlayDlg::OnContPlay()
{
	bContPlay = !bContPlay;
	Menu.CheckMenuItem(ID_OPTIONS_CONTPLAY, bContPlay ? MF_CHECKED : MF_CHECKED);
}

void CYogaPlayDlg::OnSkipForward()
{
	if (pList == 0)
		return;

	MpegStopPlay();
	if (bPaused) {
		bPlaying = FALSE;
		bPaused = FALSE;
	}

	nCurFile++;
	if (nCurFile >= pList->Count) {
		nCurFile = 0;
		if (!bContPlay)
			bPlaying = FALSE;
	}

	if (bPlaying)
		MpegPlayFile(nCurFile);

	RefreshDisplay();
}

void CYogaPlayDlg::OnSkipBackward()
{
	if (pList == 0)
		return;

	UINT Pos = SongPos.GetPos();

	MpegStopPlay();
	if (bPaused) {
		bPlaying = FALSE;
		bPaused = FALSE;
	}

	// restart this song if too far into it
	// otherwise skip to previous
	if (Pos < 6666) { // some arbitrary value
		// start prev. song
		nCurFile--;
		if (nCurFile == -1) {
			nCurFile = pList->Count - 1;
			if (!bContPlay)
				bPlaying = FALSE;
		}
	}
	
	if (bPlaying)
		MpegPlayFile(nCurFile);

	RefreshDisplay();
}

void CYogaPlayDlg::RefreshVolume()
{
	if (hMixer == NULL)
		return;

	MIXERCONTROLDETAILS mcds;
	MIXERCONTROLDETAILS_UNSIGNED mcd;
	mcds.cbStruct = sizeof(mcds);
	mcds.dwControlID = VolCtrl.dwControlID;
	mcds.cChannels = 1;
	mcds.cMultipleItems = 0;
	mcds.cbDetails = sizeof(mcd);
	mcds.paDetails = &mcd;
	if (MMSYSERR_NOERROR == mixerGetControlDetails(hMixer, &mcds, MIXER_GETCONTROLDETAILSF_VALUE))
		Volume.SetPos(VolCtrl.Bounds.dwMaximum - (mcd.dwValue - VolCtrl.Bounds.dwMinimum));
}

void CYogaPlayDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) 
{
	CDialog::OnActivate(nState, pWndOther, bMinimized);
	
	if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE) {
		RefreshVolume();
	}
}

void CYogaPlayDlg::OnSongPosChange(UINT nSBCode)
{
	if (!bPlaying)
		return;		// screw this

	if (nSBCode == TB_ENDTRACK) {
		// play from new position
		double pos = Info.StreamSize;
		pos = pos / 1000000 * SongPos.GetPos();
		MpegSetStreamPtr((UINT)pos);
		
		bUpdatePos = TRUE;
	} else {
		// slider is being moved, freeze its update
		bUpdatePos = FALSE;
	}
}

void CYogaPlayDlg::OnVolumeChange()
{
	MIXERCONTROLDETAILS_UNSIGNED mcd;
	mcd.dwValue = VolCtrl.Bounds.dwMaximum - Volume.GetPos() + VolCtrl.Bounds.dwMinimum;

	MIXERCONTROLDETAILS mcds;
	mcds.cbStruct = sizeof(mcds);
	mcds.dwControlID = VolCtrl.dwControlID;
	mcds.cChannels = 1;
	mcds.cMultipleItems = 0;
	mcds.cbDetails = sizeof(mcd);
	mcds.paDetails = &mcd;
	mixerSetControlDetails(hMixer, &mcds, MIXER_GETCONTROLDETAILSF_VALUE);
}

void CYogaPlayDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	// called when Slider is moved
	OnVolumeChange();
}
