// AudioSwitcherDlg.cpp : implementation file
//

#include "stdafx.h"
#include "AudioSwitcher.h"
#include "AudioSwitcherDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


CString IntToHex(const int i)
{
	CString s;
	s.Format(_T("%.8X"), i);
	return s;
}

CString IntToHex(const __int64 i)
{
	CString s;
	s.Format(_T("%.4X%.8X"), static_cast<INT32>((i >> 32) & 0x00000FFFF),
		static_cast<INT32>(i) & 0xFFFFFFFF);
	return s;
}

// CAudioSwitcherDlg dialog




CAudioSwitcherDlg::CAudioSwitcherDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CAudioSwitcherDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CAudioSwitcherDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_COMBO_ROLE, cbRole);
	DDX_Control(pDX, IDC_CHECK_ACTIVE, cbActive);
	DDX_Control(pDX, IDC_CHECK_DISABLED, cbDisabled);
	DDX_Control(pDX, IDC_CHECK_UNPLUGGED, cbUnplugged);
	DDX_Control(pDX, IDC_CHECK_NOT_PRESENT, cbNotPresent);
	DDX_Control(pDX, IDC_LIST_DEVICES, lvDevices);
	DDX_Control(pDX, IDC_LIST_LOG, lbLog);
	DDX_Control(pDX, IDC_CHECK_USE_MAC, cbUseMac);
	DDX_Control(pDX, IDC_COMBO_FLOW, cbFlow);
}

BEGIN_MESSAGE_MAP(CAudioSwitcherDlg, CDialog)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDC_BUTTON_ENUM, &CAudioSwitcherDlg::OnBnClickedButtonEnum)
	ON_BN_CLICKED(IDC_BUTTON_DEFAULT, &CAudioSwitcherDlg::OnBnClickedButtonDefault)
	ON_BN_CLICKED(IDC_BUTTON_CLEAR, &CAudioSwitcherDlg::OnBnClickedButtonClear)
	ON_BN_CLICKED(IDC_BUTTON_REFRESH, &CAudioSwitcherDlg::OnBnClickedButtonRefresh)
	ON_BN_CLICKED(IDC_BUTTON_CONNECT, &CAudioSwitcherDlg::OnBnClickedButtonConnect)
	ON_BN_CLICKED(IDC_BUTTON_DISCONNECT, &CAudioSwitcherDlg::OnBnClickedButtonDisconnect)
	ON_BN_CLICKED(IDC_BUTTON_GET_DEFAULT, &CAudioSwitcherDlg::OnBnClickedButtonGetDefault)
END_MESSAGE_MAP()


// CAudioSwitcherDlg message handlers

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

	// 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 big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// TODO: Add extra initialization here

	cbRole.AddString(_T("Console"));
	cbRole.AddString(_T("Multimedia"));
	cbRole.AddString(_T("Communications"));
	cbRole.SetCurSel(0);

	cbFlow.AddString(_T("Capture"));
	cbFlow.AddString(_T("Render"));
	cbFlow.SetCurSel(0);
	
	cbActive.SetCheck(TRUE);

	lvDevices.InsertColumn(0, _T("Id"), 0, 100);
	lvDevices.InsertColumn(1, _T("Friendly name"), 0, 100);
	lvDevices.InsertColumn(2, _T("Description"), 0, 100);
	lvDevices.InsertColumn(3, _T("Flow"), 0, 100);
	lvDevices.InsertColumn(4, _T("Roles"), 0, 100);
	lvDevices.InsertColumn(5, _T("States"), 0, 100);
	lvDevices.InsertColumn(6, _T("MAC"), 0, 100);
	lvDevices.InsertColumn(6, _T("Service"), 0, 100);
	lvDevices.SetExtendedStyle(lvDevices.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	__hook(&CwclAudioSwitcher::OnOpened, &Switcher, &CAudioSwitcherDlg::SwitcherOpened);
	__hook(&CwclAudioSwitcher::OnClosed, &Switcher, &CAudioSwitcherDlg::SwitcherClosed);
	__hook(&CwclAudioSwitcher::OnDefaultDeviceChanged, &Switcher, &CAudioSwitcherDlg::SwitcherDefaultDeviceChanged);
	__hook(&CwclAudioSwitcher::OnDeviceAdded, &Switcher, &CAudioSwitcherDlg::SwitcherDeviceAdded);
	__hook(&CwclAudioSwitcher::OnDeviceRemoved, &Switcher, &CAudioSwitcherDlg::SwitcherDeviceRemoved);
	__hook(&CwclAudioSwitcher::OnStateChanged, &Switcher, &CAudioSwitcherDlg::SwitcherStateChanged);

	int Res = Switcher.Open();
	if (Res != WCL_E_SUCCESS)
		lbLog.AddString(_T("Open failed: 0x") + IntToHex(Res));

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

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CAudioSwitcherDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CAudioSwitcherDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

void CAudioSwitcherDlg::OnDestroy()
{
	CDialog::OnDestroy();

	Switcher.Close();
	__unhook(&Switcher);
}

void CAudioSwitcherDlg::OnBnClickedButtonEnum()
{
	EnumDevices();
}

void CAudioSwitcherDlg::OnBnClickedButtonDefault()
{
	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	if (Pos == NULL)
		AfxMessageBox(_T("Select device"));
	else
	{
		wclAudioDeviceRole Role;
		switch (cbRole.GetCurSel())
		{
			case 0:
				Role = drConsole;
				break;
			case 1:
				Role = drMultimedia;
				break;
			default:
				Role = drCommunications;
				break;
		}
		
		int Item = lvDevices.GetNextSelectedItem(Pos);
		int Res = Switcher.SetDefault(Role, tstring(lvDevices.GetItemText(Item, 0)));
		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Unable to set default device: 0x") + IntToHex(Res));
	}
}

void CAudioSwitcherDlg::SwitcherOpened(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);

	lbLog.AddString(_T("Audio switcher opened"));
	EnumDevices();
}

