// TapeUnit.cpp: implementation of the CTapeUnit class.
//

#include "pch.h"
#include "BKMessageBox.h"
#include "Config.h"
#include "MSFManager.h"
#include "resource.h"
#include "Tape.h"
#include "TapeUnit.h"


/////////////////////////////////////////////////////////////////////////////
// Construction/Destruction


CTapeUnit::CTapeUnit()
= default;

CTapeUnit::CTapeUnit(CTapeUnit &tapeUnit)
{
	m_nType = tapeUnit.m_nType;
	m_nAddress = tapeUnit.m_nAddress;
	m_nLength = tapeUnit.m_nLength;
	m_nMSTime = tapeUnit.m_nMSTime;
	m_nWaveLength = tapeUnit.m_nWaveLength;
	m_bCRC = tapeUnit.m_bCRC;
	m_strName = tapeUnit.m_strName;
	m_pathFileName = tapeUnit.m_pathFileName;
}

CTapeUnit::~CTapeUnit()
    = default;


CTapeUnit &CTapeUnit::operator= (CTapeUnit &tapeUnit)
    = default;


bool CTapeUnit::SetFile(const fs::path &pathFileName)
{
	bool bRet = false;

	if (fs::exists(pathFileName)) // если файл существует.
	{
		// то определим тип файла
		m_strName.Empty();
		m_bCRC = false;

		if (IsFileWave(pathFileName))
		{
			// это PCM Wave
			m_nType = TAPETYPE::TYPE_WAV;
			bRet = true;
		}
		else if (IsFileMSF(pathFileName))
		{
			// это MSF, т.е. tap файл
			m_nType = TAPETYPE::TYPE_MSF;
			bRet = true;
		}
		else if (IsFileBin(pathFileName))
		{
			// это BIN, обычный БК бин
			m_nType = TAPETYPE::TYPE_BIN;
			m_bCRC = true;
			bRet = true;
		}

		if (bRet) // если формат определили
		{
			m_nAddress = UNKNOWN;
			m_nLength = UNKNOWN;
			m_nMSTime = UNKNOWN;
			m_nWaveLength = UNKNOWN;
			m_pathFileName = pathFileName;
		}
	}

	return bRet;
}


bool CTapeUnit::SetTmpFile(const fs::path &pathFileName)
{
	if (fs::exists(pathFileName)) // если файл существует.
	{
		// то зададим тип файла
		m_nType = TAPETYPE::TYPE_TMP;     // файл типа TMP, для захвата Wave
		m_nAddress = UNKNOWN;
		m_nLength = UNKNOWN;
		m_nMSTime = UNKNOWN;
		m_nWaveLength = UNKNOWN;
		m_pathFileName = pathFileName;
		m_bCRC = false;
		m_strName.Empty();
		return true;
	}

	return false; // файла нет - делать нечего
}


bool CTapeUnit::IsFileWave(const fs::path &pathFileName) const
{
	CFile waveFile;

	if (waveFile.Open(pathFileName.c_str(), CFile::modeRead))
	{
		WaveHeader waveHeader{};

		if (waveFile.Read(&waveHeader, sizeof(WaveHeader)) == sizeof(WaveHeader))
		{
			if (waveHeader.riffTag == RIFF_TAG && waveHeader.fmtTag == FMT_TAG)
			{
				WAVEFORMATEX wfx;

				if (waveFile.Read(&wfx, waveHeader.fmtSize) == waveHeader.fmtSize)
				{
					wfx.cbSize = 0;

					if (/* wfx.nAvgBytesPerSec != 44100 || wfx.nBlockAlign != 1
                            || wfx.nChannels != 1 || wfx.nSamplesPerSec != 44100
                            || wfx.wBitsPerSample != 8 || */ wfx.wFormatTag != WAVE_FORMAT_PCM)
					{
						return false;
					}

					DataHeader dataHeader{};

					for (;;)
					{
						if (waveFile.Read(&dataHeader, sizeof(DataHeader)) != sizeof(DataHeader))
						{
							return false;
						}

						if (dataHeader.dataTag == DATA_TAG)
						{
							break;
						}

						waveFile.Seek(dataHeader.dataSize, CFile::SeekPosition::current);
					}

					return true;
				}
			}
		}
	}

	return false;
}


