/*************************************************
*           BK Emulator v3.9 for Windows         *
*************************************************/

/*
File: TapeManagerDlg.cpp
Version: 1.0.0
Written by: Yuriy Kalmykov <kalmykov@stoik.com>
    Copyright (c) 2002-2004 Yuriy Kalmykov

    BK Emulator is a program emulated hardware environment for running
code for BK 0010(01) in different configurations.

    This code may be used in compiled form in any way you desire.
This file or it's parts can't be redistributed without the authors
written consent, but can be modified for your private needs.
    Providing that this notice and the authors name and all copyright
notices remains intact.

    Please, an email me to know how you are using it and where. You can
ask me for any information about this below code or any attendant
knowledge.

    This file is provided "as is" with no expressed or implied warranty.
The author accepts no liability for any damage or loss of business that
this product may cause.
*/


// TapeManagerDlg.cpp : implementation file
//

#include "stdafx.h"
#include "Globals.h"
#include "BKMessageBox.h"
#include "BrowseForFolder.h"
#include "TapeManagerDlg.h"

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

/////////////////////////////////////////////////////////////////////////////
// CTapeManagerDlg dialog

CTapeManagerDlg::CTapeManagerDlg(CWnd *pParent /*=nullptr*/)
	: CDialogEx(CTapeManagerDlg::IDD, pParent),
	  m_CurrentCaptureBuffer(nullptr),
	  m_hWaveIn(nullptr),
	  m_hStatusNew(nullptr),
	  m_hStatusExit(nullptr),
	  m_pTapeUnit(nullptr),
	  m_pStatusThread(nullptr),
	  m_nOutStringsCount(OUTSTRINGS_COUNT),
	  m_bCaptureMode(false),
	  m_bRun(false)
{
	m_strCurrentFolder = g_Config.m_strTapePath;
	m_nSilenceLength = g_Config.m_nSoundSampleRate / 10;
}

CTapeManagerDlg::~CTapeManagerDlg()
{
	if (m_hStatusExit)
	{
		CloseHandle(m_hStatusExit);
	}

	if (m_hStatusNew)
	{
		CloseHandle(m_hStatusNew);
	}

	m_imgList.DeleteImageList();
}

void CTapeManagerDlg::DoDataExchange(CDataExchange *pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_TM_LIST, m_list);
}

BEGIN_MESSAGE_MAP(CTapeManagerDlg, CDialogEx)
	ON_MESSAGE(WM_BUFFER_READY, &OnBufferReady)
	ON_MESSAGE(WM_INFO_READY, &OnInfoReady)
	ON_NOTIFY(LVN_GETDISPINFO, IDC_TM_LIST, &OnGetdispinfoTmList)
	ON_NOTIFY(NM_RCLICK, IDC_TM_LIST, &OnRclickTmList)
	ON_BN_CLICKED(IDC_TM_LOAD, &OnTmLoad)
	ON_BN_CLICKED(IDC_TM_BROWSE, &OnTmBrowse)
	ON_BN_CLICKED(IDC_TM_REMOVE, &OnTmRemove)
	ON_UPDATE_COMMAND_UI(IDC_TM_REMOVE, &OnUpdateTmRemove)
	ON_UPDATE_COMMAND_UI(IDC_TM_SAVE_AS, &OnUpdateSelendokTmSaveAs)
	ON_CBN_SELENDOK(IDC_TM_SAVE_AS, &OnSelendokTmSaveAs)
	ON_COMMAND(ID_TM_SELECTALL, &OnTmSelectall)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTapeManagerDlg message handlers


BOOL CTapeManagerDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	if (FillDevices() == 0)
	{
		// предупредим, что устройств ввода нету, но прочие действия делать ещё можно
		CString strMessage;
		strMessage.LoadString(IDS_TAPAMNGR_CAPTURE_ERROR);
		CBKMessageBox(strMessage, MB_OK | MB_ICONSTOP);
	}
	else
	{
		if (OpenDevice())   //всегда открываем устройство ввода по умолчанию.
		{
			//остальные устройства из списка - так, для информации. т.к. алгоритм не
			//предусматривает смены устройства на ходу.
			m_bRun = true;
			waveInStart(m_hWaveIn);
		}
	}

	// Start status event thread
	m_pStatusThread = AfxBeginThread((AFX_THREADPROC)StatusThreadWrapper, (void *)this, THREAD_PRIORITY_NORMAL);
	m_pStatusThread->m_bAutoDelete = false;
	InitListCtrl();
	InitComboCtrl();
	SetFolder(m_strCurrentFolder);
	FileListChanged(m_list.GetItemCount()); //разрешим/запретим некоторые кнопки
	return TRUE;  // return TRUE unless you set the focus to a control
	// EXCEPTION: OCX Property Pages should return FALSE
}