void CAudioSwitcherDlg::EnumDevices()
{
	wclAudioDeviceStates States;
	if (cbActive.GetCheck())
		States.insert(asActive);
	if (cbDisabled.GetCheck())
		States.insert(asDisabled);
	if (cbNotPresent.GetCheck())
		States.insert(asNotPresent);
	if (cbUnplugged.GetCheck())
		States.insert(asUnplugged);
	if (States.empty())
		AfxMessageBox(_T("Select device state."));
	else
	{
		lvDevices.DeleteAllItems();
	
		wclAudioDevices Devices;
		int Res = Switcher.Enum(States, Devices);
		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Enum failed: 0x") + IntToHex(Res));
		else
		{
			if (Devices.size() > 0)
			{
				for (wclAudioDevices::iterator Device = Devices.begin(); Device != Devices.end(); Device++)
				{
					int Item = lvDevices.GetItemCount();
					lvDevices.InsertItem(Item, Device->Id.c_str());
					lvDevices.SetItemText(Item, 1, _T(""));
					lvDevices.SetItemText(Item, 2, _T(""));
					lvDevices.SetItemText(Item, 3, _T(""));
					lvDevices.SetItemText(Item, 4, _T(""));
					lvDevices.SetItemText(Item, 5, _T(""));
					lvDevices.SetItemText(Item, 6, _T(""));
					lvDevices.SetItemText(Item, 7, _T(""));
					UpdateDevice(Item, (*Device));
				}
			}
		}
	}
}

void CAudioSwitcherDlg::SwitcherClosed(void* Sender)
{
	UNREFERENCED_PARAMETER(Sender);

	lvDevices.DeleteAllItems();
	lbLog.AddString(_T("Audio switcher closed"));
}

void CAudioSwitcherDlg::OnBnClickedButtonClear()
{
	lbLog.ResetContent();
}

void CAudioSwitcherDlg::SwitcherDefaultDeviceChanged(void* Sender,
	const tstring& Id, const wclAudioDeviceDataFlow Flow,
	const wclAudioDeviceRole Role)
{
	UNREFERENCED_PARAMETER(Sender);

	CString Str = _T("Default device changed: ") + CString(Id.c_str()) + _T("; Flow: ");
	if (Flow == dfRender)
		Str += _T("Render");
	else
		Str += _T("Capture");
	Str += _T("; Role: ");
	switch (Role)
	{
		case drConsole:
			Str += _T("Console");
			break;
		case drMultimedia:
			Str += _T("Multimedia");
			break;
		case drCommunications:
			Str += _T("Communications");
			break;
	}
	lbLog.AddString(Str);
}

void CAudioSwitcherDlg::SwitcherDeviceAdded(void* Sender, const tstring& Id)
{
	UNREFERENCED_PARAMETER(Sender);

	CString s = _T("Device added: ");
	s += CString(Id.c_str());
	lbLog.AddString(s);
}

void CAudioSwitcherDlg::SwitcherDeviceRemoved(void* Sender, const tstring& Id)
{
	UNREFERENCED_PARAMETER(Sender);
	
	CString s = _T("Device removed: ");
	s += CString(Id.c_str());
	lbLog.AddString(s);
}

void CAudioSwitcherDlg::SwitcherStateChanged(void* Sender, const tstring& Id,
	const wclAudioDeviceState State)
{
	UNREFERENCED_PARAMETER(Sender);

	CString Str;
	switch (State)
	{
		case asActive:
			Str = _T("Active");
			break;
		case asDisabled:
			Str = _T("Disabled");
			break;
		case asNotPresent:
			Str = _T("NotPresent");
			break;
		case asUnplugged:
			Str = _T("Unplugged");
			break;
	}
	lbLog.AddString(_T("Device ") + CString(Id.c_str()) + _T(" state changed: ") + Str);
}

