#include "stdafx.h"
#include "7ZSfxModInt.h"
#include "Splash.h"

using namespace Gdiplus;

#pragma comment (lib, "gdiplus.lib")
#define ID_GIF_DETERMINATE_TIMER 0x1

bool IsRTLLocale()
{
	unsigned int rtlLocales[] = { 1025, 1037 };
	std::vector<unsigned int> rtlLocalesVector(rtlLocales, rtlLocales + 2);
	LANGID langId = ::GetUserDefaultUILanguage();
	std::vector<unsigned int>::iterator it = std::find(rtlLocalesVector.begin(), rtlLocalesVector.end(), langId);
	if (it != rtlLocalesVector.end())
		return true;
	else
		return false;
}

SplashCore::SplashCore(const HINSTANCE&)
{
	//do nothing	
}

void SplashCore::init(int& guiMode)
{
	//do nothing
}

bool SplashCore::CheckShowSplashScreen(void)
{
	return false;
}

bool SplashCore::CreateSplashScreen()
{
	return false;
}

void SplashCore::SetSelfUpdateMode(bool)
{
	// do nothing
}

void SplashCore::CloseSplashScreen()
{
	//do nothing
}

Splash::Splash(const HINSTANCE& hInstance) : SplashCore(hInstance), m_CChWnd(NULL), m_pImage(NULL), m_gdiplusToken(NULL),
m_pSpinner(NULL), m_bCustomSplashScreen(0), m_bUseQuadroSplash(false), m_bIsSelfUpdate(false)
{
	m_hInstance = hInstance;

#ifdef USE_QUADRO_SPLASH
	m_bUseQuadroSplash = true;
#endif

	Splash::m_pSplashObject = NULL;
}

void Splash::SetupLocalizedStrings()
{
	LANGID langId = ::GetUserDefaultUILanguage();

	switch (langId)
	{
	case 0x401:
		versionText = L"إصدار";
		updatingText = L"التحديث";
		break;
	case 0x404:
		versionText = L"版本";
		updatingText = L"正在更新";
		break;
	case 0x405:
		versionText = L"Verze";
		updatingText = L"Probíhá aktualizace";
		break;
	case 0x406:
		versionText = L"Version";
		updatingText = L"Opdaterer";
		break;
	case 0x407:
		versionText = L"Version";
		updatingText = L"Aktualisierung von";
		break;
	case 0x408:
		versionText = L"Έκδοση";
		updatingText = L"Ενημέρωση";
		break;
	case 0x409:
		versionText = L"Version";
		updatingText = L"Updating";
		break;
	case 0x40a:
		versionText = L"Versión";
		updatingText = L"Actualizando";
		break;
	case 0x40b:
		versionText = L"Versio";
		updatingText = L"Päivittää:";
		break;
	case 0x40c:
		versionText = L"Version";
		updatingText = L"Mise à jour en cours";
		break;
	case 0x40d:
		versionText = L"גרסה";
		updatingText = L"מעדכן את";
		break;
	case 0x40e:
		versionText = L"Verzió";
		updatingText = L"frissítése";
		break;
	case 0x410:
		versionText = L"Versione";
		updatingText = L"Aggiornamento di";
		break;
	case 0x411:
		versionText = L"バージョン";
		updatingText = L"アップデート";
		break;
	case 0x412:
		versionText = L"버전";
		updatingText = L"업데이트";
		break;
	case 0x413:
		versionText = L"Versie";
		updatingText = L"Bijwerken";
		break;
	case 0x414:
		versionText = L"Versjon";
		updatingText = L"Oppdaterer";
		break;
	case 0x415:
		versionText = L"Wersja";
		updatingText = L"Aktualizowanie";
		break;
	case 0x416:
		versionText = L"Versão";
		updatingText = L"Atualizando";
		break;
	case 0x419:
		versionText = L"Версия";
		updatingText = L"Идет обновление";
		break;
	case 0x41b:
		versionText = L"Verzia";
		updatingText = L"Aktualizuje sa";
		break;
	case 0x41d:
		versionText = L"Version";
		updatingText = L"Uppdaterar";
		break;
	case 0x41e:
		versionText = L"รุ่น";
		updatingText = L"กำลังอัพเดต";
		break;
	case 0x41f:
		versionText = L"Sürüm";
		updatingText = L"güncelleştiriliyor";
		break;
	case 0x424:
		versionText = L"Različica";
		updatingText = L"Posodabljanje";
		break;
	case 0x804:
		versionText = L"版本";
		updatingText = L"更新";
		break;
	case 0x809:
		versionText = L"Version";
		updatingText = L"Updating";
		break;
	case 0x80a:
		versionText = L"Versión";
		updatingText = L"Actualizando";
		break;
	case 0x816:
		versionText = L"Versión";
		updatingText = L"A actualizar o";
		break;
	default:
		versionText = L"Version";
		updatingText = L"Updating";
	}
}

