/*
 * TuningDialog.h
 * --------------
 * Purpose: Alternative sample tuning configuration dialog.
 * 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 "tuningRatioMapWnd.h"
#include "tuningcollection.h"
#include <vector>
#include <string>
#include "resource.h"
#include "CDecimalSupport.h"

OPENMPT_NAMESPACE_BEGIN


// Tunings exist even outside of CSoundFile objects. We thus cannot use the
// GetCharsetInternal() encoding consistently. For now, just always treat
// tuning strings as Charset::Locale. As of OpenMPT 1.27, this distinction does
// not yet matter, because GetCharsetInteral() is always mpt::Charset::Locale if
// MODPLUG_TRACKER anyway.
extern const mpt::Charset TuningCharsetFallback;

template<class T1, class T2>
class CBijectiveMap
{
public:
	CBijectiveMap(const T1& a, const T2& b)
		:	m_NotFoundT1(a),
			m_NotFoundT2(b)
	{}

	void AddPair(const T1& a, const T2& b)
	{
		m_T1.push_back(a);
		m_T2.push_back(b);
	}

	void ClearMapping()
	{
		m_T1.clear();
		m_T2.clear();
	}

	size_t Size() const
	{
		ASSERT(m_T1.size() == m_T2.size());
		return m_T1.size();
	}

	void RemoveValue_1(const T1& a)
	{
		auto iter = find(m_T1.begin(), m_T1.end(), a);
		if(iter != m_T1.end())
		{
			m_T2.erase(m_T2.begin() + (iter-m_T1.begin()));
			m_T1.erase(iter);
		}
	}

	void RemoveValue_2(const T2& b)
	{
		auto iter = find(m_T2.begin(), m_T2.end(), b);
		if(iter != m_T2.end())
		{
			m_T1.erase(m_T1.begin() + (iter-m_T2.begin()));
			m_T2.erase(iter);
		}
	}

	T2 GetMapping_12(const T1& a) const
	{
		auto iter = find(m_T1.begin(), m_T1.end(), a);
		if(iter != m_T1.end())
		{
			return m_T2[iter-m_T1.begin()];
		}
		else
			return m_NotFoundT2;
	}

	T1 GetMapping_21(const T2& b) const
	{
		auto iter = find(m_T2.begin(), m_T2.end(), b);
		if(iter != m_T2.end())
		{
			return m_T1[iter-m_T2.begin()];
		}
		else
			return m_NotFoundT1;
	}

private:
	//Elements are collected to two arrays so that elements with the
	//same index are mapped to each other.
	std::vector<T1> m_T1;
	std::vector<T2> m_T2;

	T1 m_NotFoundT1;
	T2 m_NotFoundT2;
};

class CTuningDialog;

class CTuningTreeCtrl : public CTreeCtrl
{
private:
	CTuningDialog& m_rParentDialog;
	bool m_Dragging;

public:
	CTuningTreeCtrl(CTuningDialog* parent)
		: m_rParentDialog(*parent)
		, m_Dragging(false)
	{}
	//Note: Parent address may be given in its initializer list.

	void SetDragging(bool state = true) {m_Dragging = state;}
	bool IsDragging() {return m_Dragging;}

	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	DECLARE_MESSAGE_MAP()
};

class CTuningTreeItem
{
private:
	CTuning* m_pTuning;
	CTuningCollection* m_pTuningCollection;

public:
	CTuningTreeItem() : m_pTuning(NULL),
						m_pTuningCollection(NULL)
	{}

	CTuningTreeItem(CTuning* pT) :
					m_pTuning(pT),
					m_pTuningCollection(NULL)
	{}

	CTuningTreeItem(CTuningCollection* pTC) :
					m_pTuning(NULL),
					m_pTuningCollection(pTC)
	{}

	bool operator==(const CTuningTreeItem& ti) const
	{
		if(m_pTuning == ti.m_pTuning &&
			m_pTuningCollection == ti.m_pTuningCollection)
			return true;
		else
			return false;
	}

	void Reset() {m_pTuning = NULL; m_pTuningCollection = NULL;}


	void Set(CTuning* pT)
	{
		m_pTuning = pT;
		m_pTuningCollection = NULL;
	}

	void Set(CTuningCollection* pTC)
	{
		m_pTuning = NULL;
		m_pTuningCollection = pTC;
	}

	operator bool () const
	{
		return m_pTuning || m_pTuningCollection;
	}
	bool operator ! () const
	{
		return !operator bool();
	}

	CTuningCollection* GetTC() {return m_pTuningCollection;}

	CTuning* GetT() {return m_pTuning;}
};

// CTuningDialog dialog

class CTuningDialog : public CDialog
{
	friend class CTuningTreeCtrl;

	enum EnSclImport
	{
		enSclImportOk,
		enSclImportFailTooLargeNumDenomIntegers,
		enSclImportFailZeroDenominator,
		enSclImportFailNegativeRatio,
		enSclImportFailUnableToOpenFile,
		enSclImportLineCountMismatch,
		enSclImportTuningCreationFailure,
		enSclImportAddTuningFailure,
		enSclImportFailTooManyNotes
	};

public:
	using TUNINGVECTOR = std::vector<CTuningCollection*>;

public:
	CTuningDialog(CWnd* pParent, INSTRUMENTINDEX inst, CSoundFile &csf);
	virtual ~CTuningDialog();

	BOOL OnInitDialog();

	void UpdateRatioMapEdits(const Tuning::NOTEINDEXTYPE&);

	bool GetModifiedStatus(const CTuningCollection* const pTc) const;

// Dialog Data
	enum { IDD = IDD_TUNING };

protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

private:

	bool CanEdit(CTuningCollection * pTC) const;
	bool CanEdit(CTuning * pT, CTuningCollection * pTC) const;

	void UpdateView(const int UpdateMask = 0);
	void UpdateTuningType();

	HTREEITEM AddTreeItem(CTuningCollection* pTC, HTREEITEM parent, HTREEITEM insertAfter);
	HTREEITEM AddTreeItem(CTuning* pT, HTREEITEM parent, HTREEITEM insertAfter);

	void DeleteTreeItem(CTuning* pT);
	void DeleteTreeItem(CTuningCollection* pTC);

	// Check if item can be dropped here. If yes, the target collection is returned, otherwise nullptr.
	CTuningCollection *CanDrop(HTREEITEM dragDestItem);
	void OnEndDrag(HTREEITEM dragDestItem);

	//Returns pointer to the tuning collection where tuning given as argument
	//belongs to.
	CTuningCollection* GetpTuningCollection(const CTuning* const) const;

	//Returns the address of corresponding tuningcollection; if it points
	//to tuning-entry, returning the owning tuningcollection
	CTuningCollection* GetpTuningCollection(HTREEITEM ti) const;

	//Checks whether tuning collection can be deleted.
	bool IsDeletable(const CTuningCollection* const pTC) const;

	// Scl-file import.
	EnSclImport ImportScl(const mpt::PathString &filename, const mpt::ustring &name, std::unique_ptr<CTuning> & result);
	EnSclImport ImportScl(std::istream& iStrm, const mpt::ustring &name, std::unique_ptr<CTuning> & result);


private:

	CSoundFile & m_sndFile;

	CTuningRatioMapWnd m_RatioMapWnd;
	TUNINGVECTOR m_TuningCollections;
	std::vector<CTuningCollection*> m_DeletableTuningCollections;

	std::map<const CTuningCollection*, CString> m_TuningCollectionsNames;
	std::map<const CTuningCollection*, mpt::PathString> m_TuningCollectionsFilenames;

	CTuning* m_pActiveTuning;
	CTuningCollection* m_pActiveTuningCollection;

	CComboBox m_CombobTuningType;

	//Tuning Edits-->
	CEdit m_EditSteps;
	CNumberEdit m_EditRatioPeriod;
	CNumberEdit m_EditRatio;
	CEdit m_EditNotename;
	CEdit m_EditMiscActions;
	CEdit m_EditFineTuneSteps;
	CEdit m_EditName;
	//<--Tuning Edits

	CButton m_ButtonSet;
	CButton m_ButtonNew;
	CButton m_ButtonExport;
	CButton m_ButtonImport;
	CButton m_ButtonRemove;

	CTuningTreeCtrl m_TreeCtrlTuning;

private:
	using TUNINGTREEITEM = CTuningTreeItem;
	using TREETUNING_MAP = CBijectiveMap<HTREEITEM, TUNINGTREEITEM>;
	TREETUNING_MAP m_TreeItemTuningItemMap;

	TUNINGTREEITEM m_DragItem;
	TUNINGTREEITEM m_CommandItemSrc;
	TUNINGTREEITEM m_CommandItemDest;
	//Commanditem is used when receiving context menu-commands,
	//m_CommandItemDest is used when the command really need only
	//one argument.

	using MODIFIED_MAP = std::map<const CTuningCollection* const, bool>;
	MODIFIED_MAP m_ModifiedTCs;
	//If tuning collection seems to have been modified, its address
	//is added to this map.

	enum
	{
		TT_TUNINGCOLLECTION = 1,
		TT_TUNING
	};

	static CString GetSclImportFailureMsg(EnSclImport);
	static constexpr size_t s_nSclImportMaxNoteCount = 256;

	//To indicate whether to apply changes made to
	//those edit boxes(they are modified by certain activities
	//in case which the modifications should not be applied to
	//tuning data.
	bool m_NoteEditApply;
	bool m_RatioEditApply;

	enum
	{
		UM_TUNINGDATA = 1, //UM <-> Update Mask
		UM_TUNINGCOLLECTION = 2,
	};

	static const TUNINGTREEITEM s_notFoundItemTuning;
	static const HTREEITEM s_notFoundItemTree;

	bool AddTuning(CTuningCollection*, CTuning* pT);
	bool AddTuning(CTuningCollection*, Tuning::Type type);

	//Flag to prevent multiple exit error-messages.
	bool m_DoErrorExit;

	void DoErrorExit();

	virtual void OnOK();

//Treectrl context menu functions.
public:
	afx_msg void OnRemoveTuning();
	afx_msg void OnAddTuningGeneral();
	afx_msg void OnAddTuningGroupGeometric();
	afx_msg void OnAddTuningGeometric();
	afx_msg void OnCopyTuning();
	afx_msg void OnRemoveTuningCollection();

//Event-functions
public:
	afx_msg void OnEnChangeEditSteps();
	afx_msg void OnEnChangeEditRatioperiod();
	afx_msg void OnEnChangeEditNotename();
	afx_msg void OnBnClickedButtonSetvalues();
	afx_msg void OnEnChangeEditRatiovalue();
	afx_msg void OnBnClickedButtonNew();
	afx_msg void OnBnClickedButtonExport();
	afx_msg void OnBnClickedButtonImport();
	afx_msg void OnBnClickedButtonRemove();
	afx_msg void OnEnChangeEditFinetunesteps();
	afx_msg void OnEnKillfocusEditFinetunesteps();
	afx_msg void OnEnKillfocusEditName();
	afx_msg void OnEnKillfocusEditSteps();
	afx_msg void OnEnKillfocusEditRatioperiod();
	afx_msg void OnEnKillfocusEditRatiovalue();
	afx_msg void OnEnKillfocusEditNotename();

	//Treeview events
	afx_msg void OnTvnSelchangedTreeTuning(NMHDR *pNMHDR, LRESULT *pResult);
	afx_msg void OnTvnDeleteitemTreeTuning(NMHDR *pNMHDR, LRESULT *pResult);
	afx_msg void OnNMRclickTreeTuning(NMHDR *pNMHDR, LRESULT *pResult);
	afx_msg void OnTvnBegindragTreeTuning(NMHDR *pNMHDR, LRESULT *pResult);

	DECLARE_MESSAGE_MAP()
};

OPENMPT_NAMESPACE_END