void CTapeManagerDlg::GetAllBuffers(BYTE *pBuffer)
{
	POSITION pos = m_captureBuffers.GetHeadPosition();
	int nBufferLength = GetBufferLength();
	int nBuffer = 0;

	while (pos)
	{
		BYTE *pBuff = m_captureBuffers.GetNext(pos);
		memcpy(pBuffer + nBuffer * nBufferLength, pBuff, nBufferLength);
		nBuffer++;
	}
}


void CTapeManagerDlg::ClearBuffers()
{
	POSITION pos = m_captureBuffers.GetHeadPosition();
	DWORD nBufferLength = GetBufferLength();

	while (pos)
	{
		BYTE *pBuff = m_captureBuffers.GetNext(pos);
		memset(pBuff, 0, nBufferLength);
	}
}


LRESULT CTapeManagerDlg::OnBufferReady(WPARAM wParam, LPARAM lParam)
{
	DebugDraw(m_CurrentCaptureBuffer, GetBufferLength());

	if (!m_bCaptureMode)
	{
		// Выделим память под все буферы
		DWORD nLength = GetAllBuffersLength();
		m_headReader.AllocWaveBuffer(nLength);
		// Скопируем все захваченные буферы в парсер кассеты
		GetAllBuffers(m_headReader.GetWaveBuffer());
		// Попробуем найти заголовок
		TAPE_FILE_INFO tfi;
		m_headReader.GetWaveFile(&tfi, true);

		//TRACE ("tfi.start_tuning = %i\n", tfi.start_tuning);
		//TRACE ("tfi.synchro_header = %i\n", tfi.synchro_header);
		if (tfi.synchro_data != -1)
		{
			// Если заголовок найден - подготовимся к захвату данных
			m_nCaptureOffset = tfi.start_tuning * SAMPLE_LENGTH;
			m_nCaptureLength = 0;
			CString strName = BKToUNICODE(tfi.name, 16);
			strName.Trim();
			CString strNameFormat;
			strNameFormat.Format(IDS_TAPEMNGR_NAME_FORMAT, strName, tfi.address, tfi.length);
			OutString(strNameFormat);
			// Открываем временный файл для захвата
			m_strCaptureFileName = GetTmpFilePath();

			if (m_captureFile.Open(m_strCaptureFileName, CFile::modeCreate | CFile::modeWrite))
			{
				m_bCaptureMode = true;
				WriteBuffer(m_headReader.GetWaveBuffer(), nLength);
			}
		}
	}
	else
	{
		// Продолжаем захват данных
		WriteBuffer(m_CurrentCaptureBuffer, GetBufferLength());
	}

	return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
//на входе размер буфера в байтах
bool CTapeManagerDlg::WriteBuffer(BYTE *pBuff, DWORD nSize)
{
	if (m_captureFile.m_hFile == CFile::hFileNull)
	{
		return false;
	}

	m_nCaptureLength += nSize;

	if (m_nCaptureLength < m_nCaptureOffset)
	{
		return true;
	}

	DWORD nOffs = max(0, (nSize - (m_nCaptureLength - m_nCaptureOffset)));
	DWORD nSizeToWrite = min(nSize, m_nCaptureLength - m_nCaptureOffset);
	m_captureFile.Write(pBuff + nOffs, nSizeToWrite);

	if (IsCaptureEnd(pBuff + nOffs, nSizeToWrite))
	{
		m_captureFile.Close();
		m_bCaptureMode = false;
		m_strCaptureFileNameList.AddTail(m_strCaptureFileName);
		CString strOut;
		strOut.LoadString(IDS_TAPEMNGR_END_OF_FILE);
		OutString(strOut);
		auto pTapeUnit = new CTapeUnit;
		pTapeUnit->SetTmpFile(m_strCaptureFileName);
		InsertNewUnit(pTapeUnit);
		FileListChanged(m_list.GetItemCount());
		RetrieveInfo(reinterpret_cast<CTapeUnit *>(m_list.GetItemData(m_nTapePos)));
	}

	return true;
}


bool CTapeManagerDlg::IsCaptureEnd(BYTE *pBuff, DWORD nSize)
{
	register auto pSMBuff = reinterpret_cast<SAMPLE_TYPE *>(pBuff);
	register int nSmplSize = nSize / SAMPLE_LENGTH;
	register int nAverageSum = 0;
	register int nSilenceLength = 0;

	for (int n = 0; n < nSmplSize; ++n)
	{
		register int nAverage = (n > 0) ? (nAverageSum / n) : 0;
		register int sample = ((int)(*pSMBuff++) + (int)(*pSMBuff++)) / 2;
		nAverageSum += sample;
		register int ds = sample - nAverage;

		if (-SILENCE_THRESHOLD <= ds && ds <= SILENCE_THRESHOLD)
		{
			nSilenceLength++;
		}
		else
		{
			nSilenceLength = 0;
		}
	}

	//TRACE ("nAverage = %i\n", nAverageSum / nSize);
	//TRACE ("Silence length = %i\n", nSilenceLength);
	if (nSilenceLength >= m_nSilenceLength)
	{
		return true;
	}

	return false;
}


void CTapeManagerDlg::OutString(const CString &strOut)
{
	if (m_outStrings.GetCount() >= OUTSTRINGS_COUNT)
	{
		m_outStrings.RemoveHead();
	}

	m_outStrings.AddTail(strOut);
	CString strOutput;
	POSITION pos = m_outStrings.GetHeadPosition();
	bool bFirst = true;

	while (pos)
	{
		if (bFirst)
		{
			bFirst = false;
			strOutput = m_outStrings.GetNext(pos);
		}
		else
		{
			strOutput += _T("\r\n") + m_outStrings.GetNext(pos);
		}
	}

	SetDlgItemText(IDC_TM_OUT, strOutput);
	auto pEdit = static_cast<CEdit *>(GetDlgItem(IDC_TM_OUT));
	pEdit->LineScroll(OUTSTRINGS_COUNT);
}


/////////////////////////////////////////////////////////////////////////////
//на входе указатель на буфер данных. внутри 16 бит стерео
void CTapeManagerDlg::DebugDraw(BYTE *pBuffer, int nBufferLength)
{
//#ifdef _DEBUG
	CWnd *pMonitor = GetDlgItem(IDC_TM_MONITOR);

	if (pMonitor)
	{
		CClientDC dc(this);
		CRect rcMonitor;
		pMonitor->GetWindowRect(&rcMonitor);
		ScreenToClient(&rcMonitor);
		rcMonitor.DeflateRect(1, 1);
		register int x_offs = rcMonitor.left;
		register int y_offs = rcMonitor.top;
		register int width = rcMonitor.Width();
		register int height = rcMonitor.Height();
		register int height_half = height / 2;
		register int height_4 = height / 4;
		register int nBufLenSmpl = nBufferLength / sizeof(SAMPLE_TYPE);  //длина буфера в сэмплах * 2 канала.
		register auto pWave = reinterpret_cast<SAMPLE_TYPE *>(pBuffer);
		dc.PatBlt(x_offs, y_offs, width, height, WHITENESS);

		for (int p = 0; p < width; ++p)
		{
			register int pos = p * nBufLenSmpl / width;
			register double dyL = (double(pWave[pos++]) / FLOAT_BASE); //dy == -0.5 .. 0.5
			register double dyR = (double(pWave[pos]) / FLOAT_BASE); //dy == -0.5 .. 0.5
			register int y = y_offs + height_4;
			dc.MoveTo(x_offs, y);  //ставим точку
			dc.LineTo(x_offs, y - int(height_half * dyL));  //рисуем линию от той точки до этой
			y = y_offs + height_4 * 3;
			dc.MoveTo(x_offs, y);  //ставим точку
			dc.LineTo(x_offs, y - int(height_half * dyR));  //рисуем линию от той точки до этой
			x_offs++;
		}
	}

//#endif
}


CString CTapeManagerDlg::GetTmpFilePath()
{
	TCHAR pTmpFilePath[_MAX_PATH];
	GetTempFileName(g_Config.m_strTapePath, _T("tap"), 0, pTmpFilePath);
	return CString(pTmpFilePath);
}


UINT CTapeManagerDlg::StatusThreadWrapper(CTapeManagerDlg *pThis)
{
	pThis->StatusThread();
	return 0;
}


void CTapeManagerDlg::StatusThread()
{
	m_hStatusExit = CreateEvent(nullptr, FALSE, FALSE, nullptr);
	m_hStatusNew = CreateEvent(nullptr, FALSE, FALSE, nullptr);
	HANDLE pEvents[] = {m_hStatusExit, m_hStatusNew};
	int nEvents = sizeof(pEvents) / sizeof(pEvents[0]);

	for (;;)
	{
		register int object = WaitForMultipleObjects(nEvents, pEvents, FALSE, INFINITE);

		// если событие выхода
		if (object == WAIT_OBJECT_0)
		{
			break;    //прерываем цикл
		}

		bool bSkip = false;
		ASSERT(m_pTapeUnit); //по идее. сюда не должны попадать с m_pTapeUnit == nullptr
		// Задаём новый tape unit
		TRACE("Tread CS IN\n");
		CSingleLock lock(&m_csModifyStatus);

		do
		{
			lock.Lock();
		}
		while (!lock.IsLocked());

		if (m_pTapeUnit)
		{
			TRACE("Tread CS GetWaveLength IN\n");

			if (m_pTapeUnit->GetWaveLength() != -1)
			{
				bSkip = true;
			}

			TRACE("Tread CS GetWaveLength OUT\n");

			// Получаем информацию о нём
			if (!bSkip)
			{
				m_pTapeUnit->RetrieveInfo();
			}

			TRACE("Tread PostMessage IN\n");
			PostMessage(WM_INFO_READY, (WPARAM)bSkip);
			TRACE("Tread PostMessage OUT\n");
		}

		TRACE("Tread CS OUT\n");
		lock.Unlock();
	}
}


LRESULT CTapeManagerDlg::OnInfoReady(WPARAM wParam, LPARAM lParam)
{
	bool bSkip = (wParam != 0);
	ASSERT(m_pTapeUnit);

	if (!bSkip)
	{
		m_list.Invalidate(FALSE);
		CSingleLock lock(&m_csModifyStatus);

		do
		{
			lock.Lock();
		}
		while (!lock.IsLocked());

		if (!m_pTapeUnit->GetCRC())
		{
			CString strError;
			strError.Format(IDS_TAPEMNGR_CRC_ERROR, m_pTapeUnit->GetName());
			OutString(strError);
		}

		lock.Unlock();
	}

	if (++m_nTapePos < m_nTapeCount)
	{
		TRACE("RetriveInfo CS IN\n");
		RetrieveInfo(reinterpret_cast<CTapeUnit *>(m_list.GetItemData(m_nTapePos)));
		TRACE("RetriveInfo CS OUT\n");
	}

	return S_OK;
}


void CTapeManagerDlg::RetrieveInfo(CTapeUnit *pUnit)
{
	CSingleLock lock(&m_csModifyStatus);

	do
	{
		lock.Lock();
	}
	while (!lock.IsLocked());

	// Set new unit for porocess
	m_pTapeUnit = pUnit;

	if (m_pTapeUnit)
	{
		SetEvent(m_hStatusNew);
	}

	lock.Unlock();
}


void CTapeManagerDlg::FileListChanged(int nTapeCount)
{
	m_nTapePos = 0;
	m_nTapeCount = nTapeCount;
	auto pCombo = static_cast<CComboBox *>(GetDlgItem(IDC_TM_SAVE_AS));
	pCombo->EnableWindow(m_nTapeCount > 0);
	GetDlgItem(IDC_TM_REMOVE)->EnableWindow(m_nTapeCount > 0);
}


void CTapeManagerDlg::InsertNewUnit(CTapeUnit *pUnit)
{
	int nItem = m_list.GetItemCount();

	for (int n = COLUMN_TYPE; n <= COLUMN_PATH; n++)
	{
		LVITEM lvi;
		memset(&lvi, 0, sizeof(LVITEM));
		lvi.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM;
		lvi.iItem = nItem;
		lvi.iSubItem = n;
		lvi.pszText = LPSTR_TEXTCALLBACK;
		lvi.iImage = pUnit->GetType();
		lvi.lParam = reinterpret_cast<LPARAM >(pUnit);
		m_list.InsertItem(&lvi);
	}
}


void CTapeManagerDlg::OnTmLoad()
{
	CString strFilterManager;
	strFilterManager.LoadString(IDS_FILEFILTER_TAPEMNGR_LOAD);
	CFileDialog dlg(TRUE, nullptr, nullptr, OFN_ALLOWMULTISELECT | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER, strFilterManager);
	int MaxFile = 2562;
	auto pc = new TCHAR[MaxFile];
	ZeroMemory(pc, MaxFile);
	dlg.GetOFN().nMaxFile = MaxFile;
	dlg.GetOFN().lpstrFile = pc;
	dlg.GetOFN().lpstrInitialDir = g_Config.m_strTapePath;

	if (dlg.DoModal() == IDOK)
	{
		CString strFilePath = dlg.GetPathName();
		POSITION pos = dlg.GetStartPosition();
		int i = 0; //счётчик добавленных файлов

		do
		{
			strFilePath = dlg.GetNextPathName(pos);
			// Create tape unit from file
			auto pTapeUnit = new CTapeUnit;

			if (pTapeUnit)
			{
				if (pTapeUnit->SetFile(strFilePath))
				{
					InsertNewUnit(pTapeUnit);
				}

				i++;
			}
		}
		while (pos);

		FileListChanged(m_list.GetItemCount());
		RetrieveInfo(reinterpret_cast<CTapeUnit *>(m_list.GetItemData(m_nTapePos)));
	}

	SAFE_DELETE_ARRAY(pc);
}


void CTapeManagerDlg::OnTmBrowse()
{
	CBrowseForFolder browse(GetSafeHwnd(), nullptr, m_strCurrentFolder, _T("Выберите папку для копирования..."));

	if (!browse.SelectFolder())
	{
		return;
	}

	SetFolder(browse.GetSelectedFolder());
}


void CTapeManagerDlg::OnCancel()
{
	if (m_bRun)
	{
		waveInStop(m_hWaveIn);
	}

	CloseDevice();
	// Wait while status thread finishing
	SetEvent(m_hStatusExit);
	WaitForSingleObject(*m_pStatusThread, INFINITE);
	delete m_pStatusThread;
	DeleteUnits(false);
	POSITION pos = m_strCaptureFileNameList.GetHeadPosition();

	while (pos)
	{
		CString strFileName = m_strCaptureFileNameList.GetNext(pos);
		DeleteFile(strFileName);
	}

	CDialogEx::OnCancel();
}


void CTapeManagerDlg::OnUpdateTmRemove(CCmdUI *pCmdUI)
{
	pCmdUI->Enable(m_list.GetSelectedCount());
}


void CTapeManagerDlg::OnTmRemove()
{
	DeleteUnits(true);
	FileListChanged(m_list.GetItemCount());
}


void CTapeManagerDlg::OnUpdateSelendokTmSaveAs(CCmdUI *pCmdUI)
{
	pCmdUI->Enable(m_list.GetSelectedCount());
}


void CTapeManagerDlg::OnSelendokTmSaveAs()
{
	auto pCombo = static_cast<CComboBox *>(GetDlgItem(IDC_TM_SAVE_AS));
	int nItem = pCombo->GetCurSel();
	int type = CTapeUnit::TYPE_NONE;

	if (nItem >= 0)
	{
		DWORD_PTR nSaveAs = pCombo->GetItemData(nItem);

		switch (nSaveAs)
		{
			case COMBO_SAVE_AS_BIN:
				type = CTapeUnit::TYPE_BIN;
				break;

			case COMBO_SAVE_AS_WAV:
				type = CTapeUnit::TYPE_WAV;
				break;

			case COMBO_SAVE_AS_MSF:
				type = CTapeUnit::TYPE_MSF;
				break;
		}
	}

	pCombo->SetCurSel(0);
	pCombo->ShowDropDown(FALSE);
	SaveUnitsAs(type);
}


void CTapeManagerDlg::OnGetdispinfoTmList(NMHDR *pNMHDR, LRESULT *pResult)
{
	auto pDispInfo = reinterpret_cast<NMLVDISPINFO *>(pNMHDR);

	if (pDispInfo->item.mask & LVIF_TEXT)
	{
		//pDispInfo->item.mask |= LVIF_DI_SETITEM;
		auto pTapeUnit = reinterpret_cast<CTapeUnit *>(pDispInfo->item.lParam);

		switch (pDispInfo->item.iSubItem)
		{
			case COLUMN_NAME:
			{
				CString strName = pTapeUnit->GetName();

				if (strName.IsEmpty())
				{
					strName.LoadString(IDS_TAPEMNGR_UNKNOWN);
				}

				_tcscpy_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, strName.GetBuffer());
			}
			break;

			case COLUMN_ADDR:
			{
				CString strAddress = WordToOctString(pTapeUnit->GetAddress());

				if (pTapeUnit->GetAddress() == -1)
				{
					strAddress.LoadString(IDS_TAPEMNGR_UNKNOWN);
				}

				_tcscpy_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, strAddress.GetBuffer());
			}
			break;

			case COLUMN_LEN:
			{
				CString strLength = WordToOctString(pTapeUnit->GetLength());

				if (pTapeUnit->GetLength() == -1)
				{
					strLength.LoadString(IDS_TAPEMNGR_UNKNOWN);
				}

				_tcscpy_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, strLength.GetBuffer());
			}
			break;

			case COLUMN_TIME:
			{
				CString strTime = MsTimeToTimeString(pTapeUnit->GetTime());

				if (pTapeUnit->GetTime() == -1)
				{
					strTime.LoadString(IDS_TAPEMNGR_UNKNOWN);
				}

				_tcscpy_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, strTime.GetBuffer());
			}
			break;

			case COLUMN_WAVELEN:
			{
				CString strLength = IntToFileLengthString(pTapeUnit->GetWaveLength());

				if (pTapeUnit->GetWaveLength() == -1)
				{
					strLength.LoadString(IDS_TAPEMNGR_UNKNOWN);
				}

				_tcscpy_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, strLength.GetBuffer());
			}
			break;

			case COLUMN_CRC:
			{
				bool bCRC = pTapeUnit->GetCRC();
				CString strCRC;

				if (bCRC)
				{
					strCRC.LoadString(IDS_TAPEMNGR_CRC_OK);
				}
				else
				{
					strCRC.LoadString(IDS_TAPEMNGR_CRC_FAIL);
				}

				_tcscpy_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, strCRC.GetBuffer());
			}
			break;

			case COLUMN_PATH:
			{
				CString strPath = pTapeUnit->GetPath();
				_tcscpy_s(pDispInfo->item.pszText, pDispInfo->item.cchTextMax, strPath.GetBuffer());
			}
			break;
		}
	}

	*pResult = S_OK;
}


