// MultiEditListCtrl.cpp : implementation file
//

#include "pch.h"
#include "MultiEditListCtrl.h"
#include "Config.h"
#include "BKMessageBox.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CMultiEdit

CMultiEdit::CMultiEdit()
	: m_pListCtrl(nullptr),
	  m_nItem(0),
	  m_nSubItem(0)
{
}

CMultiEdit::~CMultiEdit()
{
}

BEGIN_MESSAGE_MAP(CMultiEdit, CEdit)
	ON_WM_KILLFOCUS()
	ON_WM_KEYDOWN()
	ON_WM_CHAR()
END_MESSAGE_MAP()


void CMultiEdit::Attach(CMultiEditListCtrl *pListCtrl, int nItem, int nSubItem)
{
	m_pListCtrl = pListCtrl;
	m_nItem = nItem;
	m_nSubItem = nSubItem;
}


void CMultiEdit::OnKillFocus(CWnd *pNewWnd)
{
	CEdit::OnKillFocus(pNewWnd);
// если закомментировать эти строки, то можно будет редактировать сразу несколько элементов.
	SetItem();
	delete this;
}


void CMultiEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if (nChar == VK_RETURN)
	{
		SetItem();
		delete this;
		return;
	}

	CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CMultiEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if (nChar == VK_RETURN)
	{
		return; // запрет обработки по-умолчанию
	}

	// запрещаем цифры 8 и 9
//  if (nChar == 0x38 || nChar == 0x39)
//  {
//      return;
//  }
// нельзя запрещать. иначе мы не можем редактировать всякие вещи
	CEdit::OnChar(nChar, nRepCnt, nFlags);
}


void CMultiEdit::SetItem()
{
	CString strText;
	GetWindowText(strText);
	auto pLVNotyfy = new NMLISTVIEW;

	if (pLVNotyfy)
	{
		ZeroMemory(pLVNotyfy, sizeof(NMLISTVIEW));
		pLVNotyfy->hdr.hwndFrom = m_pListCtrl->GetSafeHwnd();
		pLVNotyfy->hdr.idFrom = m_pListCtrl->GetDlgCtrlID();
		pLVNotyfy->hdr.code = LVN_ITEMCHANGING;
		pLVNotyfy->iItem = m_nItem;
		pLVNotyfy->iSubItem = m_nSubItem;
		pLVNotyfy->lParam = reinterpret_cast<LPARAM>(&strText); // эту строку можно передавать вот так, потому что SendMessage ждёт, пока
		// функция, которая там принимает эту строку, обработает её, и не использует на неё указатели.
		CWnd *pParent = m_pListCtrl->GetParent();
		ASSERT(pParent);

		if (pParent)
		{
			LRESULT bProtect = pParent->SendMessage(WM_NOTIFY, pLVNotyfy->hdr.idFrom, reinterpret_cast<LPARAM>(pLVNotyfy));

			if (!bProtect)
			{
				pLVNotyfy->hdr.code = LVN_ITEMCHANGED;
				pParent->SendMessage(WM_NOTIFY, pLVNotyfy->hdr.idFrom, reinterpret_cast<LPARAM>(pLVNotyfy));
			}
		}

		delete pLVNotyfy;
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
	}
}


/////////////////////////////////////////////////////////////////////////////
// CMultiEditListCtrl

CMultiEditListCtrl::CMultiEditListCtrl()
	: m_pEdit(nullptr)
	, m_bDigits(true) // принимаем только цифры по умолчанию
	, m_StdBgColor(0)
	, m_StdColor(0)
{
}

CMultiEditListCtrl::~CMultiEditListCtrl()
{
	ClearColorTable();
}


/////////////////////////////////////////////////////////////////////////////
// CMultiEditListCtrl message handlers

BEGIN_MESSAGE_MAP(CMultiEditListCtrl, CListCtrl)
	ON_NOTIFY_REFLECT_EX(NM_DBLCLK, &CMultiEditListCtrl::OnDblclk)
	ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CMultiEditListCtrl::OnCustomDraw)
	ON_NOTIFY_REFLECT_EX(LVN_INSERTITEM, &CMultiEditListCtrl::OnInsertitem)
	ON_NOTIFY_REFLECT_EX(LVN_DELETEITEM, &CMultiEditListCtrl::OnDeleteitem)
	ON_NOTIFY_REFLECT_EX(LVN_DELETEALLITEMS, &CMultiEditListCtrl::OnDeleteallitems)
END_MESSAGE_MAP()


void CMultiEditListCtrl::PreSubclassWindow()
{
	LOGFONT lfnt;
	GetFont()->GetLogFont(&lfnt);
	m_font.CreateFontIndirect(&lfnt);
	m_StdColor = GetSysColor(COLOR_BTNTEXT);
	m_StdBgColor = GetSysColor(COLOR_WINDOW);
	CListCtrl::PreSubclassWindow();
}


