// TapeManagerDlg.cpp : implementation file
//

#include "stdafx.h"
#include "resource.h"
#include "TapeManagerDlg.h"
#include "Config.h"
#include "BKErrMsgSys.h"
#include "BrowseForFolder.h"
#include "TapeUnit.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 = DEFAULT_SOUND_SAMPLE_RATE / 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;

		if (pTapeUnit)
		{
			pTapeUnit->SetTmpFile(m_strCaptureFileName);
			InsertNewUnit(pTapeUnit);
		}
		else
		{
			CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
		}

		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_TAPE_LOAD);
	CFileDialog dlg(TRUE, nullptr, nullptr, OFN_ALLOWMULTISELECT | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER, strFilterManager);
	int MaxFile = 2562;
	auto pc = new TCHAR[MaxFile];

	if (pc)
	{
		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++;
				}
				else
				{
					CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
				}
			}
			while (pos);

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

		SAFE_DELETE_ARRAY(pc);
	}
	else
	{
		CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
	}
}


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;

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

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

			case COLUMN_LEN:
			{
				CString strLength;

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

				_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    = DEFAULT_SOUND_SAMPLE_RATE;
	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()];

		if (pBuff)
		{
			memset(pBuff, 0, GetBufferLength());
			m_captureBuffers.AddTail(pBuff);
		}
		else
		{
			CBKMessageBox(g_pstrErrors[BK_ERROR_NOTENMEMR], MB_OK);
		}
	}
}

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\n", 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);
	}
}