void Splash::init(int& guiMode)
{
	if (guiMode == GUIMODE_HIDDEN)
	{
		LOG(Info, "No splash screen");
		m_bCustomSplashScreen = 0;
	}
	else
	{
		SetupLocalizedStrings();
		LOG(Info, "Will show splash screen");
		m_bCustomSplashScreen = 1;
		guiMode = GUIMODE_HIDDEN;
		if (IsRTLLocale())
		{
			SetRTLLayout(true);
		}
	}

	m_pSplashObject = this;
}

void Splash::SetSelfUpdateMode(bool val)
{
	m_bIsSelfUpdate = val;
}

bool Splash::IsSelfUpdateMode()
{
	return m_bIsSelfUpdate;
}

bool Splash::CreateSplashScreen()
{
	HANDLE hThread = NULL;

	if (m_bCustomSplashScreen == 1)
	{
		DWORD myThreadID;
		hThread = CreateThread(NULL, 0, CreateCustomSplashScreen, NULL, 0, &myThreadID);

		if (hThread != NULL)
		{
			LOG(Info, "Successfully created splash screen thread");
		}
		else
		{
			LOG(Warning, "Couldn't create custom splash screen thread");
		}
	}

	return hThread != NULL;
}

bool Splash::CheckShowSplashScreen()
{
	return true;
}

void Splash::CloseSplashScreen()
{
	if (m_bCustomSplashScreen == 1)
	{
		SendMessage(m_CChWnd, WM_CLOSE, NULL, NULL);
	}
}

bool Splash::LoadSpinner(HWND hWnd)
{
#ifdef NOUAC
	//No need to show spinner.
	//return true;
#endif

	if (m_pSpinner != NULL)
	{
		return true;
	}
	LOG(Info, "Initializing spinner animation.");
	HGLOBAL		hGlobal;
	LPSTREAM	pStream;
	HRSRC	hRsrc = FindResource(NULL, MAKEINTRESOURCE(IDR_GIF2), L"GIF");
	HGLOBAL	hGlob1 = LoadResource(NULL, hRsrc);

	int	size = SizeofResource(NULL, hRsrc);
	hGlobal = GlobalAlloc(GMEM_FIXED, size);
	LPVOID	resPtr = LockResource(hGlob1);
	memcpy(hGlobal, resPtr, size);
	FreeResource(hGlob1);
	CreateStreamOnHGlobal(hGlobal, true, &pStream);

	m_pSpinner = new CAnimatedGif(pStream);
	if (m_pSpinner == NULL)
	{
		return false;
	}

	POINT pos;
	pos.x = 115;
	pos.y = 260;
	m_pSpinner->InitAnimation(hWnd, pos);
	LOG(Info, "Spinner animation initialized.");
	return true;
}

bool Splash::LoadIndeterminateProgressBar(HWND hWnd)
{
	if (m_pSpinner != NULL)
	{
		delete m_pSpinner;
		m_pSpinner = NULL;
	}
	InvalidateRect(hWnd, 0, TRUE);
	LOG(Info, "Initializing indeterminate progress bar animation.");
	HGLOBAL		hGlobal;
	LPSTREAM	pStream;
	HRSRC	hRsrc = FindResource(NULL, MAKEINTRESOURCE(IsRTLLocale() ? IDR_GIF1 : IDR_GIF2), L"GIF");
	HGLOBAL	hGlob1 = LoadResource(NULL, hRsrc);

	int	size = SizeofResource(NULL, hRsrc);
	hGlobal = GlobalAlloc(GMEM_FIXED, size);
	LPVOID	resPtr = LockResource(hGlob1);
	memcpy(hGlobal, resPtr, size);
	FreeResource(hGlob1);
	CreateStreamOnHGlobal(hGlobal, true, &pStream);

	m_pSpinner = new CAnimatedGif(pStream);
	if (m_pSpinner == NULL)
	{
		return false;
	}

	POINT pos;
	pos.x = 190;
	pos.y = 320;
	m_pSpinner->InitAnimation(hWnd, pos);
	LOG(Info, "Indeterminate progress bar initialized.");
	return true;
}

