/*
 * CleanupSong.cpp
 * ---------------
 * Purpose: Dialog for cleaning up modules (rearranging, removing unused items).
 * Notes  : (currently none)
 * Authors: Olivier Lapicque
 *          OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include "Moddoc.h"
#include "Mainfrm.h"
#include "CleanupSong.h"
#include "../common/mptStringBuffer.h"
#include "../soundlib/mod_specifications.h"
#include "../soundlib/modsmp_ctrl.h"
#include "../tracklib/SampleEdit.h"


OPENMPT_NAMESPACE_BEGIN


// Default checkbox state
bool CModCleanupDlg::m_CheckBoxes[kMaxCleanupOptions] =
{
	true,	false,	true,	true,	// patterns
	false,	false,					// orders
	true,	false,	false,	true,	// samples
	true,	false,					// instruments
	true,	false,					// plugins
	false,	true,					// misc
};

// Checkbox -> Control ID LUT
WORD const CModCleanupDlg::m_CleanupIDtoDlgID[kMaxCleanupOptions] =
{
	// patterns
	IDC_CHK_CLEANUP_PATTERNS,		IDC_CHK_REMOVE_PATTERNS,
	IDC_CHK_REARRANGE_PATTERNS,		IDC_CHK_REMOVE_DUPLICATES,
	// orders
	IDC_CHK_MERGE_SEQUENCES,		IDC_CHK_REMOVE_ORDERS,
	// samples
	IDC_CHK_CLEANUP_SAMPLES,		IDC_CHK_REMOVE_SAMPLES,
	IDC_CHK_REARRANGE_SAMPLES,		IDC_CHK_OPTIMIZE_SAMPLES,
	// instruments
	IDC_CHK_CLEANUP_INSTRUMENTS,	IDC_CHK_REMOVE_INSTRUMENTS,
	// plugins
	IDC_CHK_CLEANUP_PLUGINS,		IDC_CHK_REMOVE_PLUGINS,
	// misc
	IDC_CHK_RESET_VARIABLES,		IDC_CHK_UNUSED_CHANNELS,
};

// Options that are mutually exclusive to each other
CModCleanupDlg::CleanupOptions const CModCleanupDlg::m_MutuallyExclusive[CModCleanupDlg::kMaxCleanupOptions] =
{
	// patterns
	kRemovePatterns,		kCleanupPatterns,
	kRemovePatterns,		kRemovePatterns,
	// orders
	kRemoveOrders,			kMergeSequences,
	// samples
	kRemoveSamples,			kCleanupSamples,
	kRemoveSamples,			kRemoveSamples,
	// instruments
	kRemoveAllInstruments,	kCleanupInstruments,
	// plugins
	kRemoveAllPlugins,		kCleanupPlugins,
	// misc
	kNone,					kNone,

};

///////////////////////////////////////////////////////////////////////
// CModCleanupDlg

BEGIN_MESSAGE_MAP(CModCleanupDlg, CDialog)
	//{{AFX_MSG_MAP(CModTypeDlg)
	ON_COMMAND(IDC_BTN_CLEANUP_SONG,			&CModCleanupDlg::OnPresetCleanupSong)
	ON_COMMAND(IDC_BTN_COMPO_CLEANUP,			&CModCleanupDlg::OnPresetCompoCleanup)

	ON_COMMAND(IDC_CHK_CLEANUP_PATTERNS,		&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_REMOVE_PATTERNS,			&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_REARRANGE_PATTERNS,		&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_REMOVE_DUPLICATES,		&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_MERGE_SEQUENCES,			&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_REMOVE_ORDERS,			&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_CLEANUP_SAMPLES,			&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_REMOVE_SAMPLES,			&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_REARRANGE_SAMPLES,		&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_OPTIMIZE_SAMPLES,		&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_CLEANUP_INSTRUMENTS,		&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_REMOVE_INSTRUMENTS,		&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_CLEANUP_PLUGINS,			&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_REMOVE_PLUGINS,			&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_RESET_VARIABLES,			&CModCleanupDlg::OnVerifyMutualExclusive)
	ON_COMMAND(IDC_CHK_UNUSED_CHANNELS,			&CModCleanupDlg::OnVerifyMutualExclusive)

	ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CModCleanupDlg::OnToolTipNotify)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


BOOL CModCleanupDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
	for(int i = 0; i < kMaxCleanupOptions; i++)
	{
		CheckDlgButton(m_CleanupIDtoDlgID[i], (m_CheckBoxes[i]) ? BST_CHECKED : BST_UNCHECKED);
	}

	CSoundFile &sndFile = modDoc.GetSoundFile();

	GetDlgItem(m_CleanupIDtoDlgID[kMergeSequences])->EnableWindow((sndFile.Order.GetNumSequences() > 1) ? TRUE : FALSE);

	GetDlgItem(m_CleanupIDtoDlgID[kRemoveSamples])->EnableWindow((sndFile.GetNumSamples() > 0) ? TRUE : FALSE);
	GetDlgItem(m_CleanupIDtoDlgID[kRearrangeSamples])->EnableWindow((sndFile.GetNumSamples() > 1) ? TRUE : FALSE);

	GetDlgItem(m_CleanupIDtoDlgID[kCleanupInstruments])->EnableWindow((sndFile.GetNumInstruments() > 0) ? TRUE : FALSE);
	GetDlgItem(m_CleanupIDtoDlgID[kRemoveAllInstruments])->EnableWindow((sndFile.GetNumInstruments() > 0) ? TRUE : FALSE);

	EnableToolTips(TRUE);
	return TRUE;
}


void CModCleanupDlg::OnOK()
{
	ScopedLogCapturer logcapturer(modDoc, _T("cleanup"), this);
	for(int i = 0; i < kMaxCleanupOptions; i++)
	{
		m_CheckBoxes[i] = IsDlgButtonChecked(m_CleanupIDtoDlgID[i]) != BST_UNCHECKED;
	}

	bool modified = false;

	// Orders
	if(m_CheckBoxes[kMergeSequences]) modified |= MergeSequences();
	if(m_CheckBoxes[kRemoveOrders]) modified |= RemoveAllOrders();

	// Patterns
	if(m_CheckBoxes[kRemovePatterns]) modified |= RemoveAllPatterns();
	if(m_CheckBoxes[kCleanupPatterns]) modified |= RemoveUnusedPatterns();
	if(m_CheckBoxes[kRemoveDuplicatePatterns]) modified |= RemoveDuplicatePatterns();
	if(m_CheckBoxes[kRearrangePatterns]) modified |= RearrangePatterns();

	// Instruments
	if(modDoc.GetNumInstruments() > 0)
	{
		if(m_CheckBoxes[kRemoveAllInstruments]) modified |= RemoveAllInstruments();
		if(m_CheckBoxes[kCleanupInstruments]) modified |= RemoveUnusedInstruments();
	}

	// Samples
	if(m_CheckBoxes[kRemoveSamples]) modified |= RemoveAllSamples();
	if(m_CheckBoxes[kCleanupSamples]) modified |= RemoveUnusedSamples();
	if(m_CheckBoxes[kOptimizeSamples]) modified |= OptimizeSamples();
	if(modDoc.GetNumSamples() > 1)
	{
		if(m_CheckBoxes[kRearrangeSamples]) modified |= RearrangeSamples();
	}

	// Plugins
	if(m_CheckBoxes[kRemoveAllPlugins]) modified |= RemoveAllPlugins();
	if(m_CheckBoxes[kCleanupPlugins]) modified |= RemoveUnusedPlugins();

	// Create samplepack
	if(m_CheckBoxes[kResetVariables]) modified |= ResetVariables();

	// Remove unused channels
	if(m_CheckBoxes[kCleanupChannels]) modified |= RemoveUnusedChannels();

	if(modified) modDoc.SetModified();
	modDoc.UpdateAllViews(nullptr, UpdateHint().ModType());
	logcapturer.ShowLog(true);
	CDialog::OnOK();
}


void CModCleanupDlg::OnVerifyMutualExclusive()
{
	HWND hFocus = GetFocus()->m_hWnd;
	for(int i = 0; i < kMaxCleanupOptions; i++)	
	{
		// if this item is focussed, we have just (un)checked it.
		if(hFocus == GetDlgItem(m_CleanupIDtoDlgID[i])->m_hWnd)
		{
			// if we just unchecked it, there's nothing to verify.
			if(IsDlgButtonChecked(m_CleanupIDtoDlgID[i]) == BST_UNCHECKED)
				return;

			// now we can disable all elements that are mutually exclusive.
			if(m_MutuallyExclusive[i] != kNone)
				CheckDlgButton(m_CleanupIDtoDlgID[m_MutuallyExclusive[i]], BST_UNCHECKED);
			// find other elements which are mutually exclusive with the selected element.
			for(int j = 0; j < kMaxCleanupOptions; j++)	
			{
				if(m_MutuallyExclusive[j] == i)
					CheckDlgButton(m_CleanupIDtoDlgID[j], BST_UNCHECKED);
			}
			return;
		}
	}
}


void CModCleanupDlg::OnPresetCleanupSong()
{
	// patterns
	CheckDlgButton(IDC_CHK_CLEANUP_PATTERNS, BST_CHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_PATTERNS, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_REARRANGE_PATTERNS, BST_CHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_DUPLICATES, BST_CHECKED);
	// orders
	CheckDlgButton(IDC_CHK_MERGE_SEQUENCES, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_ORDERS, BST_UNCHECKED);
	// samples
	CheckDlgButton(IDC_CHK_CLEANUP_SAMPLES, BST_CHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_SAMPLES, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_REARRANGE_SAMPLES, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_OPTIMIZE_SAMPLES, BST_CHECKED);
	// instruments
	CheckDlgButton(IDC_CHK_CLEANUP_INSTRUMENTS, BST_CHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_INSTRUMENTS, BST_UNCHECKED);
	// plugins
	CheckDlgButton(IDC_CHK_CLEANUP_PLUGINS, BST_CHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_PLUGINS, BST_UNCHECKED);
	// misc
	CheckDlgButton(IDC_CHK_SAMPLEPACK, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_UNUSED_CHANNELS, BST_CHECKED);
}


void CModCleanupDlg::OnPresetCompoCleanup()
{
	// patterns
	CheckDlgButton(IDC_CHK_CLEANUP_PATTERNS, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_PATTERNS, BST_CHECKED);
	CheckDlgButton(IDC_CHK_REARRANGE_PATTERNS, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_DUPLICATES, BST_UNCHECKED);
	// orders
	CheckDlgButton(IDC_CHK_MERGE_SEQUENCES, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_ORDERS, BST_CHECKED);
	// samples
	CheckDlgButton(IDC_CHK_CLEANUP_SAMPLES, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_SAMPLES, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_REARRANGE_SAMPLES, BST_CHECKED);
	CheckDlgButton(IDC_CHK_OPTIMIZE_SAMPLES, BST_UNCHECKED);
	// instruments
	CheckDlgButton(IDC_CHK_CLEANUP_INSTRUMENTS, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_INSTRUMENTS, BST_CHECKED);
	// plugins
	CheckDlgButton(IDC_CHK_CLEANUP_PLUGINS, BST_UNCHECKED);
	CheckDlgButton(IDC_CHK_REMOVE_PLUGINS, BST_CHECKED);
	// misc
	CheckDlgButton(IDC_CHK_SAMPLEPACK, BST_CHECKED);
	CheckDlgButton(IDC_CHK_UNUSED_CHANNELS, BST_CHECKED);
}


BOOL CModCleanupDlg::OnToolTipNotify(UINT, NMHDR *pNMHDR, LRESULT *)
{
	TOOLTIPTEXT* pTTT = (TOOLTIPTEXT*)pNMHDR;
	UINT_PTR nID = pNMHDR->idFrom;
	if (pTTT->uFlags & TTF_IDISHWND)
	{
		// idFrom is actually the HWND of the tool
		nID = ::GetDlgCtrlID((HWND)nID);
	}

	LPCTSTR lpszText = nullptr;
	switch(nID)
	{
	// patterns
	case IDC_CHK_CLEANUP_PATTERNS:
		lpszText = _T("Remove all unused patterns and rearrange them.");
		break;
	case IDC_CHK_REMOVE_PATTERNS:
		lpszText = _T("Remove all patterns.");
		break;
	case IDC_CHK_REARRANGE_PATTERNS:
		lpszText = _T("Number the patterns given by their order in the sequence.");
		break;
	case IDC_CHK_REMOVE_DUPLICATES:
		lpszText = _T("Merge patterns with identical content.");
		break;
	// orders
	case IDC_CHK_REMOVE_ORDERS:
		lpszText = _T("Reset the order list.");
		break;
	case IDC_CHK_MERGE_SEQUENCES:
		lpszText = _T("Merge multiple sequences into one.");
		break;
	// samples
	case IDC_CHK_CLEANUP_SAMPLES:
		lpszText = _T("Remove all unused samples.");
		break;
	case IDC_CHK_REMOVE_SAMPLES:
		lpszText = _T("Remove all samples.");
		break;
	case IDC_CHK_REARRANGE_SAMPLES:
		lpszText = _T("Reorder sample list by removing empty samples.");
		break;
	case IDC_CHK_OPTIMIZE_SAMPLES:
		lpszText = _T("Remove unused data after the sample loop end.");
		break;
	// instruments
	case IDC_CHK_CLEANUP_INSTRUMENTS:
		lpszText = _T("Remove all unused instruments.");
		break;
	case IDC_CHK_REMOVE_INSTRUMENTS:
		lpszText = _T("Remove all instruments and convert them to samples.");
		break;
	// plugins
	case IDC_CHK_CLEANUP_PLUGINS:
		lpszText = _T("Remove all unused plugins.");
		break;
	case IDC_CHK_REMOVE_PLUGINS:
		lpszText = _T("Remove all plugins.");
		break;
	// misc
	case IDC_CHK_SAMPLEPACK:
		lpszText = _T("Convert the module to .IT and reset song / sample / instrument variables");
		break;
	case IDC_CHK_UNUSED_CHANNELS:
		lpszText = _T("Removes all empty pattern channels.");
		break;
	default:
		lpszText = _T("");
		break;
	}
	pTTT->lpszText = const_cast<LPTSTR>(lpszText);
	return TRUE;
}


///////////////////////////////////////////////////////////////////////
// Actual cleanup implementations

bool CModCleanupDlg::RemoveDuplicatePatterns()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();
	const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
	std::vector<PATTERNINDEX> patternMapping(numPatterns, PATTERNINDEX_INVALID);

	BeginWaitCursor();
	CriticalSection cs;

	PATTERNINDEX foundDupes = 0;
	for(PATTERNINDEX pat1 = 0; pat1 < numPatterns; pat1++)
	{
		if(!sndFile.Patterns.IsValidPat(pat1))
			continue;
		const CPattern &pattern1 = sndFile.Patterns[pat1];
		for(PATTERNINDEX pat2 = pat1 + 1; pat2 < numPatterns; pat2++)
		{
			if(!sndFile.Patterns.IsValidPat(pat2) || patternMapping[pat2] != PATTERNINDEX_INVALID)
				continue;
			const CPattern &pattern2 = sndFile.Patterns[pat2];
			if(pattern1 == pattern2)
			{
				modDoc.GetPatternUndo().PrepareUndo(pat2, 0, 0, pattern2.GetNumChannels(), pattern2.GetNumRows(), "Remove Duplicate Patterns", foundDupes != 0, false);
				sndFile.Patterns.Remove(pat2);

				patternMapping[pat2] = pat1;
				foundDupes++;
			}
		}
	}

	if(foundDupes != 0)
	{
		modDoc.AddToLog(MPT_AFORMAT("{} duplicate pattern{} merged.")(foundDupes, foundDupes == 1 ? "" : "s"));

		// Fix order list
		for(auto &order : sndFile.Order)
		{
			for(auto &pat : order)
			{
				if(pat < numPatterns && patternMapping[pat] != PATTERNINDEX_INVALID)
				{
					pat = patternMapping[pat];
				}
			}
		}
	}

	EndWaitCursor();

	return foundDupes != 0;
}


// Remove unused patterns
bool CModCleanupDlg::RemoveUnusedPatterns()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();
	const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
	std::vector<bool> patternUsed(numPatterns, false);

	BeginWaitCursor();
	// First, find all used patterns in all sequences.
	for(auto &order : sndFile.Order)
	{
		for(auto pat : order)
		{
			if(pat < numPatterns)
			{
				patternUsed[pat] = true;
			}
		}
	}

	// Remove all other patterns.
	CriticalSection cs;
	PATTERNINDEX numRemovedPatterns = 0;
	for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
	{
		if(!patternUsed[pat] && sndFile.Patterns.IsValidPat(pat))
		{
			numRemovedPatterns++;
			modDoc.GetPatternUndo().PrepareUndo(pat, 0, 0, sndFile.GetNumChannels(), sndFile.Patterns[pat].GetNumRows(), "Remove Unused Patterns", numRemovedPatterns != 0, false);
			sndFile.Patterns.Remove(pat);
		}
	}
	EndWaitCursor();

	if(numRemovedPatterns)
	{
		modDoc.AddToLog(MPT_AFORMAT("{} pattern{} removed.")(numRemovedPatterns, numRemovedPatterns == 1 ? "" : "s"));
		return true;
	}
	return false;
}


// Rearrange patterns (first pattern in order list = 0, etc...)
bool CModCleanupDlg::RearrangePatterns()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();

	const PATTERNINDEX numPatterns = sndFile.Patterns.Size();
	std::vector<PATTERNINDEX> newIndex(numPatterns, PATTERNINDEX_INVALID);

	bool modified = false;

	BeginWaitCursor();
	CriticalSection cs;

	// First, find all used patterns in all sequences.
	PATTERNINDEX patOrder = 0;
	for(auto &order : sndFile.Order)
	{
		for(auto &pat : order)
		{
			if(pat < numPatterns)
			{
				if(newIndex[pat] == PATTERNINDEX_INVALID)
				{
					newIndex[pat] = patOrder++;
				}
				pat = newIndex[pat];
			}
		}
	}
	// All unused patterns are moved to the end of the pattern list.
	for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
	{
		PATTERNINDEX &index = newIndex[pat];
		if(index == PATTERNINDEX_INVALID && sndFile.Patterns.IsValidPat(pat))
		{
			index = patOrder++;
		}
	}
	// Also need new indices for any non-existent patterns
	for(auto &index : newIndex)
	{
		if(index == PATTERNINDEX_INVALID)
		{
			index = patOrder++;
		}
	}

	modDoc.GetPatternUndo().RearrangePatterns(newIndex);

	// Now rearrange the actual patterns
	for(PATTERNINDEX i = 0; i < static_cast<PATTERNINDEX>(newIndex.size()); i++)
	{
		PATTERNINDEX j = newIndex[i];
		if(i == j)
			continue;
		while(i < j)
			j = newIndex[j];
		std::swap(sndFile.Patterns[i], sndFile.Patterns[j]);
		modified = true;
	}

	EndWaitCursor();

	return modified;
}


// Remove unused samples
bool CModCleanupDlg::RemoveUnusedSamples()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();

	std::vector<bool> samplesUsed(sndFile.GetNumSamples() + 1, true);

	BeginWaitCursor();

	// Check if any samples are not referenced in the patterns (sample mode) or by an instrument (instrument mode).
	// This doesn't check yet if a sample is referenced by an instrument, but actually unused in the patterns.
	for(SAMPLEINDEX smp = 1; smp <= sndFile.GetNumSamples(); smp++) if (sndFile.GetSample(smp).HasSampleData())
	{
		if(!modDoc.IsSampleUsed(smp))
		{
			samplesUsed[smp] = false;
		}
	}

	SAMPLEINDEX nRemoved = sndFile.RemoveSelectedSamples(samplesUsed);

	const SAMPLEINDEX unusedInsSamples = sndFile.DetectUnusedSamples(samplesUsed);

	EndWaitCursor();

	if(unusedInsSamples)
	{
		mpt::ustring s = MPT_UFORMAT("OpenMPT detected {} sample{} referenced by an instrument,\nbut not used in the song. Do you want to remove them?")
			( unusedInsSamples
			, (unusedInsSamples == 1) ? U_("") : U_("s")
			);
		if(Reporting::Confirm(s, "Sample Cleanup", false, false, this) == cnfYes)
		{
			nRemoved += sndFile.RemoveSelectedSamples(samplesUsed);
		}
	}

	if(nRemoved > 0)
	{
		modDoc.AddToLog(LogNotification, MPT_UFORMAT("{} unused sample{} removed")(nRemoved, (nRemoved == 1) ? U_("") : U_("s")));
	}

	return (nRemoved > 0);
}


// Check if the stereo channels of a sample contain identical data
template<typename T>
static bool ComapreStereoChannels(SmpLength length, const T *sampleData)
{
	for(SmpLength i = 0; i < length; i++, sampleData += 2)
	{
		if(sampleData[0] != sampleData[1])
		{
			return false;
		}
	}
	return true;
}

// Remove unused sample data
bool CModCleanupDlg::OptimizeSamples()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();

	SAMPLEINDEX numLoopOpt = 0, numStereoOpt = 0;
	std::vector<bool> stereoOptSamples(sndFile.GetNumSamples(), false);
	
	for(SAMPLEINDEX smp = 1; smp <= sndFile.GetNumSamples(); smp++)
	{
		const ModSample &sample = sndFile.GetSample(smp);

		// Determine how much of the sample will be played
		SmpLength loopLength = sample.nLength;
		if(sample.uFlags[CHN_LOOP])
		{
			loopLength = sample.nLoopEnd;
			if(sample.uFlags[CHN_SUSTAINLOOP])
			{
				loopLength = std::max(sample.nLoopEnd, sample.nSustainEnd);
			}
		}

		// Check if the sample contains identical stereo channels
		if(sample.GetNumChannels() == 2)
		{
			bool identicalChannels = false;
			if(sample.GetElementarySampleSize() == 1)
			{
				identicalChannels = ComapreStereoChannels(loopLength, sample.sample8());
			} else if(sample.GetElementarySampleSize() == 2)
			{
				identicalChannels = ComapreStereoChannels(loopLength, sample.sample16());
			}
			if(identicalChannels)
			{
				numStereoOpt++;
				stereoOptSamples[smp - 1] = true;
			}
		}

		if(sample.HasSampleData() && sample.nLength > loopLength + 2) numLoopOpt++;
	}
	if(!numLoopOpt && !numStereoOpt) return false;

	std::string s;
	if(numLoopOpt)
		s = MPT_AFORMAT("{} sample{} unused data after the loop end point.\n")(numLoopOpt, (numLoopOpt == 1) ? " has" : "s have");
	if(numStereoOpt)
		s += MPT_AFORMAT("{} stereo sample{} actually mono.\n")(numStereoOpt, (numStereoOpt == 1) ? " is" : "s are");
	if(numLoopOpt + numStereoOpt == 1)
		s += "Do you want to optimize it and remove this unused data?";
	else
		s += "Do you want to optimize them and remove this unused data?";

	if(Reporting::Confirm(s.c_str(), "Sample Optimization", false, false, this) != cnfYes)
	{
		return false;
	}

	for(SAMPLEINDEX smp = 1; smp <= sndFile.m_nSamples; smp++)
	{
		ModSample &sample = sndFile.GetSample(smp);

		// Determine how much of the sample will be played
		SmpLength loopLength = sample.nLength;
		if(sample.uFlags[CHN_LOOP])
		{
			loopLength = sample.nLoopEnd;

			// Sustain loop is played before normal loop, and it can actually be located after the normal loop.
			if(sample.uFlags[CHN_SUSTAINLOOP])
			{
				loopLength = std::max(sample.nLoopEnd, sample.nSustainEnd);
			}
		}

		if(sample.nLength > loopLength && loopLength >= 2)
		{
			modDoc.GetSampleUndo().PrepareUndo(smp, sundo_delete, "Trim Unused Data", loopLength, sample.nLength);
			SampleEdit::ResizeSample(sample, loopLength, sndFile);
		}

		// Convert stereo samples with identical channels to mono
		if(stereoOptSamples[smp - 1])
		{
			modDoc.GetSampleUndo().PrepareUndo(smp, sundo_replace, "Mono Conversion");
			ctrlSmp::ConvertToMono(sample, sndFile, ctrlSmp::onlyLeft);
		}
	}
	if(numLoopOpt)
	{
		s = MPT_AFORMAT("{} sample loop{} optimized")(numLoopOpt, (numLoopOpt == 1) ? "" : "s");
		modDoc.AddToLog(s);
	}
	if(numStereoOpt)
	{
		s = MPT_AFORMAT("{} sample{} converted to mono")(numStereoOpt, (numStereoOpt == 1) ? "" : "s");
		modDoc.AddToLog(s);
	}
	return true;
}

// Rearrange sample list
bool CModCleanupDlg::RearrangeSamples()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();
	if(sndFile.GetNumSamples() < 2)
		return false;

	std::vector<SAMPLEINDEX> sampleMap;
	sampleMap.reserve(sndFile.GetNumSamples());

	// First, find out which sample slots are unused and create the new sample map only with used samples
	for(SAMPLEINDEX i = 1; i <= sndFile.GetNumSamples(); i++)
	{
		if(sndFile.GetSample(i).HasSampleData())
		{
			sampleMap.push_back(i);
		}
	}

	// Nothing found to remove...
	if(sndFile.GetNumSamples() == sampleMap.size())
	{
		return false;
	}

	return (modDoc.ReArrangeSamples(sampleMap) != SAMPLEINDEX_INVALID);
}


// Remove unused instruments
bool CModCleanupDlg::RemoveUnusedInstruments()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();
	if(!sndFile.GetNumInstruments())
		return false;

	deleteInstrumentSamples removeSamples = doNoDeleteAssociatedSamples;
	if(Reporting::Confirm("Remove samples associated with unused instruments?", "Removing unused instruments", false, false, this) == cnfYes)
	{
		removeSamples = deleteAssociatedSamples;
	}

	BeginWaitCursor();

	std::vector<bool> instrUsed(sndFile.GetNumInstruments());
	bool prevUsed = true, reorder = false;
	INSTRUMENTINDEX numUsed = 0, lastUsed = 1;
	for(INSTRUMENTINDEX i = 0; i < sndFile.GetNumInstruments(); i++)
	{
		instrUsed[i] = (modDoc.IsInstrumentUsed(i + 1));
		if(instrUsed[i])
		{
			numUsed++;
			lastUsed = i;
			if(!prevUsed)
			{
				reorder = true;
			}
		}
		prevUsed = instrUsed[i];
	}

	EndWaitCursor();

	if(reorder && numUsed >= 1)
	{
		reorder = (Reporting::Confirm("Do you want to reorganize the remaining instruments?", "Removing unused instruments", false, false, this) == cnfYes);
	} else
	{
		reorder = false;
	}

	const INSTRUMENTINDEX numRemoved = sndFile.GetNumInstruments() - numUsed;

	if(numRemoved != 0)
	{
		BeginWaitCursor();

		std::vector<INSTRUMENTINDEX> instrMap;
		instrMap.reserve(sndFile.GetNumInstruments());
		for(INSTRUMENTINDEX i = 0; i < sndFile.GetNumInstruments(); i++)
		{
			if(instrUsed[i])
			{
				instrMap.push_back(i + 1);
			} else if(!reorder && i < lastUsed)
			{
				instrMap.push_back(INSTRUMENTINDEX_INVALID);
			}
		}

		modDoc.ReArrangeInstruments(instrMap, removeSamples);

		EndWaitCursor();

		modDoc.AddToLog(LogNotification, MPT_UFORMAT("{} unused instrument{} removed")(numRemoved, (numRemoved == 1) ? U_("") : U_("s")));
		return true;
	}
	return false;
}


// Remove ununsed plugins
bool CModCleanupDlg::RemoveUnusedPlugins()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();

	std::vector<bool> usedmap(MAX_MIXPLUGINS, false);
	
	for(PLUGINDEX nPlug = 0; nPlug < MAX_MIXPLUGINS; nPlug++)
	{

		// Is the plugin assigned to a channel?
		for(CHANNELINDEX nChn = 0; nChn < sndFile.GetNumChannels(); nChn++)
		{
			if (sndFile.ChnSettings[nChn].nMixPlugin == nPlug + 1)
			{
				usedmap[nPlug] = true;
				break;
			}
		}

		// Is the plugin used by an instrument?
		for(INSTRUMENTINDEX nIns = 1; nIns <= sndFile.GetNumInstruments(); nIns++)
		{
			if (sndFile.Instruments[nIns] && (sndFile.Instruments[nIns]->nMixPlug == nPlug + 1))
			{
				usedmap[nPlug] = true;
				break;
			}
		}

		// Is the plugin assigned to master?
		if(sndFile.m_MixPlugins[nPlug].IsMasterEffect())
			usedmap[nPlug] = true;

		// All outputs of used plugins count as used
		if(usedmap[nPlug])
		{
			if(!sndFile.m_MixPlugins[nPlug].IsOutputToMaster())
			{
				PLUGINDEX output = sndFile.m_MixPlugins[nPlug].GetOutputPlugin();
				if(output != PLUGINDEX_INVALID)
				{
					usedmap[output] = true;
				}
			}
		}

	}

	PLUGINDEX numRemoved = modDoc.RemovePlugs(usedmap);
	if(numRemoved != 0)
	{
		modDoc.AddToLog(LogInformation, MPT_UFORMAT("{} unused plugin{} removed")(numRemoved, (numRemoved == 1) ? U_("") : U_("s")));
		return true;
	}
	return false;
}


// Reset variables (convert to IT, reset global/smp/ins vars, etc.)
bool CModCleanupDlg::ResetVariables()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();

	if(Reporting::Confirm(_T("OpenMPT will convert the module to IT format and reset all song, sample and instrument attributes to default values. Continue?"), _T("Resetting variables"), false, false, this) == cnfNo)
		return false;

	// Stop play.
	CMainFrame::GetMainFrame()->StopMod(&modDoc);

	BeginWaitCursor();
	CriticalSection cs;

	// Convert to IT...
	modDoc.ChangeModType(MOD_TYPE_IT);
	sndFile.SetDefaultPlaybackBehaviour(sndFile.GetType());
	sndFile.SetMixLevels(MixLevels::Compatible);
	sndFile.m_songArtist.clear();
	sndFile.m_nTempoMode = TempoMode::Classic;
	sndFile.m_SongFlags = SONG_LINEARSLIDES;
	sndFile.m_MidiCfg.Reset();
	
	// Global vars
	sndFile.m_nDefaultTempo.Set(125);
	sndFile.m_nDefaultSpeed = 6;
	sndFile.m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
	sndFile.m_nSamplePreAmp = 48;
	sndFile.m_nVSTiVolume = 48;
	sndFile.Order().SetRestartPos(0);

	if(sndFile.Order().empty())
	{
		modDoc.InsertPattern(64, 0);
	}

	// Reset instruments (if there are any)
	for(INSTRUMENTINDEX i = 1; i <= sndFile.GetNumInstruments(); i++) if(sndFile.Instruments[i])
	{
		sndFile.Instruments[i]->nFadeOut = 256;
		sndFile.Instruments[i]->nGlobalVol = 64;
		sndFile.Instruments[i]->nPan = 128;
		sndFile.Instruments[i]->dwFlags.reset(INS_SETPANNING);
		sndFile.Instruments[i]->nMixPlug = 0;

		sndFile.Instruments[i]->nVolSwing = 0;
		sndFile.Instruments[i]->nPanSwing = 0;
		sndFile.Instruments[i]->nCutSwing = 0;
		sndFile.Instruments[i]->nResSwing = 0;
	}

	for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++)
	{
		sndFile.InitChannel(chn);
	}

	// reset samples
	SampleEdit::ResetSamples(sndFile, SampleEdit::SmpResetCompo);

	cs.Leave();
	EndWaitCursor();

	return true;
}


bool CModCleanupDlg::RemoveUnusedChannels()
{
	// Avoid M.K. modules to become xCHN modules if some channels are unused.
	if(modDoc.GetModType() == MOD_TYPE_MOD && modDoc.GetNumChannels() == 4)
		return false;

	std::vector<bool> usedChannels;
	modDoc.CheckUsedChannels(usedChannels, modDoc.GetNumChannels() - modDoc.GetSoundFile().GetModSpecifications().channelsMin);
	return modDoc.RemoveChannels(usedChannels);
}


// Remove all patterns
bool CModCleanupDlg::RemoveAllPatterns()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();

	if(sndFile.Patterns.Size() == 0) return false;
	modDoc.GetPatternUndo().ClearUndo();
	sndFile.Patterns.ResizeArray(0);
	sndFile.SetCurrentOrder(0);
	return true;
}

// Remove all orders
bool CModCleanupDlg::RemoveAllOrders()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();

	sndFile.Order.Initialize();
	sndFile.SetCurrentOrder(0);
	return true;
}

// Remove all samples
bool CModCleanupDlg::RemoveAllSamples()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();

	if (sndFile.GetNumSamples() == 0) return false;

	std::vector<bool> keepSamples(sndFile.GetNumSamples() + 1, false);
	sndFile.RemoveSelectedSamples(keepSamples);

	SampleEdit::ResetSamples(sndFile, SampleEdit::SmpResetInit, 1, MAX_SAMPLES - 1);

	return true;
}

// Remove all instruments
bool CModCleanupDlg::RemoveAllInstruments()
{
	CSoundFile &sndFile = modDoc.GetSoundFile();

	if(sndFile.GetNumInstruments() == 0) return false;

	modDoc.ConvertInstrumentsToSamples();

	for(INSTRUMENTINDEX i = 1; i <= sndFile.GetNumInstruments(); i++)
	{
		sndFile.DestroyInstrument(i, doNoDeleteAssociatedSamples);
	}

	sndFile.m_nInstruments = 0;
	return true;
}

// Remove all plugins
bool CModCleanupDlg::RemoveAllPlugins()
{
	std::vector<bool> keepMask(MAX_MIXPLUGINS, false);
	modDoc.RemovePlugs(keepMask);
	return true;
}


bool CModCleanupDlg::MergeSequences()
{
	return modDoc.GetSoundFile().Order.MergeSequences();
}


OPENMPT_NAMESPACE_END