// Screen.cpp: файл реализации
//

#include "stdafx.h"
#include "Screen.h"
#include "BKScreenD2D\BKScreenD2D.h"
#include "BKScreenD3D\BKScreenD3D.h"
#include "BKScreenDIB\BKScreenDIB.h"
#include "BKScreenOGL\BKScreenOGL.h"

const DWORD CScreen::ScreenView_ColorPalettes[16][4] =
{
	//                                                            Palette#     01           10          11
	Color_Black, Color_Blue, Color_Green, Color_Red,                // 00    синий   |   зеленый  |  красный
	Color_Black, Color_Yellow, Color_Magenta, Color_Red,            // 01   желтый   |  сиреневый |  красный
	Color_Black, Color_Cyan, Color_Blue, Color_Magenta,             // 02   голубой  |    синий   | сиреневый
	Color_Black, Color_Green, Color_Cyan, Color_Yellow,             // 03   зеленый  |   голубой  |  желтый
	Color_Black, Color_Magenta, Color_Cyan, Color_White,            // 04  сиреневый |   голубой  |   белый
	Color_Black, Color_White, Color_White, Color_White,             // 05    белый   |    белый   |   белый
	Color_Black, Color_DarkRed, Color_Brown, Color_Red,             // 06  темн-красн| красн-корич|  красный !!!
	Color_Black, Color_LightGreen, Color_Green, Color_Yellow,       // 07  салатовый | светл-зелен|  желтый  !!!
	Color_Black, Color_Violet, Color_VioletBlue, Color_Magenta,     // 08  фиолетовый| фиол-синий | сиреневый !!!
	Color_Black, Color_LightGreen, Color_VioletBlue, Color_Brown,   // 09 светл-зелен| фиол-синий |красн-корич !!!
	Color_Black, Color_Green, Color_Violet, Color_DarkRed,          // 10  салатовый | фиолетовый |темн-красный !!!
	Color_Black, Color_Cyan, Color_Yellow, Color_Red,               // 11   голубой  |   желтый   |  красный
	Color_Black, Color_Red, Color_Green, Color_Cyan,                // 12   красный  |   зеленый  |  голубой
	Color_Black, Color_Cyan, Color_Yellow, Color_White,             // 13   голубой  |   желтый   |   белый
	Color_Black, Color_Yellow, Color_Green, Color_White,            // 14   желтый   |   зеленый  |   белый
	Color_Black, Color_Cyan, Color_Green, Color_White               // 15   голубой  |   зеленый  |   белый
};

//0,1,2,3
const DWORD CScreen::MonochromePalette[2][4] =
{
	Color_Black, Color_White, Color_Black, Color_White,
	Color_Black, Color_Black, Color_White, Color_White
};

const DWORD CScreen::AdaptMonochromePalette[2][4] =
{
	Color_Black, 0x999999, 0xcccccc, Color_White,
	Color_Black, 0x999999, 0xcccccc, Color_White
};
const int CScreen::TEXTURE_WIDTH  = (SCREEN_WIDTH   *TEXTURE_MULTIPLER_X);
const int CScreen::TEXTURE_HEIGHT = (SCREEN_HEIGHT *TEXTURE_MULTIPLER_Y);

// const double CScreen::BK_ASPECT_RATIO = (1.0);
// const int CScreen::BK_SCREEN_HEIGHT = 512;
// const int CScreen::BK_SCREEN_WIDTH = (int)(BK_SCREEN_HEIGHT * BK_ASPECT_RATIO + 1);
const double CScreen::BK_ASPECT_RATIO  = (4.0 / 3.0);
const int CScreen::BK_SCREEN_WIDTH = (768);
const int CScreen::BK_SCREEN_HEIGHT = (int)(BK_SCREEN_WIDTH / BK_ASPECT_RATIO);

// CScreen

IMPLEMENT_DYNAMIC(CScreen, CWnd)