bool Splash::LoadSplashImage()
{
	if (m_pImage != NULL)
	{
		LOG(Info, "m_pImage already initialized");
		return true;
	}

	ULONG resID = m_bUseQuadroSplash ? IDB_PNG2 : IDB_PNG1;

#ifdef NOUAC
	resID = IDB_PNG3;
#endif

	HRSRC	hRsrc = FindResource(NULL, MAKEINTRESOURCEW(resID), L"PNG");
	HGLOBAL	hGlob1 = LoadResource(NULL, hRsrc);
	int	size = SizeofResource(NULL, hRsrc);
	HGLOBAL		hGlobal;
	LPSTREAM	pStream;

	hGlobal = GlobalAlloc(GMEM_FIXED, size);
	LPVOID	resPtr = LockResource(hGlob1);
	memcpy(hGlobal, resPtr, size);
	FreeResource(hGlob1);
	CreateStreamOnHGlobal(hGlobal, true, &pStream);

	m_pImage = new Image(pStream);
	if (m_pImage == NULL)
	{
		return false;
	}
	LOG(Info, "Splash image loading successful");
	return true;
}

void Splash::FreeResources(HWND hWnd)
{
	if (m_pSpinner != NULL)
	{
		delete m_pSpinner;
		m_pSpinner = NULL;
	}
	if (m_pImage != NULL)
	{
		delete m_pImage;
		m_pImage = NULL;
	}
	if (m_gdiplusToken != NULL)
	{
		GdiplusShutdown(m_gdiplusToken);
	}
}

void Splash::SetRTLLayout(bool value)
{
	if (value == false)
		return;

	BOOL ret = false;
	if (value)
	{
		ret = SetProcessDefaultLayout(LAYOUT_RTL);
		if (ret == 0)
		{
			LOG(Error, "Error in setting RTL layout");
			return;
		}
		LOG(Info, "Successfuly set RTL layout");
	}
}

// Helper function to center the window
void CenterDlg(HWND hwnd) 
{
    RECT rect = { };
    GetWindowRect(hwnd, &rect);

    int windowWidth = rect.right - rect.left;
    int windowHeight = rect.bottom - rect.top;

    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);

    int x = (screenWidth - windowWidth) / 2;
    int y = (screenHeight - windowHeight) / 2;

    SetWindowPos(hwnd, NULL, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
}