bool CTapeUnit::IsFileMSF(const fs::path &pathFileName) const
{
	CMSFManager msf;

	if (msf.CheckFile(pathFileName, true))
	{
		if (msf.OpenFile(pathFileName, true))
		{
			MSF_BLOCK_INFO bi;

			if (msf.FindBlock(MSF_BLOCKTYPE::WAVE, -1, &bi))
			{
				return true;
			}
		}
	}

	return false;
}


bool CTapeUnit::IsFileBin(const fs::path &pathFileName) const
{
	Global::BinFileParameters_t bfp{ 0, 0, 0, false };
	return Global::CheckBinFile(pathFileName, bfp);
}


bool CTapeUnit::RetrieveInfo()
{
	bool bRet = false;
	const auto pTape = std::make_unique<CTape>();

	if (pTape)
	{
		TAPE_FILE_INFO tfi{};

		switch (m_nType)
		{
			case TAPETYPE::TYPE_BIN:
				// Open binary file
				// здесь результат tfi не используется
				bRet = pTape->LoadBinFile(m_pathFileName, &tfi);
				break;

			case TAPETYPE::TYPE_WAV:
				// Open wave file
				bRet = pTape->LoadWaveFile(m_pathFileName);
				break;

			case TAPETYPE::TYPE_MSF:
				// Open MSF file
				bRet = pTape->LoadMSFFile(m_pathFileName);
				break;

			case TAPETYPE::TYPE_TMP:
				// Open TMP file
				bRet = pTape->LoadTmpFile(m_pathFileName);
				break;
		}

		if (bRet)
		{
			pTape->GetWaveFile(&tfi, false);

			if (tfi.marker2 == -1) // файл повреждён как-то
			{
				bRet = false; // у файла нет конца
			}
			else
			{
				// да - получим полную информацию о нём
				m_nAddress = tfi.address;
				m_nLength = tfi.length;
				m_nWaveLength = static_cast<DWORD>(pTape->GetWaveLength());
				m_nMSTime = int(float(m_nWaveLength) * 1000.0 / float(pTape->GetWorkingSSR()));
				m_bCRC = (tfi.crc == pTape->CalcCRC(&tfi));
				m_strName = Global::BKToUNICODE(tfi.name);
				m_strName.Trim();
			}
		}
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}

	return bRet;
}

