#include "stdafx.h"
#include "imp_view.h"
#include "FrmWnd.h"

#define __AXEHEIGHT		24
#define __AXEWIDTH		4
#define __AXEXSPACING	12
#define __AXEYSPACING	4
#define __CROSSSIZE		3
#define __CIRCLESIZE	3

#define __FONTHEIGHT	18

CImpView::CImpView(CFrmWnd *pParent)
{
	m_pParent = pParent;
	m_strXLabel = NULL;
	m_strYLabel = NULL;
	m_strGraphName = NULL;
	m_bXRange = FALSE;
	m_bYRange = FALSE;
}

CImpView::~CImpView()
{
	DeleteData();
}

BEGIN_MESSAGE_MAP(CImpView, CWnd)
ON_WM_PAINT()
ON_WM_HSCROLL()
ON_WM_VSCROLL()
ON_WM_MOUSEWHEEL()
ON_WM_SIZE()
END_MESSAGE_MAP()

void CImpView::OnPaint()
{
	CPaintDC dc(this); // device context for painting
	OnPrepareDC(&dc);
	OnDraw(&dc);
}

void CImpView::OnPrepareDC(CDC *pDC)
{
}

void CImpView::OnDraw(CDC *pDC)
{
	DWORD dwLayer = m_pParent->m_dwLayer;
	LPVIEWERDATA pData = m_pParent->m_pData;

	if ( pData && pData->ppData[dwLayer] && pData->dwDataLenArray[dwLayer] >= sizeof(PLOTFILEHEADER) && dwLayer < pData->dwLayers )
	{

			// soubor je typu PLOT
			RECT rc;
			GetClientRect(&rc);
			CDC dcComp;
			dcComp.CreateCompatibleDC(pDC);
			CBitmap bmpComp;
			bmpComp.CreateCompatibleBitmap(pDC, rc.right, rc.bottom);
			dcComp.SelectObject(bmpComp);

			DrawGraphs(&dcComp, rc.right, rc.bottom, (BYTE*)pData->ppData[dwLayer]);

			pDC->BitBlt(0, 0, rc.right, rc.bottom, &dcComp, 0, 0, SRCCOPY);
	}
}

void CImpView::UpdateScrollBars()
{
	DWORD dwLayer = m_pParent->m_dwLayer;
	LPVIEWERDATA pData = m_pParent->m_pData;

	if ( pData && dwLayer < pData->dwLayers && pData->ppData[dwLayer] )
	{
		if ( pData->dwDataLenArray[dwLayer] >= sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) )
		{
			BYTE* pBits = (BYTE*)pData->ppData[dwLayer];
			BITMAPINFOHEADER *bi = (BITMAPINFOHEADER*)&pBits[14];
		}
	}
}
void CImpView::OnSize(UINT nType, int cx, int cy)
{
	UpdateScrollBars();
	
	CWnd::OnSize(nType, cx, cy);
	Invalidate(FALSE);
	// TODO: Add your message handler code here
}

BOOL CImpView::DeleteData()
{
	while ( !m_mapXValues.empty() )
	{
		std::map<DWORD, DOUBLE*>::iterator it;
		it = m_mapXValues.begin();
		delete[](it->second);
		m_mapXValues.erase(it);
	}

	while ( !m_mapYValues.empty() )
	{
		std::map<DWORD, DOUBLE*>::iterator it;
		it = m_mapYValues.begin();
		delete[](it->second);
		m_mapYValues.erase(it);
	}

	while ( !m_mapLegend.empty() )
	{
		std::map<DWORD, LPWSTR>::iterator it;
		it = m_mapLegend.begin();
		delete[](it->second);
		m_mapLegend.erase(it);
	}

	if ( m_strXLabel )
		delete[](m_strXLabel);

	if ( m_strYLabel )
		delete[](m_strYLabel);

	if ( m_strGraphName )
		delete[](m_strGraphName);


	m_mapColor.clear();
	m_mapMark.clear();
	m_mapMinValues.clear();

	m_dMinX = 1e308;
	m_dMaxX = -1e308;
	m_dMinY = 1e308;
	m_dMaxY = -1e308;
	m_bestItemTypeX = ITEMTYPE_BYTE;
	m_bestItemTypeY = ITEMTYPE_BYTE;
	m_dwGraphs = 0;

	m_bXRange = FALSE;
	m_bYRange = FALSE;

	return TRUE;
}