void CTapeManagerDlg::SetFolder(const CString &strFolder)
{
	m_strCurrentFolder = strFolder;
	g_Config.NormalizePath(m_strCurrentFolder);
	SetDlgItemText(IDC_TM_FOLDER, m_strCurrentFolder);
}


void CTapeManagerDlg::DeleteUnits(bool bSelected)
{
	CArray<int, int> items;

	if (bSelected)
	{
		POSITION pos = m_list.GetFirstSelectedItemPosition();

		while (pos)
		{
			items.Add(m_list.GetNextSelectedItem(pos));
		}
	}
	else
	{
		int nItems = m_list.GetItemCount();

		for (int n = 0; n < nItems; ++n)
		{
			items.Add(n);
		}
	}

	for (INT_PTR n = items.GetSize() - 1; n >= 0; n--)
	{
		int nItem = items[n];
		auto pTapeUnit = reinterpret_cast<CTapeUnit *>(m_list.GetItemData(nItem));

		if (pTapeUnit)
		{
			delete pTapeUnit;
		}

		m_list.DeleteItem(nItem);
	}
}


void CTapeManagerDlg::SaveUnitsAs(int type)
{
	if (type == CTapeUnit::TYPE_NONE
	        || type == CTapeUnit::TYPE_TMP)
	{
		return;
	}

	CString strSaveMessage;
	strSaveMessage.LoadString(IDS_TAPEMNGR_SAVE_MESSAGE);
	OutString(strSaveMessage);
	POSITION pos = m_list.GetFirstSelectedItemPosition();

	while (pos)
	{
		int nItem = m_list.GetNextSelectedItem(pos);
		auto pTapeUnit = reinterpret_cast<CTapeUnit *>(m_list.GetItemData(nItem));

		if (pTapeUnit->SaveAs(m_strCurrentFolder, type))
		{
			strSaveMessage.Format(IDS_TAPEMNGR_SAVE_SUCCEED, pTapeUnit->GetName());
		}
		else
		{
			strSaveMessage.Format(IDS_TAPEMNGR_SAVE_ERROR, pTapeUnit->GetName());
		}

		OutString(strSaveMessage);
	}
}