LRESULT CALLBACK Splash::WindProcedure(HWND hWnd, UINT Msg,
	WPARAM wParam, LPARAM lParam)
{
	HDC hDC;
	PAINTSTRUCT Ps;
	Graphics* graphics;
	HDC hdc = NULL;

	Splash* pSplash = (Splash*)GetObjectInstance();

	if (!pSplash)
		return false;

	switch (Msg)
	{
	case WM_CREATE:
		//if(pSplash->LoadSplashImage() == false || pSplash->LoadSpinner(hWnd) == false)
		if (pSplash->LoadSplashImage() == false || pSplash->LoadIndeterminateProgressBar(hWnd) == false)
		{
			LOG(Error, "Could not load splash screen.");
			pSplash->FreeResources(hWnd);
			DestroyWindow(pSplash->m_CChWnd);
		}
		return 0;
	case WM_PAINT:
		hDC = BeginPaint(hWnd, &Ps);
		graphics = new Graphics(hDC);
		if (pSplash->m_pImage != NULL)
		{
			// There is artifact of 1 pixel border once GDI scaling is activated hence adjusted in Height and width
			graphics->DrawImage(pSplash->m_pImage, 0, 0, pSplash->m_pImage->GetWidth()+1, pSplash->m_pImage->GetHeight()+1);
			if (pSplash->IsSelfUpdateMode())
			{
				HFONT hFont, hOldFont;
				// Retrieve a handle to the system font.
				hFont = CreateFont(12, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 0, 0, ANTIALIASED_QUALITY, 0, L"SYSTEM_FONT");
				hdc = GetDC(pSplash->m_CChWnd);
				// Select the variable stock font into the specified device context. 
				if (hOldFont = (HFONT)SelectObject(hdc, hFont))
				{
					SetBkMode(hdc, TRANSPARENT);
					SetTextColor(hdc, 0x00FFFFFF);
					// TextOut(hdc, 165, 165, pSplash->versionText.c_str(), versionText.length());
					TextOut(hdc, 190, 300, pSplash->updatingText.c_str(), pSplash->updatingText.length());
					// Restore the original font.
					SelectObject(hdc, hOldFont);
				}
			}
		}
		delete(graphics);
		EndPaint(hWnd, &Ps);
		return 0;
    case WM_DISPLAYCHANGE:
        CenterDlg(hWnd);
        break;
	case WM_CLOSE:
		pSplash->FreeResources(hWnd);
		break;
	}
	return DefWindowProc(hWnd, Msg, wParam, lParam);
}

DWORD WINAPI Splash::CreateCustomSplashScreen(LPVOID lpParameter)
{
	Splash* pSplash = (Splash*)GetObjectInstance();

	if (!pSplash)
		return false;


	WNDCLASSEX  WndCls;
	static wchar_t szAppName[] = TEXT("NVIDIA Installer Splash");
	MSG         Msg;

	WndCls.cbSize = sizeof(WndCls);
	WndCls.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
	WndCls.lpfnWndProc = &Splash::WindProcedure;
	WndCls.cbClsExtra = 0;
	WndCls.cbWndExtra = 0;
	WndCls.hInstance = pSplash->m_hInstance;
	WndCls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	WndCls.hCursor = LoadCursor(NULL, IDC_ARROW);
	WndCls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	WndCls.lpszMenuName = NULL;
	WndCls.lpszClassName = szAppName;
	WndCls.hIconSm = LoadIcon(pSplash->m_hInstance, IDI_APPLICATION);
	RegisterClassEx(&WndCls);

	GdiplusStartupInput gdiplusStartupInput;
	Status st = GdiplusStartup(&pSplash->m_gdiplusToken, &gdiplusStartupInput, NULL);

	if (st != Ok)
	{
		return false;
	}

#ifndef NOUAC
	pSplash->m_CChWnd = CreateWindowEx(WS_EX_TOOLWINDOW,
		szAppName,
		TEXT("NVIDIA Installer Splash"),
		WS_POPUP | WS_VISIBLE,
		GetSystemMetrics(SM_CXSCREEN) / 2 - 155,
		GetSystemMetrics(SM_CYSCREEN) / 2 - 78,
		310,
		156,
		NULL,
		NULL,
		pSplash->m_hInstance,
		NULL);
#else
	pSplash->m_CChWnd = CreateWindowEx(WS_EX_TOOLWINDOW,
		szAppName,
		TEXT("NVIDIA Installer Splash"),
		WS_POPUP | WS_VISIBLE,
		GetSystemMetrics(SM_CXSCREEN) / 2 - 325,
		GetSystemMetrics(SM_CYSCREEN) / 2 - 200,
		650/*pSplash->m_pImage->GetWidth()*/,
		400/*pSplash->m_pImage->GetHeight()*/,
		NULL,
		NULL,
		pSplash->m_hInstance,
		NULL);
#endif

	SetWindowLong(pSplash->m_CChWnd, GWL_STYLE, 0);
	ShowWindow(pSplash->m_CChWnd, SW_SHOW); //display window
	UpdateWindow(pSplash->m_CChWnd);

	while (GetMessage(&Msg, NULL, 0, 0))
	{
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);
	}

	return static_cast<int>(Msg.wParam);
}
Splash* Splash::m_pSplashObject = NULL;
std::wstring Splash::updatingText;
std::wstring Splash::versionText;
Splash* Splash::GetObjectInstance()
{
	return m_pSplashObject;
}