CScreen::CScreen(int nRenderType)
	: m_pscrSharedFunc(NULL)
	, hmod(NULL)
	, m_colTable32(NULL)
	, m_monTable32(NULL)
	, m_bExtended(FALSE)
	, m_bAdapt(FALSE)
	, m_bColorMode(TRUE)
	, m_nOfs(0330)
	, m_nPaletteNum(0)
	, m_nFrame(0)
	, m_curFPS(0)
{
	InitVars(nRenderType);
}

CScreen::CScreen(int nRenderType, BYTE *buffer, size_t size)
	: m_pBuffer(buffer)
	, m_nBufSize(size)
	, m_pscrSharedFunc(NULL)
	, hmod(NULL)
	, m_colTable32(NULL)
	, m_monTable32(NULL)
	, m_bExtended(FALSE)
	, m_bAdapt(FALSE)
	, m_bColorMode(TRUE)
	, m_nOfs(0330)
	, m_nPaletteNum(0)
	, m_nFrame(0)
	, m_curFPS(0)
{
	InitVars(nRenderType);
}

void CScreen::InitVars(int nRenderType)
{
	m_msec = GetTickCount();
	GETBKSCR pGetBKScr = NULL;

	switch (nRenderType)//Берём номер из конфига. 0 - OGL, 1 - D2D, 2 - DIB, 3 - D3D (глючный)
	{
		case 0:
		{
#ifdef _WIN64
			hmod = LoadLibrary(_T("BKScreenOGL_x64.dll"));
			pGetBKScr = (GETBKSCR) GetProcAddress(hmod, "GetBKScreen");
#else
			hmod = LoadLibrary(_T("BKScreenOGL.dll"));
			pGetBKScr = (GETBKSCR) GetProcAddress(hmod, "_GetBKScreen@0"); //нихера не помню, как делать правильно. поэтому так пока будет.
#endif

			if (pGetBKScr)
			{
				m_pscrSharedFunc = reinterpret_cast<CScreenOGL *>(pGetBKScr());
			}

			break;
		}

		case 1:
		{
#ifdef _WIN64
			hmod = LoadLibrary(_T("BKScreenD2D_x64.dll"));
			pGetBKScr = (GETBKSCR) GetProcAddress(hmod, "GetBKScreen");
#else
			hmod = LoadLibrary(_T("BKScreenD2D.dll"));
			pGetBKScr = (GETBKSCR) GetProcAddress(hmod, "_GetBKScreen@0");
#endif

			if (pGetBKScr)
			{
				m_pscrSharedFunc = reinterpret_cast<CScreenD2D *>(pGetBKScr());
			}

			break;
		}

		default:
		case 2:
		{
#ifdef _WIN64
			hmod = LoadLibrary(_T("BKScreenDIB_x64.dll"));
			pGetBKScr = (GETBKSCR) GetProcAddress(hmod, "GetBKScreen");
#else
			hmod = LoadLibrary(_T("BKScreenDIB.dll"));
			pGetBKScr = (GETBKSCR) GetProcAddress(hmod, "_GetBKScreen@0");
#endif

			if (pGetBKScr)
			{
				m_pscrSharedFunc = reinterpret_cast<CScreenDIB *>(pGetBKScr());
			}

			break;
		}

		case 3:
		{
#ifdef _WIN64
			hmod = LoadLibrary(_T("BKScreenD3D_x64.dll"));
			pGetBKScr = (GETBKSCR) GetProcAddress(hmod, "GetBKScreen");
#else
			hmod = LoadLibrary(_T("BKScreenD3D.dll"));
			pGetBKScr = (GETBKSCR) GetProcAddress(hmod, "_GetBKScreen@0");
#endif

			if (pGetBKScr)
			{
				m_pscrSharedFunc = reinterpret_cast<CScreenD3D *>(pGetBKScr());
			}

			break;
		}
	}

	if (m_pscrSharedFunc)
	{
		m_bReverseScreen = m_pscrSharedFunc->BKSS_GetReverseFlag();
	}
}

CScreen::~CScreen(void)
{
	ClearObjects();
}

void CScreen::ClearObjects()
{
	m_pscrSharedFunc->BKSS_ScreenView_Done();
	SAFE_DELETE_ARRAY(m_colTable32);
	SAFE_DELETE_ARRAY(m_monTable32);

	if (m_pscrSharedFunc)
	{
		delete m_pscrSharedFunc;
	}

	if (hmod)
	{
		FreeLibrary(hmod);
	}
}