BOOL CMultiEditListCtrl::OnDblclk(NMHDR *pNMHDR, LRESULT *pResult)
{
	LVHITTESTINFO hti;
	// Get current click info (item, subitem, etc.)
	GetCursorPos(&hti.pt);
	ScreenToClient(&hti.pt);
	SubItemHitTest(&hti);

	if ((hti.flags & LVHT_ONITEMLABEL) && !(GetStyle() & LVS_EDITLABELS))
	{
		if (m_colorTable[hti.iItem][hti.iSubItem].bEditable)
		{
			m_colorTable[hti.iItem][hti.iSubItem].bEdited = true;
			// If label click - create edit ctrl for item
			CRect rcSubItem;
			GetSubItemRect(hti.iItem, hti.iSubItem, LVIR_LABEL, rcSubItem);
			ClientToScreen(&rcSubItem);
			m_pEdit = new CMultiEdit;

			if (m_pEdit)
			{
				DWORD dwStyle = WS_VISIBLE | WS_POPUP | WS_BORDER | ES_WANTRETURN | ES_MULTILINE;

				if (m_bDigits)
				{
					dwStyle |= ES_NUMBER;
				}

				m_pEdit->CreateEx(0, _T("edit"), nullptr, dwStyle, rcSubItem, this, 0);
				m_pEdit->SetFont(&m_font);
				m_pEdit->Attach(this, hti.iItem, hti.iSubItem);
				m_pEdit->SetWindowText(GetItemText(hti.iItem, hti.iSubItem));
				m_pEdit->SetFocus();
			}
			else
			{
				g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
			}
		}
	}

	*pResult = S_OK;
	return FALSE;
}

void CMultiEditListCtrl::SetEndEdit(int nItem, int nSubitem)
{
	m_colorTable[nItem][nSubitem].bEdited = false;
}


void CMultiEditListCtrl::OnCustomDraw(NMHDR *pNMHDR, LRESULT *pResult)
{
	auto pCustomDraw = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
	*pResult = CDRF_DODEFAULT;

	switch (pCustomDraw->nmcd.dwDrawStage)
	{
		case CDDS_PREPAINT:
			*pResult = CDRF_NOTIFYITEMDRAW;
			break;

		case CDDS_ITEMPREPAINT:
			*pResult = CDRF_NOTIFYSUBITEMDRAW;
			break;

		case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
		{
			ITEM_INFO &ii = m_colorTable[pCustomDraw->nmcd.dwItemSpec][pCustomDraw->iSubItem];
			pCustomDraw->clrText = ii.clrText;
			pCustomDraw->clrTextBk = ii.clrTextBk;
			// *pResult = CDRF_NEWFONT;
			break;
		}
	}
}


BOOL CMultiEditListCtrl::OnInsertitem(NMHDR *pNMHDR, LRESULT *pResult)
{
	auto pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
	int n = GetColumnNumber();
	auto ptr = new ITEM_INFO[n];

	if (ptr)
	{
		m_colorTable.SetAtGrow(pNMListView->iItem, ptr);

		for (int i = 0; i < n; ++i)
		{
			ITEM_INFO &p = m_colorTable[pNMListView->iItem][i];
			p.clrText = m_StdColor;
			p.clrTextBk = m_StdBgColor;
		}

		*pResult = S_OK;
	}
	else
	{
		g_BKMsgBox.Show(IDS_BK_ERROR_NOTENMEMR, MB_OK);
		*pResult = E_FAIL;
	}

	return FALSE;
}


BOOL CMultiEditListCtrl::OnDeleteitem(NMHDR *pNMHDR, LRESULT *pResult)
{
	auto pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
	int nItemCount = GetItemCount();
	int item = pNMListView->iItem;

	if (item < nItemCount)
	{
		if (item < m_colorTable.GetCount())
		{
			SAFE_DELETE_ARRAY(m_colorTable[item]);
			m_colorTable.RemoveAt(item);
		}
	}

	*pResult = S_OK;
	return FALSE;
}


BOOL CMultiEditListCtrl::OnDeleteallitems(NMHDR *pNMHDR, LRESULT *pResult)
{
	auto pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
	ClearColorTable();
	*pResult = S_OK;
	return FALSE;
}


void CMultiEditListCtrl::InitColorTable()
{
}


void CMultiEditListCtrl::ClearColorTable()
{
	for (int n = 0; n < m_colorTable.GetCount(); ++n)
	{
		SAFE_DELETE_ARRAY(m_colorTable[n]);
	}

	m_colorTable.RemoveAll();
}


int CMultiEditListCtrl::GetColumnNumber()
{
	return GetHeaderCtrl()->GetItemCount();
}


void CMultiEditListCtrl::EnableEdit(bool bEnable, int nItem, int nColumn)
{
	int nItemCount = GetItemCount();
	ASSERT(nItemCount - 1 >= nItem);

	if (nItemCount - 1 < nItem)
	{
		return;
	}

	int nColumnCount = GetColumnNumber();
	ASSERT(nColumnCount - 1 >= nColumn);

	if (nColumnCount - 1 < nColumn)
	{
		return;
	}

	m_colorTable[nItem][nColumn].bEditable = bEnable;
}


void CMultiEditListCtrl::EnableRowEdit(bool bEnable, int nRow)
{
	int nColumnCount = GetColumnNumber();

	for (int n = 0; n < nColumnCount; ++n)
	{
		EnableEdit(bEnable, nRow, n);
	}
}