BOOL CImpView::LoadData(BYTE *pData)
{
	DeleteData();

	DWORD dwGraph = 0;

	while ( memcmp(((PLOTFILEHEADER*)pData)->type, "plot", 4) == 0 )
	{
		pData += sizeof(PLOTFILEHEADER);
		PLOTHEADER* ph = (PLOTHEADER*)pData;

		while ( ph->dataType != DATATYPE_EOP )
		{
			DWORD dwDataLen = ph->dwDataLen;
			pData = (BYTE*)&ph[1]; // premisteni za hlavicku

			// switch typu dat

			switch ( ph->dataType ) 
			{
			case DATATYPE_XVALUES:
				if ( ph->itemType > m_bestItemTypeX )
					m_bestItemTypeX = ph->itemType;
				goto position;
			case DATATYPE_YVALUES:
				if ( ph->itemType > m_bestItemTypeY )
					m_bestItemTypeY = ph->itemType;
position:		{
					double *d = new double[dwDataLen];
					switch ( ph->itemType )
					{
					case ITEMTYPE_BYTE:
						for ( DWORD i = 0; i < dwDataLen; i++ )
						{
							d[i] = *(BYTE*)pData;
							pData += sizeof(BYTE);
							switch ( ph->dataType )
							{
							case DATATYPE_XVALUES:
								if ( d[i] < m_dMinX )
									m_dMinX = d[i];
								else if ( d[i] > m_dMaxX )
									m_dMaxX = d[i];
								break;
							case DATATYPE_YVALUES:
								if ( d[i] < m_dMinY )
									m_dMinY = d[i];
								else if ( d[i] > m_dMaxY )
									m_dMaxY = d[i];
								break;
							}
						}
						break;
					case ITEMTYPE_WORD:
						for ( DWORD i = 0; i < dwDataLen; i++ )
						{
							d[i] = *(WORD*)pData;
							pData += sizeof(WORD);
							switch ( ph->dataType )
							{
							case DATATYPE_XVALUES:
								if ( d[i] < m_dMinX )
									m_dMinX = d[i];
								else if ( d[i] > m_dMaxX )
									m_dMaxX = d[i];
								break;
							case DATATYPE_YVALUES:
								if ( d[i] < m_dMinY )
									m_dMinY = d[i];
								else if ( d[i] > m_dMaxY )
									m_dMaxY = d[i];
								break;
							}
						}
						break;
					case ITEMTYPE_DWORD:
						for ( DWORD i = 0; i < dwDataLen; i++ )
						{
							d[i] = *(DWORD*)pData;
							pData += sizeof(DWORD);
							switch ( ph->dataType )
							{
							case DATATYPE_XVALUES:
								if ( d[i] < m_dMinX )
									m_dMinX = d[i];
								else if ( d[i] > m_dMaxX )
									m_dMaxX = d[i];
								break;
							case DATATYPE_YVALUES:
								if ( d[i] < m_dMinY )
									m_dMinY = d[i];
								else if ( d[i] > m_dMaxY )
									m_dMaxY = d[i];
								break;
							}
						}
						break;
					case ITEMTYPE_FLOAT:
						for ( DWORD i = 0; i < dwDataLen; i++ )
						{
							d[i] = *(FLOAT*)pData;
							pData += sizeof(FLOAT);
							switch ( ph->dataType )
							{
							case DATATYPE_XVALUES:
								if ( d[i] < m_dMinX )
									m_dMinX = d[i];
								else if ( d[i] > m_dMaxX )
									m_dMaxX = d[i];
								break;
							case DATATYPE_YVALUES:
								if ( d[i] < m_dMinY )
									m_dMinY = d[i];
								else if ( d[i] > m_dMaxY )
									m_dMaxY = d[i];
								break;
							}
						}
						break;
					case ITEMTYPE_DOUBLE:
						for ( DWORD i = 0; i < dwDataLen; i++ )
						{
							d[i] = *(DOUBLE*)pData;
							pData += sizeof(DOUBLE);
							switch ( ph->dataType )
							{
							case DATATYPE_XVALUES:
								if ( d[i] < m_dMinX )
									m_dMinX = d[i];
								else if ( d[i] > m_dMaxX )
									m_dMaxX = d[i];
								break;
							case DATATYPE_YVALUES:
								if ( d[i] < m_dMinY )
									m_dMinY = d[i];
								else if ( d[i] > m_dMaxY )
									m_dMaxY = d[i];
								break;
							}
						}
						break;
					}
					if ( ph->dataType == DATATYPE_XVALUES )
					{
						m_mapXValues.insert(std::pair<DWORD, DOUBLE*>(dwGraph, d));
					}
					else if ( ph->dataType == DATATYPE_YVALUES )
					{
						m_mapYValues.insert(std::pair<DWORD, DOUBLE*>(dwGraph, d));
					}

					// zapis poctu hodnot (pouze nejmensi pocet hodnot se uvazuje)
					std::map<DWORD, DWORD>::iterator it;
					if ( (it = m_mapMinValues.find(dwGraph)) != m_mapMinValues.end() )
					{
						if ( ph->dwDataLen < it->second )
							m_mapMinValues.insert(std::pair<DWORD, DWORD>(dwGraph, ph->dwDataLen));
					}
					else 
						m_mapMinValues.insert(std::pair<DWORD, DWORD>(dwGraph, ph->dwDataLen));

				}
				break;
			case DATATYPE_COLOR:
				m_mapColor.insert(std::pair<DWORD, COLORREF>(dwGraph, *(COLORREF*)pData));
				pData += sizeof(COLORREF);
				break;
			case DATATYPE_MARK:
				m_mapMark.insert(std::pair<DWORD, BYTE>(dwGraph, *(BYTE*)pData));
				pData += sizeof(BYTE);
				break;
			case DATATYPE_LEGEND:
				{
					DWORD dwTextLen = ph->dwDataLen;
					WCHAR *pText = new WCHAR[dwTextLen + 1];
					memcpy(pText, pData, sizeof(WCHAR) * dwTextLen);
					pText[dwTextLen] = 0x0000;
					m_mapLegend.insert(std::pair<DWORD, LPWSTR>(dwGraph, pText));
					pData += sizeof(WCHAR) * dwTextLen;
				}
				break;
			}

			// pData musi byt za daty - teda na nove hlavicce plotu
			ph = (PLOTHEADER*)pData;
		}
		dwGraph++;
		pData += sizeof(PLOTHEADER);
	}
	if ( memcmp(((PLOTFILEHEADER*)pData)->type, "glbl", 4) == 0 )
	{
		pData += sizeof(PLOTFILEHEADER);
		PLOTHEADER* ph = (PLOTHEADER*)pData;

		while ( ph->dataType != DATATYPE_EOP )
		{
			DWORD dwDataLen = ph->dwDataLen;
			pData = (BYTE*)&ph[1]; // premisteni za hlavicku
			
			switch ( ph->dataType )
			{
			case DATATYPE_GRAPHNAME:
				{
					DWORD dwTextLen = ph->dwDataLen;
					WCHAR *pText = new WCHAR[dwTextLen + 1];
					memcpy(pText, pData, sizeof(WCHAR) * dwTextLen);
					pText[dwTextLen] = 0x0000;
					m_strGraphName = pText;
					//m_mapGraphName.insert(std::pair<DWORD, LPWSTR>(dwGraph, pText));
					pData += sizeof(WCHAR) * dwTextLen;
				}
				break;
			case DATATYPE_XLABEL:
				{
					DWORD dwTextLen = ph->dwDataLen;
					WCHAR *pText = new WCHAR[dwTextLen + 1];
					memcpy(pText, pData, sizeof(WCHAR) * dwTextLen);
					pText[dwTextLen] = 0x0000;
					m_strXLabel = pText;
					//m_mapXLabel.insert(std::pair<DWORD, LPWSTR>(dwGraph, pText));
					pData += sizeof(WCHAR) * dwTextLen;
				}
				break;
			case DATATYPE_YLABEL:
				{
					DWORD dwTextLen = ph->dwDataLen;
					WCHAR *pText = new WCHAR[dwTextLen + 1];
					memcpy(pText, pData, sizeof(WCHAR) * dwTextLen);
					pText[dwTextLen] = 0x0000;
					m_strYLabel = pText;
					//m_mapYLabel.insert(std::pair<DWORD, LPWSTR>(dwGraph, pText));
					pData += sizeof(WCHAR) * dwTextLen;
				}
				break;
			case DATATYPE_XRANGE:
				ASSERT(ph->dwDataLen == 2);
				ASSERT(ph->itemType == ITEMTYPE_DOUBLE);
				memcpy(&m_dMinX, pData, sizeof(DOUBLE));
				pData += sizeof(DOUBLE);
				memcpy(&m_dMaxX, pData, sizeof(DOUBLE));
				pData += sizeof(DOUBLE);
				m_bXRange = TRUE;
				break;
			case DATATYPE_YRANGE:
				ASSERT(ph->dwDataLen == 2);
				ASSERT(ph->itemType == ITEMTYPE_DOUBLE);
				memcpy(&m_dMinY, pData, sizeof(DOUBLE));
				pData += sizeof(DOUBLE);
				memcpy(&m_dMaxY, pData, sizeof(DOUBLE));
				pData += sizeof(DOUBLE);
				m_bYRange = TRUE;
				break;
			}
			ph = (PLOTHEADER*)pData;
		}
	}
 	m_dwGraphs = dwGraph;
	return TRUE;
}