void CTapeManagerDlg::OnRclickTmList(NMHDR *pNMHDR, LRESULT *pResult)
{
	CMenu menu;
	menu.LoadMenu(IDR_TAPEMANAGER_MENU);
	CMenu *pSubMenu = menu.GetSubMenu(0);
	CPoint point;
	GetCursorPos(&point);
	pSubMenu->TrackPopupMenu(TPM_RIGHTBUTTON, point.x, point.y, this);
	*pResult = S_OK;
}


void CTapeManagerDlg::OnTmSelectall()
{
	int nCount = m_list.GetItemCount();

	for (int n = 0; n < nCount; ++n)
	{
		m_list.SetItemState(n, LVIS_SELECTED, LVIS_SELECTED);
	}
}


void CTapeManagerDlg::InitListCtrl()
{
	int nPixelW = GetDeviceCaps(GetDC()->GetSafeHdc(), LOGPIXELSX);
	CString strColumn;
	m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT);
	strColumn.LoadString(IDS_TAPEMNGR_TYPE);
	m_list.InsertColumn(COLUMN_TYPE, strColumn, LVCFMT_LEFT, MulDiv(20, nPixelW, 96));
	strColumn.LoadString(IDS_TAPEMNGR_NAME);
	m_list.InsertColumn(COLUMN_NAME, strColumn, LVCFMT_LEFT, MulDiv(100, nPixelW, 96));
	strColumn.LoadString(IDS_TAPEMNGR_ADDR);
	m_list.InsertColumn(COLUMN_ADDR, strColumn, LVCFMT_RIGHT, MulDiv(60, nPixelW, 96));
	strColumn.LoadString(IDS_TAPEMNGR_LENGTH);
	m_list.InsertColumn(COLUMN_LEN, strColumn, LVCFMT_RIGHT, MulDiv(60, nPixelW, 96));
	strColumn.LoadString(IDS_TAPEMNGR_TIME);
	m_list.InsertColumn(COLUMN_TIME, strColumn, LVCFMT_RIGHT, MulDiv(40, nPixelW, 96));
	strColumn.LoadString(IDS_TAPEMNGR_WAVELENGTH);
	m_list.InsertColumn(COLUMN_WAVELEN, strColumn, LVCFMT_RIGHT, MulDiv(80, nPixelW, 96));
	strColumn.LoadString(IDS_TAPEMNGR_CRC);
	m_list.InsertColumn(COLUMN_CRC, strColumn, LVCFMT_LEFT, MulDiv(40, nPixelW, 96));
	strColumn.LoadString(IDS_TAPEMNGR_PATH);
	m_list.InsertColumn(COLUMN_PATH, strColumn, LVCFMT_LEFT, MulDiv(300, nPixelW, 96));
	//m_list.SetColumnWidth (COLUMN_PATH, LVSCW_AUTOSIZE_USEHEADER);
	m_imgList.Create(16, 16, ILC_COLOR, 3, 1);
	m_imgList.Add(::LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME)));
	m_imgList.Add(::LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_WAVE)));
	m_imgList.Add(::LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_TAPE)));
	m_imgList.Add(::LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_TAPE)));
	m_list.SetImageList(&m_imgList, LVSIL_SMALL);
}