void CMultiEditListCtrl::EnableColumnEdit(bool bEnable, int nColumn)
{
	int nRowCount = GetItemCount();

	for (int n = 0; n < nRowCount; ++n)
	{
		EnableEdit(bEnable, n, nColumn);
	}
}


void CMultiEditListCtrl::SetItemColor(COLORREF col, int nItem, int nColumn)
{
	int nItemCount = GetItemCount();
	ASSERT(nItemCount - 1 >= nItem);

	if (nItemCount - 1 < nItem)
	{
		return;
	}

	int nColumnCount = GetColumnNumber();
	ASSERT(nColumnCount - 1 >= nColumn);

	if (nColumnCount - 1 < nColumn)
	{
		return;
	}

	m_colorTable[nItem][nColumn].clrText = col;
}


void CMultiEditListCtrl::SetRowColor(COLORREF col, int nRow)
{
	int nColumnCount = GetColumnNumber();

	for (int n = 0; n < nColumnCount; ++n)
	{
		SetItemColor(col, nRow, n);
	}
}


void CMultiEditListCtrl::SetColumnColor(COLORREF col, int nColumn)
{
	int nRowCount = GetItemCount();

	for (int n = 0; n < nRowCount; ++n)
	{
		SetItemColor(col, n, nColumn);
	}
}


void CMultiEditListCtrl::SetItemBkColor(COLORREF col, int nItem, int nColumn)
{
	int nItemCount = GetItemCount();
	ASSERT(nItemCount - 1 >= nItem);

	if (nItemCount - 1 < nItem)
	{
		return;
	}

	int nColumnCount = GetColumnNumber();
	ASSERT(nColumnCount - 1 >= nColumn);

	if (nColumnCount - 1 < nColumn)
	{
		return;
	}

	m_colorTable[nItem][nColumn].clrTextBk = col;
}


void CMultiEditListCtrl::SetRowBkColor(COLORREF col, int nRow)
{
	int nColumnCount = GetColumnNumber();

	for (int n = 0; n < nColumnCount; ++n)
	{
		SetItemBkColor(col, nRow, n);
	}
}


void CMultiEditListCtrl::SetColumnBkColor(COLORREF col, int nColumn)
{
	int nRowCount = GetItemCount();

	for (int n = 0; n < nRowCount; ++n)
	{
		SetItemBkColor(col, n, nColumn);
	}
}


void CMultiEditListCtrl::SetItemWithModified(register uint16_t word, register int nItem, register int nSubitem, bool bColored)
{
	register ITEM_INFO *p = &(m_colorTable[nItem][nSubitem]);

	// если этот итем в данный момент редактируется, то ничего не делать
	if (p->bEdited)
	{
		return;
	}

	CString str;

	if (bColored)
	{
		// если значение изменилось
		if (p->nValue != word)
		{
			p->nValue = word;
			p->bModified = true; // пометим, что оно изменилось
			SetItemColor(MODIFIED_COLOR, nItem, nSubitem); // выведем его красным цветом
			::WordToOctString(word, str);
			SetItemText(nItem, nSubitem, str);
		}
		// если не менялось, но в прошлый раз было выведено красным цветом
		else if (p->bModified)
		{
			p->bModified = false; // и пометим, что больше не нужно ничего рисовать, пока значение снова не изменится
			SetItemColor(m_StdColor, nItem, nSubitem); // выведем его обычным цветом
			::WordToOctString(word, str);
			SetItemText(nItem, nSubitem, str);
		}

		// иначе - вообще не надо ничего выводить.
	}
	else
	{
		// если значение изменилось
		if (p->nValue != word)
		{
			p->nValue = word;
			p->bModified = false;
			// выведем его обычным цветом
			SetItemColor(m_StdColor, nItem, nSubitem);
			::WordToOctString(word, str);
			SetItemText(nItem, nSubitem, str);
		}

		// если не менялось - не выводим ничего
	}
}

void CMultiEditListCtrl::SetItemWithModifiedASCII(CString &str, register int nItem, register int nSubitem)
{
// недостаток метода - если в строке изменился хоть байт - подсвечивается вся строка
	register ITEM_INFO &p = m_colorTable[nItem][nSubitem];

	// если этот итем в данный момент редактируется, то ничего не делать
	if (p.bEdited)
	{
		return;
	}

	if (GetItemText(nItem, nSubitem) != str)
	{
		p.bModified = true; // пометим, что оно изменилось
		SetItemColor(MODIFIED_COLOR, nItem, nSubitem); // выведем его красным цветом
		SetItemText(nItem, nSubitem, str);
	}
	// если не менялось, но в прошлый раз было выведено красным цветом
	else if (p.bModified)
	{
		p.bModified = false; // пометим, что больше не нужно ничего рисовать, пока значение снова не изменится
		SetItemColor(m_StdColor, nItem, nSubitem); // выведем его обычным цветом
		SetItemText(nItem, nSubitem, str);
	}

	// иначе - вообще не надо ничего выводить.
}