BEGIN_MESSAGE_MAP(CScreen, CWnd)
	ON_WM_CREATE()
	ON_WM_SIZE()
	ON_WM_SETFOCUS()
	ON_WM_LBUTTONDOWN()
	ON_WM_ERASEBKGND()
END_MESSAGE_MAP()


// обработчики сообщений CScreen
BOOL CScreen::PreCreateWindow(CREATESTRUCT &cs)
{
	m_pwndParent = FromHandlePermanent(cs.hwndParent);
	cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_SAVEBITS,
	                                   ::LoadCursor(NULL, IDC_ARROW), HBRUSH(COLOR_WINDOW + 1), NULL);
	cs.style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
	return CWnd::PreCreateWindow(cs);
}

// обработчики сообщений CScreenDIB
int CScreen::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CWnd::OnCreate(lpCreateStruct) == -1)
	{
		return -1;
	}

	// создадим окно для полноэкранного режима
	m_DDWnd.CreateEx(
	    WS_EX_APPWINDOW, //расширенные стили
	    NULL, //имя класса
	    NULL, //имя окна
	    WS_POPUP | WS_CLIPSIBLINGS, //стили окна
	    CRect(0, 0, 0, 0), //позиция и размер окна
	    this, //родительское окно
	    0 //ID окна
	);

	if (m_pscrSharedFunc == NULL)
	{
		return -1;
	}

	InitColorTables();
	//заполняем параметры экрана.
	ScrsParams scrParam;
	scrParam.nTxX = TEXTURE_WIDTH;
	scrParam.nTxY = TEXTURE_HEIGHT;
	//рассчитаем размеры окна полноэкранного режима
	int wx, wy, dx, dy;
	HDC dc = ::GetDC(NULL);
	scrParam.rcFSDim.left = 0;
	scrParam.rcFSDim.top =  0;
	dx = scrParam.rcFSDim.right = GetDeviceCaps(dc, HORZRES);   //ширина экрана
	dy = scrParam.rcFSDim.bottom = GetDeviceCaps(dc, VERTRES);  //высота экрана
	wx = (int)((double)dy * BK_ASPECT_RATIO);  //ширина экрана при высоте dy в пропорциях 4/3
	wy = (int)((double)dx / BK_ASPECT_RATIO);  //высота экрана при ширине dx в пропорциях 4/3
	::ReleaseDC(NULL, dc);

	//рассчитаем размеры рисуемой картинки в полноэкранном режиме
	if (dx <= dy) //если монитор повёрнут на 90 градусов, или нестандартный - квадратный
	{
		if (dx < wx) //если не влазит по ширине, вписываем в ширину
		{
			wy = dy;
			wx = (int)((double)wy * BK_ASPECT_RATIO);
		}
		else
		{
			//то вписываем картинку в высоту
			wx = dx;
			wy = (int)((double)wx / BK_ASPECT_RATIO);
		}
	}
	else    //если монитор в обычном положении
	{
		if (dx < wx) //если не влазит по ширине, вписываем в ширину
		{
			wx = dx;
			wy = (int)((double)wx / BK_ASPECT_RATIO);
		}
		else
		{
			//то вписываем картинку в высоту
			wy = dy;
			wx = (int)((double)wy * BK_ASPECT_RATIO);
		}
	}

	scrParam.rcFSWnd.left = (dx - wx) / 2;   //выравниваем по центру
	scrParam.rcFSWnd.top = (dy - wy) / 2;
	scrParam.rcFSWnd.right = wx;
	scrParam.rcFSWnd.bottom = wy;
	scrParam.pBits = NULL;

	if (SUCCEEDED(m_pscrSharedFunc->BKSS_ScreenView_Init(scrParam, this, &m_DDWnd)))
	{
		m_pTexBits = scrParam.pBits;
		return 0;
	}
	else
	{
		ClearObjects();
		return -1;
	}
}

void CScreen::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);
	m_pscrSharedFunc->BKSS_OnSize(cx, cy);
}