void CTapeManagerDlg::InitComboCtrl()
{
	CString strItem;
	auto pCombo = static_cast<CComboBox *>(GetDlgItem(IDC_TM_SAVE_AS));
	strItem.LoadString(IDS_TAPEMNGR_SAVE_AS);
	pCombo->InsertString(0, strItem);
	pCombo->SetItemData(0, COMBO_NONE);
	strItem.LoadString(IDS_FILEFILTER_TAP_ONLY);
	strItem = strItem.Left(strItem.Find(_T('|')));
	pCombo->InsertString(1, strItem);
	pCombo->SetItemData(1, COMBO_SAVE_AS_MSF);
	strItem.LoadString(IDS_FILEFILTER_WAV);
	strItem = strItem.Left(strItem.Find(_T('|')));
	pCombo->InsertString(2, strItem);
	pCombo->SetItemData(2, COMBO_SAVE_AS_WAV);
	strItem.LoadString(IDS_FILEFILTER_BIN);
	strItem = strItem.Left(strItem.Find(_T('|')));
	pCombo->InsertString(3, strItem);
	pCombo->SetItemData(3, COMBO_SAVE_AS_BIN);
	CDC    *pDC = pCombo->GetDC();
	CFont  *pFont = pCombo->GetFont();
	CFont  *pOldFont = pDC->SelectObject(pFont);
	TEXTMETRIC tm;
	pDC->GetTextMetrics(&tm);
	int     width = 0;
	int     nCount = pCombo->GetCount();

	for (int i = 0; i < nCount; ++i)
	{
		pCombo->GetLBText(i, strItem);
		CSize sz = pDC->GetTextExtent(strItem);
		sz.cx += tm.tmAveCharWidth;

		if (sz.cx > width)
		{
			width = sz.cx;
		}
	}

	pDC->SelectObject(pOldFont);
	pCombo->ReleaseDC(pDC);
	width += /*GetSystemMetrics(SM_CXVSCROLL) +*/ 2 * GetSystemMetrics(SM_CXEDGE);
	pCombo->SetDroppedWidth(width);
	pCombo->SetCurSel(0);
	pCombo->EnableWindow(FALSE);
}