BOOL CImpView::DrawGraphs(CDC *pDC, DWORD dwWidth, DWORD dwHeight, BYTE* pData)
{
	PLOTHEADER *ph = (PLOTHEADER*)pData;
	CBitmap* pBitmap = pDC->GetCurrentBitmap();



	LoadData(pData);	// tady se zjisti minima a maxima hodnot na osach a 
						// data se zkopiruji do map

//	DWORD dwMinValues = -1;

	CString strFmtX = L"%.5f";
	CString strFmtY = L"%.5f";

	// volba vhodnych formatu cisel pro popisky os

	switch ( m_bestItemTypeX )
	{
	case ITEMTYPE_BYTE:
	case ITEMTYPE_WORD:
	case ITEMTYPE_DWORD:
		strFmtX = L"%.0f";
		break;
	case ITEMTYPE_FLOAT:
		strFmtX = L"%.2f";
		break;
	default:
		break;
	}

	switch ( m_bestItemTypeY )
	{
	case ITEMTYPE_BYTE:
	case ITEMTYPE_WORD:
	case ITEMTYPE_DWORD:
		strFmtY = L"%.0f";
		break;
	case ITEMTYPE_FLOAT:
		strFmtY = L"%.2f";
		break;
	default:
		break;
	}

	double dRangeY = m_dMaxY - m_dMinY;
	double dRangeX = m_dMaxX - m_dMinX;

	// OSA Y

	int y = dwHeight - __AXEHEIGHT;
	DWORD dwMaxTextHeight = 0, dwMaxTextWidth = 0;
	while ( y > 0 )
	{
		CString txt;
		txt.Format(strFmtY, m_dMinY + (dRangeY * ((double)(dwHeight - __AXEHEIGHT - y) / dwHeight)));
		CSize sz = pDC->GetOutputTextExtent(txt);
		if ( (DWORD)sz.cx > dwMaxTextWidth )
			dwMaxTextWidth = sz.cx;
		if ( (DWORD)sz.cy > dwMaxTextHeight )
			dwMaxTextHeight = sz.cy;

		y -= dwMaxTextHeight + __AXEYSPACING;
	}

	pDC->SelectStockObject(WHITE_PEN);
	pDC->Rectangle(0, 0, __AXEWIDTH + dwMaxTextWidth, dwHeight - __AXEHEIGHT);

	pDC->SelectStockObject(BLACK_PEN);

	y = dwHeight - __AXEHEIGHT;
	while ( y > 0 )
	{
		CString txt;
		txt.Format(strFmtY, m_dMinY + (dRangeY * ((double)(dwHeight - __AXEHEIGHT - y) / dwHeight)));
		CSize sz = pDC->GetOutputTextExtent(txt);
		if ( dwMaxTextWidth/2 + y <= dwHeight - __AXEHEIGHT && y - (int)dwMaxTextHeight/2 >= 0 )
		{
			pDC->MoveTo(dwMaxTextWidth, y);
			pDC->LineTo(dwMaxTextWidth + __AXEWIDTH, y);
			pDC->TextOut(0, y - dwMaxTextHeight/2, txt);
		}

		y -= dwMaxTextHeight + __AXEYSPACING;
	}

	// OSA X

	DWORD dwXOffset = dwMaxTextWidth + __AXEWIDTH;

	pDC->SelectStockObject(WHITE_PEN);
	pDC->Rectangle(dwXOffset, dwHeight - __AXEHEIGHT, dwWidth, dwHeight);

	pDC->SelectStockObject(BLACK_PEN);

	DWORD x = dwXOffset;
	dwMaxTextWidth = 0;
	while ( x < dwWidth )
	{
		CString txt;
		txt.Format(strFmtX, m_dMinX + (dRangeX * ((double)(x-dwXOffset) / (dwWidth-dwXOffset))));
		CSize sz = pDC->GetOutputTextExtent(txt);
		if ( (DWORD)sz.cx > dwMaxTextWidth )
			dwMaxTextWidth = sz.cx;
		x+= dwMaxTextWidth + __AXEXSPACING;
	}

	y = dwHeight - __AXEHEIGHT;
	x = dwXOffset;
	while ( x < dwWidth )
	{
		CString txt;
		txt.Format(strFmtX, m_dMinX + (dRangeX * ((double)(x-dwXOffset) / (dwWidth-dwXOffset))));
		CSize sz = pDC->GetOutputTextExtent(txt);
		if ( dwMaxTextWidth/2 + x <= dwWidth && (int)x - dwMaxTextWidth/2 > dwXOffset )
		{
			pDC->MoveTo(x, dwHeight-__FONTHEIGHT);
			pDC->LineTo(x, dwHeight-__AXEHEIGHT);
			pDC->TextOut(x-sz.cx/2, dwHeight-__FONTHEIGHT, txt);
		}

		x+= dwMaxTextWidth + __AXEXSPACING;
	}

	for ( DWORD dwGraph = 0; dwGraph < m_dwGraphs; dwGraph++ )
	{
		std::map<DWORD, DOUBLE*>::iterator itX, itY;
		std::map<DWORD, DWORD>::iterator itMV;
		itX = m_mapXValues.find(dwGraph);
		itY = m_mapYValues.find(dwGraph);
		itMV = m_mapMinValues.find(dwGraph);

		COLORREF cr;
		std::map<DWORD, COLORREF>::iterator itColor = m_mapColor.find(dwGraph);
		if ( itColor != m_mapColor.end() )
			cr = itColor->second;
		else
			cr = RGB(0xff, 0xff, 0xff);

		char mark;
		std::map<DWORD, BYTE>::iterator itMark = m_mapMark.find(dwGraph);
		if ( itMark != m_mapMark.end() )
			mark = itMark->second;
		else
			mark = '.';

		CPen pen(PS_SOLID, 1, cr);
		CPen *pPenOld = pDC->SelectObject(&pen);
		CGdiObject* pBrushOld = pDC->SelectStockObject(NULL_BRUSH);

		if ( itMV != m_mapMinValues.end() )
		{
			DWORD dwMinValues = itMV->second;
			if ( itX != m_mapXValues.end() && itY != m_mapYValues.end() )
			{	// obe souradnice jsou zadany
				double *pdXValues = itX->second, *pdYValues = itY->second;

				switch ( mark )
				{
				case '.':
					for ( DWORD i = 0; i < dwMinValues; i++ )
					{
						pDC->SetPixel((int)(dwXOffset + (pdXValues[i] - m_dMinX) / dRangeX * (dwWidth - dwXOffset - 1)),
							(int)(dwHeight - 1 - __AXEHEIGHT - (pdYValues[i] - m_dMinY) / dRangeY * (dwHeight - __AXEHEIGHT - 1)), cr);
					}
					break;
				case 'x':
					for ( DWORD i = 0; i < dwMinValues; i++ )
					{
						int x = (int)(dwXOffset + (pdXValues[i] - m_dMinX) / dRangeX * (dwWidth - dwXOffset - 1));
						int y = (int)(dwHeight - 1 - __AXEHEIGHT - (pdYValues[i] - m_dMinY) / dRangeY * (dwHeight - __AXEHEIGHT - 1));
						pDC->MoveTo(x - __CROSSSIZE, y - __CROSSSIZE);
						pDC->LineTo(x + __CROSSSIZE+1, y + __CROSSSIZE+1);
						pDC->MoveTo(x + __CROSSSIZE, y - __CROSSSIZE);
						pDC->LineTo(x - __CROSSSIZE-1, y + __CROSSSIZE+1);
					}
					break;
				case '+':
					for ( DWORD i = 0; i < dwMinValues; i++ )
					{
						int x = (int)(dwXOffset + (pdXValues[i] - m_dMinX) / dRangeX * (dwWidth - dwXOffset - 1));
						int y = (int)(dwHeight - 1 - __AXEHEIGHT - (pdYValues[i] - m_dMinY) / dRangeY * (dwHeight - __AXEHEIGHT - 1));
						pDC->MoveTo(x - __CROSSSIZE, y);
						pDC->LineTo(x + __CROSSSIZE+1, y);
						pDC->MoveTo(x, y - __CROSSSIZE);
						pDC->LineTo(x, y + __CROSSSIZE+1);
					}
					break;
				case 'o':
					for ( DWORD i = 0; i < dwMinValues; i++ )
					{
						int x = (int)(dwXOffset + (pdXValues[i] - m_dMinX) / dRangeX * (dwWidth - dwXOffset - 1));
						int y = (int)(dwHeight - 1 - __AXEHEIGHT - (pdYValues[i] - m_dMinY) / dRangeY * (dwHeight - __AXEHEIGHT - 1));
						pDC->Ellipse(x-__CIRCLESIZE, y-__CIRCLESIZE, x+__CIRCLESIZE+1, y+__CIRCLESIZE+1);
						//pDC->SetPixel(x, y, RGB(255,255,255));
					}
					break;
				case '-':
					{
						int x = (int)(dwXOffset + (pdXValues[0] - m_dMinX) / dRangeX * (dwWidth - dwXOffset - 1));
						int y = (int)(dwHeight - 1 - __AXEHEIGHT - (pdYValues[0] - m_dMinY) / dRangeY * (dwHeight - __AXEHEIGHT - 1));
						pDC->MoveTo(x, y);
						for ( DWORD i = 1; i < dwMinValues; i++ )
						{
							x = (int)(dwXOffset + (pdXValues[i] - m_dMinX) / dRangeX * (dwWidth - dwXOffset - 1));
							y = (int)(dwHeight - 1 - __AXEHEIGHT - (pdYValues[i] - m_dMinY) / dRangeY * (dwHeight - __AXEHEIGHT - 1));							
							pDC->LineTo(x, y);
							//pDC->SetPixel(x, y, RGB(255,255,255));
						}
					}
					break;
				}
			}
			else if ( itX != m_mapXValues.end() )
			{ // zadany pouze souradnice X
				double *pdXValues = itX->second;

				for ( DWORD i = 0; i < dwMinValues; i++ )
				{
					int x = (int)(dwXOffset + (pdXValues[i] - m_dMinX) / dRangeX * (dwWidth - dwXOffset - 1));
					pDC->MoveTo(x, 0);
					pDC->LineTo(x, dwHeight - __AXEHEIGHT - 1);
				}
			}
		}
		pDC->SelectObject(pBrushOld);
		pDC->SelectObject(pPenOld);
	}

	y = 0;
	for ( DWORD dwGraph = 0; dwGraph < m_dwGraphs; dwGraph++ )
	{
		std::map<DWORD, LPWSTR>::iterator itLegend;
		itLegend = m_mapLegend.find(dwGraph);
		char mark;

		std::map<DWORD, BYTE>::iterator itMark = m_mapMark.find(dwGraph);
		if ( itMark != m_mapMark.end() )
			mark = itMark->second;
		else
			mark = '.';

		if ( itLegend != m_mapLegend.end() )
		{
			COLORREF cr;
			std::map<DWORD, COLORREF>::iterator itColor = m_mapColor.find(dwGraph);
			if ( itColor != m_mapColor.end() )
				cr = itColor->second;
			else
				cr = RGB(0xff, 0xff, 0xff);

			pDC->SetTextColor(cr);
			pDC->SetBkMode(TRANSPARENT);

			CString txt;
			txt.Format(L"%c - %s", mark, itLegend->second);

			pDC->DrawText(txt, -1, CRect(0, y, dwWidth, y + 16), DT_RIGHT);
			y+=16;
		}
	}
	return TRUE;
}