void CScreen::OnSetFocus(CWnd *pOldWnd)
{
	AfxGetMainWnd()->SetFocus();
}

void CScreen::OnLButtonDown(UINT nFlags, CPoint point)
{
	AfxGetMainWnd()->SetFocus();
	CWnd::OnLButtonDown(nFlags, point);
}

BOOL CScreen::OnEraseBkgnd(CDC *pDC)
{
	return TRUE;
}

void CScreen::DrawScreen(BOOL bCheckFPS)
{
	if (m_pTexBits == NULL)
	{
		return;
	}

	if (m_lockChangeMode.IsLocked() || m_lockBusy.IsLocked())
	{
		return;
	}

	m_lockBusy.Lock();
	m_pscrSharedFunc->BKSS_DrawScreen();
	m_lockBusy.UnLock();

	if (bCheckFPS)
	{
		// Посчитаем FPS
		m_nFrame++;

		//GetTickCount64 - возвращает количество миллисекунд, прошедшее с момента старта системы
		//GetTickCount64 не работает на WinXP
		if ((m_mtmpsec = GetTickCount()) - m_msec >= 1000)
		{
			m_msec = m_mtmpsec;
			m_curFPS = m_nFrame;
			m_nFrame = 0;
		}
	}
}

BOOL CScreen::SetFullScreenMode()
{
	while (m_lockBusy.IsLocked() || m_lockPrepare.IsLocked());   //если выполняется отрисовка, то подождём, пока процедуры не завершатся

	m_lockChangeMode.Lock();
	BOOL bRet = m_pscrSharedFunc->BKSS_SetFullScreenMode();
	m_lockChangeMode.UnLock();
	return bRet;
}

BOOL CScreen::SetWindowMode()
{
	while (m_lockBusy.IsLocked() || m_lockPrepare.IsLocked());   //если выполняется отрисовка, то подождём, пока процедуры не завершатся

	m_lockChangeMode.Lock();
	BOOL bRet = m_pscrSharedFunc->BKSS_SetWindowMode();
	AdjustLayout();
	m_lockChangeMode.UnLock();
	return bRet;
}

BOOL CScreen::IsFullScreenMode()
{
	return m_pscrSharedFunc->BKSS_IsFullScreenMode();
}

HBITMAP CScreen::GetScreenshot()
{
	HBITMAP hBm = CreateBitmap(TEXTURE_WIDTH, TEXTURE_HEIGHT, 1, 32, m_pTexBits);
	return hBm;
}

void CScreen::SetColorMode(BOOL bColorMode)
{
	m_bColorMode = bColorMode;
	m_pscrSharedFunc->BKSS_SetColorMode();
}

void CScreen::PrepareScreenRGB32(BYTE *ScreenBuffer)
{
	if (m_pTexBits == NULL)
	{
		return;
	}

	while (m_lockChangeMode.IsLocked());

	m_lockPrepare.Lock();
	int scroll = (m_nOfs - 0330) & 0377; //(m_nOfs >= 0330) ? m_nOfs - 0330 : 050 + m_nOfs;
	BYTE *pVideo;
	DWORD *pBits, *pBitp, *pPalette;
	// Render to bitmap
	DWORD *pCurPalette = (m_bColorMode) ? m_colTable32 : m_monTable32;
	int linesToShow = m_bExtended ? SCREEN_HEIGHT / 4 : SCREEN_HEIGHT;

	for (int y = 0; y < linesToShow; ++y)
	{
		int yy = (y + scroll) & 0377;
		//указатель на начало строки
		pBitp = pBits = m_pTexBits + ((m_bReverseScreen ? y : (255 - y)) * TEXTURE_WIDTH * TEXTURE_MULTIPLER_Y);
		pVideo = ScreenBuffer + (yy * 0100);

		for (int x = 0; x < SCREEN_WIDTH / 8; ++x) //рисуем побайтно
		{
			pPalette = pCurPalette + (((m_nPaletteNum << 8) + *pVideo++) << 3);

			//один байт - это 8 бит
			for (int i = 0; i < 8; ++i)
			{
				//каждый бит надо повторить TEXTURE_MULTIPLER_X раз подряд.
				for (int u = 0; u < TEXTURE_MULTIPLER_X; ++u)
				{
					*pBits++ = *pPalette; //один бит - один дворд.
				}

				pPalette++;
			}
		}

		//теперь pBits - указатель на начало новой строки, а pBitp - указатель на начало предыдущей строки
		//скопируем строку столько раз, сколько надо будет
		for (int v = 1; v < TEXTURE_MULTIPLER_Y; ++v)
		{
			memcpy(pBits, pBitp, TEXTURE_WIDTH * sizeof(DWORD));
			pBits += TEXTURE_WIDTH;
			pBitp += TEXTURE_WIDTH;
		}
	}

	if (m_bExtended)
	{
		memset(m_pTexBits, 0, (TEXTURE_HEIGHT - TEXTURE_HEIGHT / 4) * TEXTURE_WIDTH * sizeof(DWORD));
	}

	m_lockPrepare.UnLock();
}