//сперва найдём и заполним комбобокс устройствами захвата
//выход: 0 - нет ни одного устройства
UINT CTapeManagerDlg::FillDevices()
{
	auto pBox = static_cast<CComboBox *>(GetDlgItem(IDC_TM_DEVICES));
	WAVEINCAPS stWIC = { 0 };
	CString strItem;
	pBox->ResetContent();
	UINT nDevices = waveInGetNumDevs();

	if (nDevices)
	{
		strItem.LoadString(IDS_TAPEMNGR_INPUT_DEVS);
		pBox->InsertString(0, strItem);
		pBox->SetItemData(0, WAVE_MAPPER);

		for (UINT i = 0; i < nDevices; ++i)
		{
			ZeroMemory(&stWIC, sizeof(WAVEINCAPS));
			register MMRESULT mRes = waveInGetDevCaps(i, &stWIC, sizeof(WAVEINCAPS));

			if ((mRes == S_OK) && (WAVE_FORMAT_4S16 == (stWIC.dwFormats & WAVE_FORMAT_4S16)))
			{
				pBox->InsertString(i + 1, stWIC.szPname);
				pBox->SetItemData(i + 1, i);
			}
		}
	}
	else
	{
		strItem.LoadString(IDS_TAPEMNGR_NO_INPUTDEVS);
		pBox->InsertString(0, strItem);
		pBox->SetItemData(0, -1);
	}

	if (pBox->GetCount())
	{
		pBox->SetCurSel(0);
	}

	return nDevices;
}