// Вход: pathFilePath - путь куда сохранять файл
//		target_type - целевой формат, в котором сохранять
bool CTapeUnit::SaveAs(const fs::path &pathFilePath, TAPETYPE target_type)
{
	Global::SetSafeName(m_strName);
	fs::path pathNewFileName = pathFilePath / m_strName.GetString(); // вот целевое имя файла с путём по умолчанию
	std::wstring strTypeExt; // целевое расширение

	// Добавим расширение для нового типа
	switch (target_type)
	{
		case TAPETYPE::TYPE_BIN:
		{
			// Добавим расширение .bin
			strTypeExt = CString(MAKEINTRESOURCE(IDS_FILEEXT_BINARY)).GetString();
			const CString strExt = pathNewFileName.extension().c_str(); // это своё расширение целевого файла
			// если нету расширения
			if (strExt.IsEmpty())
			{
				pathNewFileName.replace_extension(strTypeExt); // то просто добавим
			}
			else // если оно есть, но не .bin, то добавляем
			{
				// и тут бейсиковский bin всё портит.
				if (strExt.CollateNoCase(strTypeExt.c_str())) // если расширение не бин,
				{
					pathNewFileName += strTypeExt; // то добавим расширение бин
				}
				else // если расширение бин, проверим, не бейсик ли это.
				{
					// если расширение начинается ровно с 6-й позиции
					if (m_strName.GetAt(6) == _T('.') && 
						m_strName.Mid(6, 4).CollateNoCase(strTypeExt.c_str()) == 0)
					{
						// значит это скорее всего бейсик, и надо тоже добавить расширение
						pathNewFileName += strTypeExt;
					}
					// в остальных случаях оставляем как есть.
				}
			}
		}
		break;

		case TAPETYPE::TYPE_WAV:
		{
			// Добавим расширение .wav
			strTypeExt = CString(MAKEINTRESOURCE(IDS_FILEEXT_WAVE)).GetString();
			pathNewFileName += strTypeExt;
		}
		break;

		case TAPETYPE::TYPE_MSF:
		{
			// Добавим расширение .tap
			strTypeExt = CString(MAKEINTRESOURCE(IDS_FILEEXT_TAPE)).GetString();
			pathNewFileName += strTypeExt;
		}
		break;
	}

	// Проверим, а не существует ли уже такой файл?
	constexpr auto MAX_COUNT = 10000;
	int nNum = 0;
	const fs::path pathName = pathNewFileName.stem();  // целевое имя файла без расширения

	while (fs::exists(pathNewFileName) && nNum < MAX_COUNT)
	{
		// сделаем новое имя
		nNum++;
		auto strNameN = pathName.wstring() + _T("(") + std::to_wstring(nNum) + _T(")") + strTypeExt;
		pathNewFileName.replace_filename(strNameN);
	} // будем проверять имена с цифрой до тех пор, пока не найдём

	if (nNum >= MAX_COUNT) // если вышли по такому условию, то явно что-то не так
	{
		// значит тупо перепишем файл с основным именем.
		pathNewFileName = pathFilePath / pathName;
		pathNewFileName += strTypeExt;
	}

	const auto pTape = std::make_unique<CTape>();
	bool bRet = true;

	if (pTape)
	{
		switch (m_nType)
		{
			case TAPETYPE::TYPE_BIN: // Если текущий тип файла - bin
				if (target_type == TAPETYPE::TYPE_BIN)
				{
					// если и новый тоже bin - просто копируем файл
					bRet = fs::copy_file(m_pathFileName, pathNewFileName, fs::copy_options::overwrite_existing);
					return bRet;
				}
				else
				{
					// сконвертируем bin в wave
					TAPE_FILE_INFO tfi{};
					bRet = pTape->LoadBinFile(m_pathFileName, &tfi);
				}

				break;

			case TAPETYPE::TYPE_WAV: // Если текущий тип файла - wav
				if (target_type == TAPETYPE::TYPE_WAV)
				{
					// если и новый тоже wav - просто копируем файл
					bRet = fs::copy_file(m_pathFileName, pathNewFileName, fs::copy_options::overwrite_existing);
					return bRet;
				}
				else
				{
					// загрузим wav файл
					bRet = pTape->LoadWaveFile(m_pathFileName);
				}

				break;

			case TAPETYPE::TYPE_MSF: // Если текущий тип файла - tap
				if (target_type == TAPETYPE::TYPE_MSF)
				{
					// если и новый тоже tap - просто копируем файл
					bRet = fs::copy_file(m_pathFileName, pathNewFileName, fs::copy_options::overwrite_existing);
					return bRet;
				}
				else
				{
					// сконвертируем tap в wave
					bRet = pTape->LoadMSFFile(m_pathFileName);
				}

				break;

			case TAPETYPE::TYPE_TMP: // если текущий файл - временный
				bRet = pTape->LoadTmpFile(m_pathFileName);
				break;

			default: // Какой-то неизвестный тип.
				ASSERT(false);
				bRet = false;
		}

		if (bRet)
		{
			// конвертируем в заданный формат
			switch (target_type)
			{
				case TAPETYPE::TYPE_BIN:
					// сохраним загруженный/сконвертированный wav как bin
				{
					TAPE_FILE_INFO tfi{};
					bRet = pTape->GetWaveFile(&tfi, false);

					if (bRet)
					{
						bRet = pTape->SaveBinFile(pathNewFileName, &tfi);
					}
				}
				break;

				case TAPETYPE::TYPE_WAV:
					// сохраним загруженный/сконвертированный wav
					bRet = pTape->SaveWaveFile(pathNewFileName);
					break;

				case TAPETYPE::TYPE_MSF:
					// сохраним загруженный/сконвертированный wav как tap
					bRet = pTape->SaveMSFFile(pathNewFileName);
					break;

				default: // Какой-то неизвестный тип.
					ASSERT(false);
			}
		}
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}

	return bRet;
}
