/*
 * ExternalSamples.cpp
 * -------------------
 * Purpose: Dialogs for locating missing external samples and handling modified samples
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include "Moddoc.h"
#include "ExternalSamples.h"
#include "FileDialog.h"
#include "FolderScanner.h"
#include "TrackerSettings.h"
#include "Reporting.h"
#include "resource.h"

OPENMPT_NAMESPACE_BEGIN

BEGIN_MESSAGE_MAP(MissingExternalSamplesDlg, ResizableDialog)
	//{{AFX_MSG_MAP(ExternalSamplesDlg)
	ON_NOTIFY(NM_DBLCLK,	IDC_LIST1,	&MissingExternalSamplesDlg::OnSetPath)
	ON_COMMAND(IDC_BUTTON1,				&MissingExternalSamplesDlg::OnScanFolder)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


void MissingExternalSamplesDlg::DoDataExchange(CDataExchange *pDX)
{
	ResizableDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST1,		m_List);
}


MissingExternalSamplesDlg::MissingExternalSamplesDlg(CModDoc &modDoc, CWnd *parent)
    : ResizableDialog(IDD_MISSINGSAMPLES, parent)
    , m_modDoc(modDoc)
    , m_sndFile(modDoc.GetSoundFile())
{
}


BOOL MissingExternalSamplesDlg::OnInitDialog()
{
	ResizableDialog::OnInitDialog();

	// Initialize table
	const CListCtrlEx::Header headers[] =
	{
		{ _T("Sample"),				128, LVCFMT_LEFT },
		{ _T("External Filename"),	308, LVCFMT_LEFT },
	};
	m_List.SetHeaders(headers);
	m_List.SetExtendedStyle(m_List.GetExtendedStyle() | LVS_EX_FULLROWSELECT);

	GenerateList();
	SetWindowText((_T("Missing External Samples - ") + m_modDoc.GetPathNameMpt().GetFullFileName().AsNative()).c_str());

	return TRUE;
}


void MissingExternalSamplesDlg::GenerateList()
{
	m_List.SetRedraw(FALSE);
	m_List.DeleteAllItems();
	CString s;
	for(SAMPLEINDEX smp = 1; smp <= m_sndFile.GetNumSamples(); smp++)
	{
		if(m_sndFile.IsExternalSampleMissing(smp))
		{
			s.Format(_T("%02u: "), smp);
			s += mpt::ToCString(m_sndFile.GetCharsetInternal(), m_sndFile.GetSampleName(smp));
			int insertAt = m_List.InsertItem(m_List.GetItemCount(), s);
			if(insertAt == -1)
				continue;
			m_List.SetItemText(insertAt, 1, m_sndFile.GetSamplePath(smp).AsNative().c_str());
			m_List.SetItemData(insertAt, smp);
		}
	}
	m_List.SetRedraw(TRUE);

	// Yay, we managed to find all samples!
	if(!m_List.GetItemCount())
		OnOK();
}


void MissingExternalSamplesDlg::OnSetPath(NMHDR *, LRESULT *)
{
	const int item = m_List.GetSelectionMark();
	if(item == -1) return;
	const SAMPLEINDEX smp = static_cast<SAMPLEINDEX>(m_List.GetItemData(item));

	const mpt::PathString path = m_modDoc.GetSoundFile().GetSamplePath(smp);
	FileDialog dlg = OpenFileDialog()
		.ExtensionFilter("All Samples|*.wav;*.flac|All files(*.*)|*.*||");  // Only show samples that we actually can save as well.
	if(TrackerSettings::Instance().previewInFileDialogs)
		dlg.EnableAudioPreview();
	if(path.empty())
		dlg.WorkingDirectory(TrackerSettings::Instance().PathSamples.GetWorkingDir());
	else
		dlg.DefaultFilename(path);
	if(!dlg.Show()) return;
	TrackerSettings::Instance().PathSamples.SetWorkingDir(dlg.GetWorkingDirectory());

	SetSample(smp, dlg.GetFirstFile());
	m_modDoc.UpdateAllViews(nullptr, SampleHint(smp).Info().Names().Data());
	GenerateList();
}


void MissingExternalSamplesDlg::OnScanFolder()
{
	if(m_isScanning)
	{
		m_isScanning = false;
		return;
	}

	BrowseForFolder dlg(TrackerSettings::Instance().PathSamples.GetWorkingDir(), _T("Select a folder to search for missing samples..."));
	if(dlg.Show())
	{
		TrackerSettings::Instance().PathSamples.SetWorkingDir(dlg.GetDirectory());

		FolderScanner scan(dlg.GetDirectory(), FolderScanner::kOnlyFiles | FolderScanner::kFindInSubDirectories);
		mpt::PathString fileName;

		m_isScanning = true;
		SetDlgItemText(IDC_BUTTON1, _T("&Cancel"));
		GetDlgItem(IDOK)->EnableWindow(FALSE);
		BeginWaitCursor();

		DWORD64 lastTick = Util::GetTickCount64();
		int foundFiles = 0;

		bool anyMissing = true;
		while(scan.Next(fileName) && m_isScanning && anyMissing)
		{
			anyMissing = false;
			for(SAMPLEINDEX smp = 1; smp <= m_sndFile.GetNumSamples(); smp++)
			{
				if(m_sndFile.IsExternalSampleMissing(smp))
				{
					if(!mpt::PathString::CompareNoCase(m_sndFile.GetSamplePath(smp).GetFullFileName(), fileName.GetFullFileName()))
					{
						if(SetSample(smp, fileName))
						{
							foundFiles++;
						}
					} else
					{
						anyMissing = true;
					}
				}
			}

			const DWORD64 tick = Util::GetTickCount64();
			if(tick < lastTick || tick > lastTick + 100)
			{
				lastTick = tick;
				SetDlgItemText(IDC_STATIC1, fileName.AsNative().c_str());
				MSG msg;
				while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
				{
					::TranslateMessage(&msg);
					::DispatchMessage(&msg);
				}
			}
		}
		EndWaitCursor();
		GetDlgItem(IDOK)->EnableWindow(TRUE);
		SetDlgItemText(IDC_BUTTON1, _T("&Scan Folder..."));

		m_modDoc.UpdateAllViews(nullptr, SampleHint().Info().Data().Names());

		if(foundFiles)
		{
			SetDlgItemText(IDC_STATIC1, MPT_CFORMAT("{} sample paths were relocated.")(foundFiles));
		} else
		{
			SetDlgItemText(IDC_STATIC1, _T("No matching sample names found."));
		}
		m_isScanning = false;
		GenerateList();
	}

}


bool MissingExternalSamplesDlg::SetSample(SAMPLEINDEX smp, const mpt::PathString &fileName)
{
	m_modDoc.GetSampleUndo().PrepareUndo(smp, sundo_replace, "Replace");
	const mpt::PathString oldPath = m_sndFile.GetSamplePath(smp);
	if(!m_sndFile.LoadExternalSample(smp, fileName))
	{
		Reporting::Information(_T("Unable to load sample:\n") + fileName.AsNative());
		m_modDoc.GetSampleUndo().RemoveLastUndoStep(smp);
		return false;
	} else
	{
		// Maybe we just put the file into its regular place, in which case the module has not really been modified.
		if(oldPath != fileName)
		{
			m_modDoc.SetModified();
		}
		return true;
	}
}


BEGIN_MESSAGE_MAP(ModifiedExternalSamplesDlg, ResizableDialog)
	//{{AFX_MSG_MAP(ExternalSamplesDlg)
	ON_COMMAND(IDC_SAVE,                  &ModifiedExternalSamplesDlg::OnSaveSelected)
	ON_COMMAND(IDC_CHECK1,                &ModifiedExternalSamplesDlg::OnCheckAll)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &ModifiedExternalSamplesDlg::OnSelectionChanged)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


void ModifiedExternalSamplesDlg::DoDataExchange(CDataExchange *pDX)
{
	ResizableDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST1, m_List);
}


ModifiedExternalSamplesDlg::ModifiedExternalSamplesDlg(CModDoc &modDoc, CWnd *parent)
    : ResizableDialog(IDD_MODIFIEDSAMPLES, parent)
    , m_modDoc(modDoc)
    , m_sndFile(modDoc.GetSoundFile())
{
}


BOOL ModifiedExternalSamplesDlg::OnInitDialog()
{
	ResizableDialog::OnInitDialog();

	// Initialize table
	const CListCtrlEx::Header headers[] =
	{
		{_T("Sample"),            120, LVCFMT_LEFT},
		{_T("Status"),            54,  LVCFMT_LEFT},
		{_T("External Filename"), 262, LVCFMT_LEFT},
	};
	m_List.SetHeaders(headers);
	m_List.SetExtendedStyle(m_List.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_CHECKBOXES);

	GenerateList();
	SetWindowText((_T("Modified External Samples - ") + m_modDoc.GetPathNameMpt().GetFullFileName().AsNative()).c_str());

	return TRUE;
}


void ModifiedExternalSamplesDlg::GenerateList()
{
	m_List.SetRedraw(FALSE);
	m_List.DeleteAllItems();
	CString s;
	mpt::winstring status;
	for(SAMPLEINDEX smp = 1; smp <= m_sndFile.GetNumSamples(); smp++)
	{
		if(!m_sndFile.GetSample(smp).uFlags[SMP_KEEPONDISK])
			continue;

		if(m_sndFile.GetSample(smp).uFlags[SMP_MODIFIED])
			status = _T("modified");
		else if(!m_sndFile.GetSamplePath(smp).IsFile())
			status = _T("missing");
		else
			continue;

		s.Format(_T("%02u: "), smp);
		s += mpt::ToCString(m_sndFile.GetCharsetInternal(), m_sndFile.GetSampleName(smp));
		int insertAt = m_List.InsertItem(m_List.GetItemCount(), s);
		if(insertAt == -1)
			continue;
		m_List.SetItemText(insertAt, 1, status.c_str());
		m_List.SetItemText(insertAt, 2, m_sndFile.GetSamplePath(smp).AsNative().c_str());
		m_List.SetCheck(insertAt, TRUE);
		m_List.SetItemData(insertAt, smp);
	}
	m_List.SetRedraw(TRUE);

	CheckDlgButton(IDC_CHECK1, BST_CHECKED);
	OnSelectionChanged(nullptr, nullptr);

	// Nothing modified?
	if(!m_List.GetItemCount())
		OnOK();
}


void ModifiedExternalSamplesDlg::OnCheckAll()
{
	const BOOL check = IsDlgButtonChecked(IDC_CHECK1) ? TRUE : FALSE;
	const int count = m_List.GetItemCount();
	for(int i = 0; i < count; i++)
	{
		m_List.SetCheck(i, check);
	}
}


void ModifiedExternalSamplesDlg::OnSelectionChanged(NMHDR *, LRESULT *)
{
	int numChecked = 0;
	const int count = m_List.GetItemCount();
	for(int i = 0; i < count; i++)
	{
		if(m_List.GetCheck(i))
			numChecked++;
	}
	const TCHAR *embedText, *saveText;
	if(numChecked == count)
	{
		embedText = _T("&Embed All");
		saveText = _T("&Save All");
	} else if(!numChecked)
	{
		embedText = _T("&Embed None");
		saveText = _T("&Save None");
	} else
	{
		embedText = _T("&Embed Selected");
		saveText = _T("&Save Selected");
	}

	GetDlgItem(IDOK)->SetWindowText(embedText);
	GetDlgItem(IDC_SAVE)->SetWindowText(saveText);
}


void ModifiedExternalSamplesDlg::Execute(bool doSave)
{
	ScopedLogCapturer log(m_modDoc, _T("Modified Samples"), this);

	bool ok = true;
	const int count = m_List.GetItemCount();
	for(int i = 0; i < count; i++)
	{
		if(!m_List.GetCheck(i))
			continue;

		SAMPLEINDEX smp = static_cast<SAMPLEINDEX>(m_List.GetItemData(i));
		if(doSave)
		{
			ok &= m_modDoc.SaveSample(smp);
		} else
		{
			m_sndFile.GetSample(smp).uFlags.reset(SMP_KEEPONDISK);
			m_modDoc.SetModified();
		}
	}
	
	if(ok)
		ResizableDialog::OnOK();
	else
		ResizableDialog::OnCancel();
}


OPENMPT_NAMESPACE_END