bool CTapeManagerDlg::OpenDevice()
{
	auto pDevices = static_cast<CComboBox *>(GetDlgItem(IDC_TM_DEVICES));
	m_wfx.cbSize            = 0; //extra size. sizeof (WAVEFORMATEX);
	m_wfx.wFormatTag        = WAVE_FORMAT_PCM;
	m_wfx.nSamplesPerSec    = g_Config.m_nSoundSampleRate;
	m_wfx.wBitsPerSample    = BUFFER_BPS;
	m_wfx.nChannels         = BUFFER_CHANNELS;
	m_wfx.nBlockAlign       = (m_wfx.wBitsPerSample >> 3) * m_wfx.nChannels;
	m_wfx.nAvgBytesPerSec   = m_wfx.nSamplesPerSec * m_wfx.nBlockAlign;
	auto uDevID = (UINT)pDevices->GetItemData(pDevices->GetCurSel());
	MMRESULT mRes = waveInOpen(&m_hWaveIn, uDevID, &m_wfx,
	                           reinterpret_cast<DWORD_PTR >(waveInProc),
	                           reinterpret_cast<DWORD_PTR >(this), CALLBACK_FUNCTION);

	if (mRes == MMSYSERR_NOERROR)
	{
		PrepareBuffers();
		return true;
	}

	return false;
}