void CAudioSwitcherDlg::UpdateDevice(int Item, const wclAudioDevice& Device)
{
	CString FlowStr;
	if (Device.Flow == dfRender)
		FlowStr = _T("Render");
	else
		FlowStr = _T("Capture");
	
	CString RolesStr = _T("[ ");
	if (Device.Roles.find(drConsole) != Device.Roles.end())
		RolesStr += _T("Console ");
	if (Device.Roles.find(drMultimedia) != Device.Roles.end())
		RolesStr += _T("Multimedia ");
	if (Device.Roles.find(drCommunications) != Device.Roles.end())
		RolesStr += _T("Communications ");
	RolesStr += _T("]");
	
	CString StateStr;
	switch (Device.State)
	{
		case asActive:
			StateStr = _T("Active");
			break;
		case asDisabled:
			StateStr = _T("Disabled");
			break;
		case asNotPresent:
			StateStr = _T("NotPresent");
			break;
		case asUnplugged:
			StateStr = _T("Unplugged");
			break;
	}
	
	CString MacStr;
	CString ServiceStr;
	if (Device.IsBluetooth)
	{
		MacStr = IntToHex(Device.Mac);
		LPOLESTR Guid;
		StringFromCLSID(Device.Service, &Guid);
		ServiceStr = CString(Guid);
		CoTaskMemFree(Guid);
	}
	else
	{
		MacStr = _T("");
		ServiceStr = _T("");
	}

	lvDevices.SetItemText(Item, 1, Device.FriendlyName.c_str());
	lvDevices.SetItemText(Item, 2, Device.Description.c_str());
	lvDevices.SetItemText(Item, 3, FlowStr);
	lvDevices.SetItemText(Item, 4, RolesStr);
	lvDevices.SetItemText(Item, 5, StateStr);
	lvDevices.SetItemText(Item, 6, MacStr);
	lvDevices.SetItemText(Item, 7, ServiceStr);
}

void CAudioSwitcherDlg::OnBnClickedButtonRefresh()
{
	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	if (Pos == NULL)
		AfxMessageBox(_T("Select device"));
	else
	{
		int Item = lvDevices.GetNextSelectedItem(Pos);
		CString Id = lvDevices.GetItemText(Item, 0);
		wclAudioDevice Device;
		int Res = Switcher.GetDeviceDetails(Id.GetBuffer(), Device);
		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Refresh device error: 0x") + IntToHex(Res));
		else
			UpdateDevice(Item, Device);
	}
}

void CAudioSwitcherDlg::OnBnClickedButtonConnect()
{
	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	if (Pos == NULL)
		AfxMessageBox(_T("Select device"));
	else
	{
		int Res;
		int Item = lvDevices.GetNextSelectedItem(Pos);
		if (cbUseMac.GetCheck())
		{
			CString Mac = lvDevices.GetItemText(Item, 6);
			if (Mac == _T(""))
			{
				AfxMessageBox(_T("Selected device is not Bluetooth enabled"));
				return;
			}
			
			Res = Switcher.Connect(_tcstoi64(Mac, NULL, 16));
		}
		else
		{
			CString Id = lvDevices.GetItemText(Item, 0);
			Res = Switcher.Connect(tstring(Id.GetBuffer()));
		}

		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Connect error: 0x") + IntToHex(Res));
	}
}

void CAudioSwitcherDlg::OnBnClickedButtonDisconnect()
{
	POSITION Pos = lvDevices.GetFirstSelectedItemPosition();
	if (Pos == NULL)
		AfxMessageBox(_T("Select device"));
	else
	{
		int Res;
		int Item = lvDevices.GetNextSelectedItem(Pos);
		if (cbUseMac.GetCheck())
		{
			CString Mac = lvDevices.GetItemText(Item, 6);
			if (Mac == _T(""))
			{
				AfxMessageBox(_T("Selected device is not Bluetooth enabled"));
				return;
			}
			
			Res = Switcher.Disconnect(_tcstoi64(Mac, NULL, 16));
		}
		else
		{
			CString Id = lvDevices.GetItemText(Item, 0);
			Res = Switcher.Disconnect(tstring(Id.GetBuffer()));
		}

		if (Res != WCL_E_SUCCESS)
			AfxMessageBox(_T("Connect error: 0x") + IntToHex(Res));
	}
}

void CAudioSwitcherDlg::OnBnClickedButtonGetDefault()
{
	wclAudioDeviceRole Role;
	switch (cbRole.GetCurSel())
	{
		case 0:
			Role = drConsole;
			break;
		case 1:
			Role = drMultimedia;
			break;
		default:
			Role = drCommunications;
			break;
	}

	wclAudioDeviceDataFlow Flow;
	if (cbFlow.GetCurSel() == 0)
		Flow = dfCapture;
	else
		Flow = dfRender;

	tstring Id;
	int Res = Switcher.GetDefault(Role, Flow, Id);
	if (Res != WCL_E_SUCCESS)
		AfxMessageBox(_T("Unable to get default device: 0x") + IntToHex(Res));
	else
	{
		CString s = _T("Default device ID: ");
		s += Id.c_str();
		AfxMessageBox(s);
	}
}
