/*
 * MPTrack.h
 * ---------
 * Purpose: OpenMPT core application class.
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#pragma once

#include "openmpt/all/BuildSettings.hpp"

#include "resource.h"  // main symbols
#include "Settings.h"
#include "MPTrackUtil.h"
#include "Reporting.h"
#include "../soundlib/MIDIMacros.h"
#include "../soundlib/modcommand.h"
#include "../common/ComponentManager.h"
#include "../misc/mptMutex.h"
#include "../common/mptRandom.h"

#include <future>

OPENMPT_NAMESPACE_BEGIN

class CModDoc;
class CModDocTemplate;
class CVstPluginManager;
namespace SoundDevice
{
class Manager;
}  // namespace SoundDevice
struct AllSoundDeviceComponents;
class CDLSBank;
class DebugSettings;
class TrackerSettings;
class ComponentManagerSettings;
namespace mpt
{
namespace Wine
{
class VersionContext;
class Context;
}  // namespace Wine
}  // namespace mpt
class GdiplusRAII;


/////////////////////////////////////////////////////////////////////////////
// 16-colors DIB
struct MODPLUGDIB
{
	BITMAPINFOHEADER bmiHeader;
	RGBQUAD bmiColors[16];
	LPBYTE lpDibBits;
};


/////////////////////////////////////////////////////////////////////////////
// Midi Library

using MidiLibrary = std::array<mpt::PathString, 128 * 2>;  // 128 instruments + 128 percussions


//////////////////////////////////////////////////////////////////////////
// Dragon Droppings

enum DragonDropType
{
	DRAGONDROP_NOTHING = 0,  // |------< Drop Type >-------------|---< dropItem >----|---< dropParam >---|
	DRAGONDROP_DLS,          // | Instrument from a DLS bank     |     DLS Bank #    |   DLS Instrument  |
	DRAGONDROP_SAMPLE,       // | Sample from a song             |     Sample #      |       NULL        |
	DRAGONDROP_INSTRUMENT,   // | Instrument from a song         |     Instrument #  |       NULL        |
	DRAGONDROP_SOUNDFILE,    // | File from instrument library   |        ?          |     File Name     |
	DRAGONDROP_MIDIINSTR,    // | File from midi library         | Midi Program/Perc |     File Name     |
	DRAGONDROP_PATTERN,      // | Pattern from a song            |      Pattern #    |       NULL        |
	DRAGONDROP_ORDER,        // | Pattern index in a song        |       Order #     |       NULL        |
	DRAGONDROP_SONG,         // | Song file (mod/s3m/xm/it)      |       0           |     File Name     |
	DRAGONDROP_SEQUENCE      // | Sequence (a set of orders)     |    Sequence #     |       NULL        |
};

struct DRAGONDROP
{
	const CSoundFile *sndFile = nullptr;
	DragonDropType dropType = DRAGONDROP_NOTHING;
	uint32 dropItem = 0;
	LPARAM dropParam = 0;

	mpt::PathString GetPath() const
	{
		const mpt::PathString *const path = reinterpret_cast<const mpt::PathString *>(dropParam);
		MPT_ASSERT(path);
		return path ? *path : mpt::PathString();
	}
};


/////////////////////////////////////////////////////////////////////////////
// CTrackApp:
// See mptrack.cpp for the implementation of this class
//

class CMPTCommandLineInfo;

class CTrackApp : public CWinApp
{
	friend class CMainFrame;
	// static data
protected:
	static MODTYPE m_nDefaultDocType;
	static MidiLibrary midiLibrary;

public:
	static std::vector<std::unique_ptr<CDLSBank>> gpDLSBanks;

protected:
	mpt::recursive_mutex_with_lock_count m_GlobalMutex;

	DWORD m_GuiThreadId = 0;

	std::future<std::vector<std::unique_ptr<CDLSBank>>> m_scannedDlsBanks;
	std::atomic<bool> m_scannedDlsBanksAvailable = false;

	std::unique_ptr<mpt::random_device> m_RD;
	std::unique_ptr<mpt::thread_safe_prng<mpt::default_prng>> m_PRNG;

	std::unique_ptr<GdiplusRAII> m_Gdiplus;

	std::shared_ptr<mpt::OS::Wine::VersionContext> m_WineVersion;

	IniFileSettingsBackend *m_pSettingsIniFile;
	SettingsContainer *m_pSettings = nullptr;
	DebugSettings *m_pDebugSettings = nullptr;
	TrackerSettings *m_pTrackerSettings = nullptr;
	IniFileSettingsBackend *m_pSongSettingsIniFile = nullptr;
	SettingsContainer *m_pSongSettings = nullptr;
	ComponentManagerSettings *m_pComponentManagerSettings = nullptr;
	IniFileSettingsContainer *m_pPluginCache = nullptr;
	CModDocTemplate *m_pModTemplate = nullptr;
	CVstPluginManager *m_pPluginManager = nullptr;
	mpt::log::GlobalLogger m_GlobalLogger{};
	std::unique_ptr<AllSoundDeviceComponents> m_pAllSoundDeviceComponents;
	std::unique_ptr<SoundDevice::Manager> m_pSoundDevicesManager;

	mpt::PathString m_InstallPath;         // i.e. "C:\Program Files\OpenMPT\" (installer mode) or "G:\OpenMPT\" (portable mode)
	mpt::PathString m_InstallBinPath;      // i.e. "C:\Program Files\OpenMPT\bin\" (multi-arch mode) or InstallPath (legacy mode)
	mpt::PathString m_InstallBinArchPath;  // i.e. "C:\Program Files\OpenMPT\bin\amd64\" (multi-arch mode) or InstallPath (legacy mode)
	mpt::PathString m_InstallPkgPath;      // i.e. "C:\Program Files\OpenMPT\" (installer mode) or "G:\OpenMPT\" (portable mode)

	mpt::PathString m_ConfigPath;  // InstallPath (portable mode) or "%AppData%\OpenMPT\"

	mpt::PathString m_szConfigFileName;
	mpt::PathString m_szPluginCacheFileName;

	std::shared_ptr<mpt::Wine::Context> m_Wine;
	mpt::PathString m_WineWrapperDllName;
	// Default macro configuration
	MIDIMacroConfig m_MidiCfg;
	DWORD m_dwLastPluginIdleCall = 0;
	bool m_bInstallerMode = false;
	bool m_bPortableMode = false;
	bool m_bSourceTreeMode = false;

public:
	CTrackApp();

	CDataRecoveryHandler *GetDataRecoveryHandler() override;
	void AddToRecentFileList(LPCTSTR lpszPathName) override;
	void AddToRecentFileList(const mpt::PathString &path);
	/// Removes item from MRU-list; most recent item has index zero.
	void RemoveMruItem(const size_t item);
	void RemoveMruItem(const mpt::PathString &path);

public:
	bool IsMultiArchInstall() const { return m_InstallPath == m_InstallBinArchPath; }
	mpt::PathString GetInstallPath() const { return m_InstallPath; }                // i.e. "C:\Program Files\OpenMPT\" (installer mode) or "G:\OpenMPT\" (portable mode)
	mpt::PathString GetInstallBinPath() const { return m_InstallBinPath; }          // i.e. "C:\Program Files\OpenMPT\bin\" (multi-arch mode) or InstallPath (legacy mode)
	mpt::PathString GetInstallBinArchPath() const { return m_InstallBinArchPath; }  // i.e. "C:\Program Files\OpenMPT\bin\amd64\" (multi-arch mode) or InstallPath (legacy mode)
	mpt::PathString GetInstallPkgPath() const { return m_InstallPkgPath; }          // i.e. "C:\Program Files\OpenMPT\" (installer mode) or "G:\OpenMPT\" (portable mode)

	static MODTYPE GetDefaultDocType() { return m_nDefaultDocType; }
	static void SetDefaultDocType(MODTYPE n) { m_nDefaultDocType = n; }
	static MidiLibrary &GetMidiLibrary() { return midiLibrary; }
	static void ImportMidiConfig(const mpt::PathString &filename, bool hideWarning = false);
	static void ExportMidiConfig(const mpt::PathString &filename);
	static void ImportMidiConfig(SettingsContainer &file, const mpt::PathString &path, bool forgetSettings = false);
	static void ExportMidiConfig(SettingsContainer &file);
	static std::future<std::vector<std::unique_ptr<CDLSBank>>> LoadDefaultDLSBanks();
	static void SaveDefaultDLSBanks();
	static void RemoveDLSBank(UINT nBank);
	static bool AddDLSBank(const mpt::PathString &filename);
	static bool OpenURL(const char *url);         // UTF8
	static bool OpenURL(const std::string &url);  // UTF8
	static bool OpenURL(const CString &url);
	static bool OpenURL(const mpt::ustring &url);
	static bool OpenURL(const mpt::PathString &lpszURL);
	static bool OpenFile(const mpt::PathString &file) { return OpenURL(file); };
	static bool OpenDirectory(const mpt::PathString &directory) { return OpenURL(directory); };

	// Retrieve the user-supplied MIDI port name for a MIDI input or output port.
	mpt::ustring GetFriendlyMIDIPortName(const mpt::ustring &deviceName, bool isInputPort, bool addDeviceName = true);
	CString GetFriendlyMIDIPortName(const CString &deviceName, bool isInputPort, bool addDeviceName = true);

	int GetOpenDocumentCount() const;
	std::vector<CModDoc *> GetOpenDocuments() const;

public:
	inline mpt::recursive_mutex_with_lock_count &GetGlobalMutexRef() { return m_GlobalMutex; }
	bool InGuiThread() const { return GetCurrentThreadId() == m_GuiThreadId; }
	mpt::random_device &RandomDevice() { return *m_RD; }
	mpt::thread_safe_prng<mpt::default_prng> &PRNG() { return *m_PRNG; }
	CModDocTemplate *GetModDocTemplate() const { return m_pModTemplate; }
	CVstPluginManager *GetPluginManager() const { return m_pPluginManager; }
	SoundDevice::Manager *GetSoundDevicesManager() const { return m_pSoundDevicesManager.get(); }
	void GetDefaultMidiMacro(MIDIMacroConfig &cfg) const { cfg = m_MidiCfg; }
	void SetDefaultMidiMacro(const MIDIMacroConfig &cfg) { m_MidiCfg = cfg; }
	mpt::PathString GetConfigFileName() const { return m_szConfigFileName; }
	SettingsContainer *GetpSettings()
	{
		return m_pSettings;
	}
	SettingsContainer &GetSettings()
	{
		ASSERT(m_pSettings);
		return *m_pSettings;
	}
	TrackerSettings &GetTrackerSettings()
	{
		ASSERT(m_pTrackerSettings);
		return *m_pTrackerSettings;
	}
	bool IsInstallerMode() const
	{
		return m_bInstallerMode;
	}
	bool IsPortableMode() const
	{
		return m_bPortableMode;
	}
	bool IsSourceTreeMode() const
	{
		return m_bSourceTreeMode;
	}

	SettingsContainer &GetPluginCache()
	{
		ASSERT(m_pPluginCache);
		return *m_pPluginCache;
	}

	SettingsContainer &GetSongSettings()
	{
		ASSERT(m_pSongSettings);
		return *m_pSongSettings;
	}
	const mpt::PathString &GetSongSettingsFilename() const
	{
		return m_pSongSettingsIniFile->GetFilename();
	}

	void SetWineVersion(std::shared_ptr<mpt::OS::Wine::VersionContext> wineVersion)
	{
		MPT_ASSERT_ALWAYS(mpt::OS::Windows::IsWine());
		m_WineVersion = wineVersion;
	}
	std::shared_ptr<mpt::OS::Wine::VersionContext> GetWineVersion() const
	{
		MPT_ASSERT_ALWAYS(mpt::OS::Windows::IsWine());
		MPT_ASSERT_ALWAYS(m_WineVersion);  // Verify initialization order. We should not should reach this until after Wine is detected.
		return m_WineVersion;
	}

	void SetWine(std::shared_ptr<mpt::Wine::Context> wine)
	{
		m_Wine = wine;
	}
	std::shared_ptr<mpt::Wine::Context> GetWine() const
	{
		return m_Wine;
	}

	void SetWineWrapperDllFilename(mpt::PathString filename)
	{
		m_WineWrapperDllName = filename;
	}
	mpt::PathString GetWineWrapperDllFilename() const
	{
		return m_WineWrapperDllName;
	}

	/// Returns path to config folder including trailing '\'.
	mpt::PathString GetConfigPath() const { return m_ConfigPath; }
	void SetupPaths(bool overridePortable);
	void CreatePaths();

#if !defined(MPT_BUILD_RETRO)
	bool CheckSystemSupport();
#endif // !MPT_BUILD_RETRO

	// Relative / absolute paths conversion
	mpt::PathString PathAbsoluteToInstallRelative(const mpt::PathString &path) { return path.AbsolutePathToRelative(GetInstallPath()); }
	mpt::PathString PathInstallRelativeToAbsolute(const mpt::PathString &path) { return path.RelativePathToAbsolute(GetInstallPath()); }
	mpt::PathString PathAbsoluteToInstallBinArchRelative(const mpt::PathString &path) { return path.AbsolutePathToRelative(GetInstallBinArchPath()); }
	mpt::PathString PathInstallBinArchRelativeToAbsolute(const mpt::PathString &path) { return path.RelativePathToAbsolute(GetInstallBinArchPath()); }

	static void OpenModulesDialog(std::vector<mpt::PathString> &files, const mpt::PathString &overridePath = mpt::PathString());

public:
	// Get name of resampling mode. addTaps = true also adds the number of taps the filter uses.
	static CString GetResamplingModeName(ResamplingMode mode, int length, bool addTaps);

	// Overrides
public:
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CTrackApp)
public:
	BOOL InitInstance() override;
	BOOL InitInstanceEarly(CMPTCommandLineInfo &cmdInfo);
	BOOL InitInstanceLate(CMPTCommandLineInfo &cmdInfo);
	BOOL InitInstanceImpl(CMPTCommandLineInfo &cmdInfo);
	int Run() override;
	LRESULT ProcessWndProcException(CException *e, const MSG *pMsg) override;
	int ExitInstance() override;
	int ExitInstanceImpl();
	BOOL OnIdle(LONG lCount) override;
	//}}AFX_VIRTUAL

	// Implementation

	//{{AFX_MSG(CTrackApp)
	CModDoc *NewDocument(MODTYPE newType = MOD_TYPE_NONE);

	afx_msg void OnFileNew() { NewDocument(); }
	afx_msg void OnFileNewMOD() { NewDocument(MOD_TYPE_MOD); }
	afx_msg void OnFileNewS3M() { NewDocument(MOD_TYPE_S3M); }
	afx_msg void OnFileNewXM() { NewDocument(MOD_TYPE_XM); }
	afx_msg void OnFileNewIT() { NewDocument(MOD_TYPE_IT); }
	afx_msg void OnFileNewMPT() { NewDocument(MOD_TYPE_MPT); }

	afx_msg void OnFileOpen();
	afx_msg void OnAppAbout();

	afx_msg void OnFileCloseAll();
	afx_msg void OnUpdateAnyDocsOpen(CCmdUI *cmd);

	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()

protected:
	size_t AddScannedDLSBanks();

	void InitializeDXPlugins();
	void UninitializeDXPlugins();

	bool MoveConfigFile(const mpt::PathString &fileName, mpt::PathString subDir = {}, mpt::PathString newFileName = {});
};


extern CTrackApp theApp;


//////////////////////////////////////////////////////////////////
// More Bitmap Helpers

class CFastBitmap
{
protected:
	static constexpr uint8 BLEND_OFFSET = 0x80;

	struct MODPLUGFASTDIB
	{
		BITMAPINFOHEADER bmiHeader;
		RGBQUAD bmiColors[256];
		std::vector<uint8> DibBits;
	};

	MODPLUGFASTDIB m_Dib;
	UINT m_nTextColor, m_nBkColor;
	MODPLUGDIB *m_pTextDib;
	uint8 m_nBlendOffset;
	uint8 m_n4BitPalette[16];
	uint8 m_nXShiftFactor;

public:
	CFastBitmap() {}

public:
	void Init(MODPLUGDIB *lpTextDib = nullptr);
	void Blit(HDC hdc, int x, int y, int cx, int cy);
	void Blit(HDC hdc, LPCRECT lprc) { Blit(hdc, lprc->left, lprc->top, lprc->right - lprc->left, lprc->bottom - lprc->top); }
	void SetTextColor(int nText, int nBk = -1)
	{
		m_nTextColor = nText;
		if(nBk >= 0)
			m_nBkColor = nBk;
	}
	void SetTextBkColor(UINT nBk) { m_nBkColor = nBk; }
	void SetColor(UINT nIndex, COLORREF cr);
	void SetAllColors(UINT nBaseIndex, UINT nColors, COLORREF *pcr);
	void TextBlt(int x, int y, int cx, int cy, int srcx, int srcy, MODPLUGDIB *lpdib = nullptr);
	void SetBlendMode(bool enable) { m_nBlendOffset = enable ? BLEND_OFFSET : 0; }
	bool GetBlendMode() const { return m_nBlendOffset != 0; }
	void SetBlendColor(COLORREF cr);
	void SetSize(int x, int y);
	int GetWidth() const { return m_Dib.bmiHeader.biWidth; }
};


///////////////////////////////////////////////////
// 4-bit DIB Drawing functions
void DibBlt(HDC hdc, int x, int y, int sizex, int sizey, int srcx, int srcy, MODPLUGDIB *lpdib);
MODPLUGDIB *LoadDib(LPCTSTR lpszName);
RGBQUAD rgb2quad(COLORREF c);

// Other bitmap functions
int DrawTextT(HDC hdc, const wchar_t *lpchText, int cchText, LPRECT lprc, UINT format);
int DrawTextT(HDC hdc, const char *lpchText, int cchText, LPRECT lprc, UINT format);
void DrawButtonRect(HDC hdc, const RECT *lpRect, LPCSTR lpszText = nullptr, BOOL bDisabled = FALSE, BOOL bPushed = FALSE, DWORD dwFlags = (DT_CENTER | DT_VCENTER), uint32 topMargin = 0);
void DrawButtonRect(HDC hdc, const RECT *lpRect, LPCWSTR lpszText = nullptr, BOOL bDisabled = FALSE, BOOL bPushed = FALSE, DWORD dwFlags = (DT_CENTER | DT_VCENTER), uint32 topMargin = 0);

// Misc functions
void ErrorBox(UINT nStringID, CWnd *p = nullptr);

// Helper function declarations.
struct SNDMIXPLUGIN;
class IMixPlugin;
void AddPluginNamesToCombobox(CComboBox &CBox, const SNDMIXPLUGIN *plugarray, const bool libraryName = false, const PLUGINDEX updatePlug = PLUGINDEX_INVALID);
void AddPluginParameternamesToCombobox(CComboBox &CBox, SNDMIXPLUGIN &plugarray);
void AddPluginParameternamesToCombobox(CComboBox &CBox, IMixPlugin &plug);

// Append note names in range [noteStart, noteEnd] to given combobox. Index starts from 0.
void AppendNotesToControl(CComboBox &combobox, ModCommand::NOTE noteStart, ModCommand::NOTE noteEnd);

// Append note names to combo box.
// If nInstr is given, instrument-specific note names are used instead of default note names.
// A custom note range may also be specified using the noteStart and noteEnd parameters.
// If they are left out, only notes that are available in the module type, plus any supported "special notes" are added.
void AppendNotesToControlEx(CComboBox &combobox, const CSoundFile &sndFile, INSTRUMENTINDEX nInstr = MAX_INSTRUMENTS, ModCommand::NOTE noteStart = 0, ModCommand::NOTE noteEnd = 0);

// Get window text (e.g. edit box content) as a CString
CString GetWindowTextString(const CWnd &wnd);

// Get window text (e.g. edit box content) as a unicode string
mpt::ustring GetWindowTextUnicode(const CWnd &wnd);

///////////////////////////////////////////////////
// Tables

extern const TCHAR *szSpecialNoteNamesMPT[];
extern const TCHAR *szSpecialNoteShortDesc[];
extern const char *szHexChar;

// Defined in load_mid.cpp
extern const char *szMidiProgramNames[128];
extern const char *szMidiPercussionNames[61];  // notes 25..85
extern const char *szMidiGroupNames[17];       // 16 groups + Percussions

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.


OPENMPT_NAMESPACE_END