void CTapeManagerDlg::CloseDevice()
{
	if (m_hWaveIn)
	{
		UnPrepareBuffers();
		MMRESULT mRes = waveInClose(m_hWaveIn);
		m_hWaveIn = nullptr;
	}
}

void CTapeManagerDlg::RemoveBuffers()
{
	// delete all buffers memory
	POSITION pos = m_captureBuffers.GetHeadPosition();

	while (pos)
	{
		BYTE *pBuf = m_captureBuffers.GetNext(pos);
		SAFE_DELETE_ARRAY(pBuf);
	}
}

void CTapeManagerDlg::InitBuffers()
{
	RemoveBuffers();

	for (int n = 0; n < CAPTURE_BUFFERS_NUM; ++n)
	{
		auto pBuff = new BYTE[GetBufferLength()];
		memset(pBuff, 0, GetBufferLength());
		m_captureBuffers.AddTail(pBuff);
	}
}

void CTapeManagerDlg::PrepareBuffers()
{
	for (int i = 0; i < CAPTURE_BUFFERS_NUM; ++i)
	{
		ZeroMemory(&m_WHDR[i], sizeof(WAVEHDR));
		m_WHDR[i].dwBufferLength = GetBufferLength();
		m_WHDR[i].lpData = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, m_WHDR[i].dwBufferLength);
		m_WHDR[i].dwUser = i;
		MMRESULT mRes = waveInPrepareHeader(m_hWaveIn, &m_WHDR[i], sizeof(WAVEHDR));
		mRes = waveInAddBuffer(m_hWaveIn, &m_WHDR[i], sizeof(WAVEHDR));
	}

	InitBuffers();
}

void CTapeManagerDlg::UnPrepareBuffers()
{
	if (m_hWaveIn)
	{
		MMRESULT mRes = waveInStop(m_hWaveIn);

		for (auto &i : m_WHDR)
		{
			if (i.lpData)
			{
				mRes = waveInUnprepareHeader(m_hWaveIn, &i, sizeof(WAVEHDR));
				HeapFree(GetProcessHeap(), 0, i.lpData);
				ZeroMemory(&i, sizeof(WAVEHDR));
			}
		}
	}

	RemoveBuffers();
}

void CALLBACK CTapeManagerDlg::waveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
	if (uMsg == WIM_DATA)
	{
		auto pDlg = reinterpret_cast<CTapeManagerDlg *>(dwInstance);
		pDlg->ProcessHeader(reinterpret_cast<WAVEHDR *>(dwParam1));
	}
}

void CTapeManagerDlg::ProcessHeader(WAVEHDR *pHdr)
{
	TRACE("%d", pHdr->dwUser);

	if (WHDR_DONE == (WHDR_DONE & pHdr->dwFlags))
	{
		CSingleLock lock(&m_csModifyBuffer, TRUE);
		BYTE *pTailBuff = m_captureBuffers.RemoveHead();
		memcpy(pTailBuff, pHdr->lpData, pHdr->dwBytesRecorded);

		if (pHdr->dwBytesRecorded < GetBufferLength())
		{
			DWORD n = GetBufferLength() - pHdr->dwBytesRecorded;
			memset(pTailBuff + pHdr->dwBytesRecorded, 0, n);
		}

		m_captureBuffers.AddTail(pTailBuff);
		m_CurrentCaptureBuffer = pTailBuff;
		lock.Unlock();
		MMRESULT mRes = waveInPrepareHeader(m_hWaveIn, pHdr, sizeof(WAVEHDR));
		mRes = waveInAddBuffer(m_hWaveIn, pHdr, sizeof(WAVEHDR));
		//SetEvent (m_hBufferReadyEvent);
		SendMessage(WM_BUFFER_READY);
	}
}