/*
на входе nLineNum, nByteNum - всегда с позиции начала экрана
независимо от скроллинга и режима РП
nLineNum всегда 0..0377
рассчитывать, что выводить надо тут.
*/
void CScreen::PrepareScreenLineByteRGB32(int nLineNum, int nByteNum, BYTE b)
{
	if (m_pTexBits == NULL)
	{
		return;
	}

	while (m_lockChangeMode.IsLocked());

	m_lockPrepare.Lock();
	int yy = (m_nOfs + 047 - nLineNum) & 0377;
	int screenline = 255 - yy;

	if (m_bReverseScreen)
	{
		yy = screenline;
	}

	DWORD *pBits = m_pTexBits + (yy * TEXTURE_MULTIPLER_Y * TEXTURE_WIDTH + nByteNum * 8 * TEXTURE_MULTIPLER_X);
	DWORD *pBitp = pBits; //указатель на начало строки
	DWORD *pCurPalette = (m_bColorMode) ? m_colTable32 : m_monTable32;

	if (m_bExtended && screenline >= (SCREEN_HEIGHT / 4))
	{
		//обнуляем
		for (int i = 0; i < 8; ++i)
		{
			for (int u = 0; u < TEXTURE_MULTIPLER_X; ++u)
			{
				*pBits++ = 0;
			}
		}
	}
	else
	{
		//копируем 1 байт
		DWORD *pPalette = pCurPalette + (((m_nPaletteNum << 8) + b) << 3);

		for (int i = 0; i < 8; ++i)
		{
			for (int u = 0; u < TEXTURE_MULTIPLER_X; ++u)
			{
				*pBits++ = *pPalette;
			}

			pPalette++;
		}
	}

	pBits = pBitp + TEXTURE_WIDTH;

	//размножим по вертикали
	for (int v = 1; v < TEXTURE_MULTIPLER_Y; ++v)
	{
		memcpy(pBits, pBitp, 8 * TEXTURE_MULTIPLER_X * sizeof(DWORD));
		pBits += TEXTURE_WIDTH;
		pBitp += TEXTURE_WIDTH;
	}

	m_lockPrepare.UnLock();
}


#define COLTABLE_SIZE (16*256*8)
//16 палитр, 256 вариантов значений байта, на каждый вариант - 8 DWORDов,

void CScreen::InitColorTables()
{
	while (m_lockPrepare.IsLocked());   //если выполняется подготовка, то подождём, пока процедуры не завершатся

	m_lockChangeMode.Lock();
	// Create color tables of all display modes for faster drawing
	SAFE_DELETE_ARRAY(m_colTable32);
	SAFE_DELETE_ARRAY(m_monTable32);
	m_colTable32 = new DWORD[COLTABLE_SIZE];
	m_monTable32 = new DWORD[COLTABLE_SIZE];

	for (int p = 0; p < 16; ++p)   //для каждой из 16 палитр
	{
		//вот это делает одну палитру. а нам надо таких 16.
		for (int i = 0; i < 256; ++i)   //для каждого значения байта (от 0 до 255) делаем маску, чтоб работать сразу с байтами и не заморачиваться с битами
		{
			DWORD *pColBuff32 = &m_colTable32[p * (256 * 8) + i * 8];
			DWORD *pMonBuff32 = &m_monTable32[p * (256 * 8) + i * 8];

			for (int n = 0; n < 4; ++n)   //в каждом байте 4 двухбития, на каждые два бита, у нас отводится по 4 дворда
			{
				int n2 = n * 2;

				switch ((i >> n2) & 3)
				{
					// Black
					case 0:
						pColBuff32[n2] = ScreenView_ColorPalettes[p][0];
						pMonBuff32[n2++] = (m_bAdapt) ? AdaptMonochromePalette[0][0] : MonochromePalette[0][0];
						pColBuff32[n2] = ScreenView_ColorPalettes[p][0];
						pMonBuff32[n2] = (m_bAdapt) ? AdaptMonochromePalette[1][0] : MonochromePalette[1][0];
						break;

					// Blue and Gray
					case 1:
						pColBuff32[n2] = ScreenView_ColorPalettes[p][1];
						pMonBuff32[n2++] = (m_bAdapt) ? AdaptMonochromePalette[0][1] : MonochromePalette[0][1];
						pColBuff32[n2] = ScreenView_ColorPalettes[p][1];
						pMonBuff32[n2] = (m_bAdapt) ? AdaptMonochromePalette[1][1] : MonochromePalette[1][1];
						break;

					// Green and Gray
					case 2:
						pColBuff32[n2] = ScreenView_ColorPalettes[p][2];
						pMonBuff32[n2++] = (m_bAdapt) ? AdaptMonochromePalette[0][2] : MonochromePalette[0][2];
						pColBuff32[n2] = ScreenView_ColorPalettes[p][2];
						pMonBuff32[n2] = (m_bAdapt) ? AdaptMonochromePalette[1][2] : MonochromePalette[1][2];
						break;

					// Red
					case 3:
						pColBuff32[n2] = ScreenView_ColorPalettes[p][3];
						pMonBuff32[n2++] = (m_bAdapt) ? AdaptMonochromePalette[0][3] : MonochromePalette[0][3];
						pColBuff32[n2] = ScreenView_ColorPalettes[p][3];
						pMonBuff32[n2] = (m_bAdapt) ? AdaptMonochromePalette[1][3] : MonochromePalette[1][3];
						break;
				}
			}
		} //end for(i)
	} //end for(p)

	m_lockChangeMode.UnLock();
}

void CScreen::AdjustLayout()
{
	if (m_pwndParent == NULL)
	{
		return;
	}

	CRect rcScreen(0, 0, BK_SCREEN_WIDTH, BK_SCREEN_HEIGHT);
	CRect rcNewScreen = rcScreen;
	CRect rcClient;
	m_pwndParent->GetClientRect(&rcClient);

	if (rcScreen.Width() * rcClient.Height() > rcClient.Width() * rcScreen.Height())
	{
		rcNewScreen.right = rcClient.Width();
		rcNewScreen.bottom = rcScreen.bottom * rcClient.Width() / rcScreen.right;
	}
	else
	{
		rcNewScreen.bottom = rcClient.Height();
		rcNewScreen.right = rcScreen.right * rcClient.Height() / rcScreen.bottom;
	}

	if (rcNewScreen.Width() > BK_SCREEN_WIDTH)
	{
		rcNewScreen.right = BK_SCREEN_WIDTH;
	}

	if (rcNewScreen.Height() > BK_SCREEN_HEIGHT)
	{
		rcNewScreen.bottom = BK_SCREEN_HEIGHT;
	}

	int x_offs = (rcClient.Width() - rcNewScreen.Width()) / 2;
	int y_offs = (rcClient.Height() - rcNewScreen.Height()) / 2;
	rcClient.right = rcClient.left + rcNewScreen.Width();
	rcClient.bottom = rcClient.top + rcNewScreen.Height();
	rcClient.OffsetRect(x_offs, y_offs);
	SetWindowPos(NULL, rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(), SWP_SHOWWINDOW | SWP_NOZORDER);
}
