/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "common/system.h"
#include "common/textconsole.h"
#include "bagel/mfc/wingdi.h"
#include "bagel/mfc/global_functions.h"
#include "bagel/mfc/afxwin.h"
#include "bagel/mfc/gfx/blitter.h"
#include "bagel/mfc/gfx/palette_map.h"

namespace Bagel {
namespace MFC {

int GetDeviceCaps(HDC hdc, int index) {
	CDC::Impl *dc = (CDC::Impl *)hdc;
	const CBitmap::Impl *bitmap = (CBitmap::Impl *)dc->_bitmap;

	switch (index) {
	case HORZRES:
		return bitmap->w;
	case VERTRES:
		return bitmap->h;
	case BITSPIXEL:
		return bitmap->format.bytesPerPixel * 8;
	case RASTERCAPS:
		return (bitmap->format.bytesPerPixel == 1 ? RC_PALETTE : 0) |
			RC_BITBLT |
			RC_BITMAP64 |
			RC_DI_BITMAP |
			RC_DIBTODEV |
			RC_STRETCHBLT;

	default:
		break;
	}

	error("TODO: CDC::GetDeviceCaps");
	return 0;
}

HDC CreateCompatibleDC(HDC hdc) {
	return new CDC::Impl(hdc);
}

HDC GetDC(HWND hWnd) {
	if (hWnd) {
		CWnd *wnd = CWnd::FromHandle(hWnd);
		CDC *dc = wnd->GetDC();
		return dc->m_hDC;
	} else {
		// Screen DC, so set the screen rect
		CDC::Impl *dc = new CDC::Impl();
		dc->setScreenRect();
		return dc;
	}
}

int ReleaseDC(HWND hWnd, HDC hDC) {
	if (hWnd) {
		// Window based DC
		CWnd *wnd = CWnd::FromHandle(hWnd);
		CDC *dc = CDC::FromHandle(hDC);
		return wnd->ReleaseDC(dc);
	} else {
		// System screen DC
		delete (CDC::Impl *)hDC;
		return 1;
	}
}

bool DeleteDC(HDC hDC) {
	delete (CDC::Impl *)hDC;
	return true;
}

bool DeleteObject(HGDIOBJ ho) {
	delete (CGdiObjectImpl *)ho;
	return true;
}

int GetObject(HANDLE h, int c, void *pv) {
	error("TODO: GetObject");
}

HDC BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint) {
	CWnd *wnd = static_cast<CWnd *>(hWnd);
	return wnd->BeginPaint(lpPaint);
}

bool EndPaint(HWND hWnd, const PAINTSTRUCT *lpPaint) {
	CWnd *wnd = static_cast<CWnd *>(hWnd);
	return wnd->EndPaint(lpPaint);
}

intptr DialogBoxParam(HINSTANCE hInstance,
                       const char *lpTemplateName, HWND hWndParent,
                       DLGPROC lpDialogFunc, LPARAM dwInitParam) {
	error("TODO: DialogBoxParam");
}

bool IsWindow(HWND hWnd) {
	error("TODO: IsWindow");
}

bool SetWindowText(HWND hWnd, const char *lpszString) {
	CWnd *wnd = CWnd::FromHandle(hWnd);
	wnd->SetWindowText(lpszString);
	return true;
}

int GetWindowText(HWND hWnd, char *lpszStringBuf, int nMaxCount) {
	CWnd *wnd = CWnd::FromHandle(hWnd);
	return wnd->GetWindowText(lpszStringBuf, nMaxCount);
}

bool ScreenToClient(HWND hWnd, LPPOINT lpPoint) {
	CWnd *wnd = CWnd::FromHandle(hWnd);
	wnd->ScreenToClient(lpPoint);
	return true;
}

HGDIOBJ SelectObject(HDC hdc, HGDIOBJ h) {
	CDC::Impl *dc = (CDC::Impl *)hdc;
	return dc->Attach(h);
}

HGDIOBJ GetStockObject(int i) {
	switch (i) {
	case SYSTEM_FONT:
		return AfxGetApp()->getDefaultFont();
	default:
		break;
	}

	error("TODO: GetStockObject value");
}

HPALETTE SelectPalette(HDC hdc, HPALETTE hPal, bool bForceBkgd) {
	auto *surf = static_cast<CDC::Impl *>(hdc);
	return surf->selectPalette(hPal, bForceBkgd);
}

unsigned int RealizePalette(HDC hdc) {
	auto *surf = static_cast<CDC::Impl *>(hdc);
	surf->realizePalette();
	return 256;
}

HBITMAP CreateDIBitmap(HDC hdc, CONST BITMAPINFOHEADER *pbmih, uint32 flInit,
		CONST void *pjBits, CONST BITMAPINFO *pbmi, unsigned int iUsage) {
	CBitmap::Impl *bitmap = new CBitmap::Impl();

	// Figure out the pixel format
	assert(pbmih->biSize == 40 && pbmih->biPlanes == 1);
	Graphics::PixelFormat format;

	switch (pbmih->biBitCount) {
	case 1:
		// Monochrome. Since ScummVM doesn't support it directly,
		// I use a standard 8bpp surface, but in the format's
		// aLoss field, I use a value of 255 as a flag.
		// This should be okay, as loss & shift aren't used for 8bpp.
		format = Graphics::PixelFormat::createFormatCLUT8();
		format.aLoss = 255;
		break;

	case 8:
		format = Graphics::PixelFormat::createFormatCLUT8();
		break;
	case 16:
		format = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
		break;
	case 32:
		format = Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24);
		break;
	default:
		error("Unknown biBitCount");
		break;
	}

	// Create the bitmap
	bitmap->create(pbmih->biWidth, pbmih->biHeight, format);

	// Set the palette
	Graphics::Palette pal((pbmi && pbmih->biClrUsed) ? pbmih->biClrUsed : 0);
	if (pbmi && pbmih->biClrUsed) {
		for (uint i = 0; i < pbmih->biClrUsed; ++i) {
			const RGBQUAD &col = pbmi->bmiColors[i];
			pal.set(i, col.rgbRed, col.rgbGreen, col.rgbBlue);
		}

//		bitmap->setPalette(pal.data(), 0, pal.size());
	}

	// If pixels are provided, copy them over
	if (flInit & CBM_INIT) {
		assert(pjBits);

		if (bitmap->format.bytesPerPixel == 1 && !pal.empty()) {
			const CDC::Impl *destDC = (const CDC::Impl *)hdc;
			const CPalette::Impl *destPal = (const CPalette::Impl * )destDC->_palette;

			Gfx::PaletteMap palMap(pal, *destPal);
			palMap.map((const byte *)pjBits, (byte *)bitmap->getPixels(),
				bitmap->w * bitmap->h);

		} else {
			size_t size = bitmap->w * bitmap->h *
				bitmap->format.bytesPerPixel;
			Common::copy((const byte *)pjBits, (const byte *)pjBits + size,
				(byte *)bitmap->getPixels());
		}
	}

	return bitmap;
}

int GetDIBits(HDC hdc, HBITMAP hbm, unsigned int start, unsigned int cLines,
              void *lpvBits, LPBITMAPINFO lpbmi, unsigned int usage) {
	error("TODO: GetDIBits");
}

bool BitBlt(HDC hdc, int xDest, int yDest, int width, int height,
        HDC hdcSrc, int xSrc, int ySrc, uint32 rop) {
	CDC::Impl *srcDc = (CDC::Impl *)hdcSrc;
	CDC::Impl *destDc = (CDC::Impl *)hdc;
	CBitmap::Impl *src = (CBitmap::Impl *)srcDc->_bitmap;
	CBitmap::Impl *dest = (CBitmap::Impl *)destDc->_bitmap;
	uint bgColor = destDc->getBkColor();

	Gfx::blit(src, dest,
		Common::Rect(xSrc, ySrc, xSrc + width, ySrc + height),
		Common::Point(xDest, yDest),
		bgColor, rop, nullptr);
	return true;
}

bool StretchBlt(HDC hdcDest, int xDest, int yDest, int wDest, int hDest,
                HDC hdcSrc, int xSrc, int ySrc, int wSrc, int hSrc, uint32 rop) {
	CDC *srcDC = CDC::FromHandle(hdcSrc);
	CDC *destDC = CDC::FromHandle(hdcDest);
	return destDC->StretchBlt(xDest, yDest, wDest, hDest,
		srcDC, xSrc, ySrc, wSrc, hSrc, rop);
}

int SetStretchBltMode(HDC hdc, int mode) {
	CDC *dc = CDC::FromHandle(hdc);
	return dc->SetStretchBltMode(mode);
}

int StretchDIBits(HDC hdc, int xDest, int yDest, int DestWidth, int DestHeight,
        int xSrc, int ySrc, int SrcWidth, int SrcHeight,
        CONST void *lpBits, CONST BITMAPINFO *lpbmi, unsigned int iUsage, uint32 rop) {
	error("TODO: StretchDIBits");
}

int GetTextExtent(HDC hdc, const char *text, size_t len) {
	CDC *dc = CDC::FromHandle(hdc);
	CSize size = dc->GetTextExtent(CString(text, len));
	return size.cx;
}

bool GetTextMetrics(HDC hdc, LPTEXTMETRIC lptm) {
	CDC *dc = CDC::FromHandle(hdc);
	return dc->GetTextMetrics(lptm);
}

intptr GetWindowWord(HWND hWnd, int nIndex) {
	assert(nIndex == GWW_HINSTANCE);
	return 0;
}

int AddFontResource(const char *fontName) {
	return AfxGetApp()->addFontResource(fontName);
}

bool RemoveFontResource(const char *fontName) {
	return AfxGetApp()->removeFontResource(fontName);
}

int SetScrollPos(HWND hWnd, int /*nBar*/,
		int nPos, bool bRedraw) {
	CWnd *wnd = CWnd::FromHandle(hWnd);
	return wnd->SetScrollPos(nPos, bRedraw);
}

void SetScrollRange(HWND hWnd, int /*nBar*/,
		int nMinPos, int nMaxPos, bool bRedraw) {
	CWnd *wnd = CWnd::FromHandle(hWnd);
	wnd->SetScrollRange(nMinPos, nMaxPos, bRedraw);
}

bool ClipCursor(const RECT *lpRect) {
	// Ignored in ScummVM
	return false;
}

bool GetCursorPos(LPPOINT lpPoint) {
	Common::Point mousePos = AfxGetApp()->getMousePos();
	lpPoint->x = mousePos.x;
	lpPoint->y = mousePos.y;
	return true;
}

bool SetCursorPos(int x, int y) {
	AfxGetApp()->setMousePos(Common::Point(x, y));
	return true;
}

bool SetCapture(HWND hWnd) {
	AfxGetApp()->SetCapture(hWnd);
	return true;
}

bool ReleaseCapture() {
	AfxGetApp()->ReleaseCapture();
	return true;
}

HWND GetCapture() {
	return AfxGetApp()->GetCapture();
}

HCURSOR LoadCursor(HINSTANCE hInstance,
                   const char *lpCursorName) {
	return AfxGetApp()->LoadCursor(lpCursorName);
}

HCURSOR SetCursor(HCURSOR hCursor) {
	return AfxGetApp()->SetCursor(hCursor);
}

int ShowCursor(bool bShow) {
	g_system->showMouse(bShow);
	return 0;
}

bool LineDDA(int x0, int y0, int x1, int y1, LINEDDAPROC lpProc, CDC *cdc) {
	if (!lpProc)
		return false;

	int dx = ABS(x1 - x0);
	int dy = ABS(y1 - y0);

	int sx = (x0 < x1) ? 1 : -1;
	int sy = (y0 < y1) ? 1 : -1;

	int err = dx - dy;

	for (;;) {
		lpProc(x0, y0, cdc);  // Call the callback for this pixel

		if (x0 == x1 && y0 == y1)
			break;

		int e2 = 2 * err;

		if (e2 > -dy)
		{
			err -= dy;
			x0 += sx;
		}

		if (e2 < dx)
		{
			err += dx;
			y0 += sy;
		}
	}

	return true;
}

byte GetRValue(COLORREF color) {
	return  ((byte)(color & 0xFF));
}

byte GetGValue(COLORREF color) {
	return ((byte)((color >> 8) & 0xFF));
}

byte GetBValue(COLORREF color) {
	return ((byte)((color >> 16) & 0xFF));
}

HWND GetDlgItem(HWND hDlg, int nIDDlgItem) {
	error("TODO: GetDlgItem");
}

bool EndDialog(HWND hDlg, intptr nResult) {
	error("TODO: EndDialog");
}

bool SetDlgItemInt(HWND hDlg, int nIDDlgItem,
                   unsigned int uValue, bool bSigned) {
	error("TODO: SetDlgItemInt");
}

bool CheckRadioButton(HWND hDlg, int nIDFirstButton,
                      int nIDLastButton, int nIDCheckButton) {
	error("TODO: CheckRadioButton");
}

uint32 GetSysColor(int nIndex) {
	switch (nIndex) {
	case COLOR_3DHIGHLIGHT:
		return RGB(255, 255, 255);
	case COLOR_BTNSHADOW:
		return RGB(128, 128, 128);
	case COLOR_BTNFACE:
		return RGB(192, 192, 192);
	case COLOR_BTNTEXT:
	case COLOR_WINDOWTEXT:
		return RGB(0, 0, 0);
	default:
		error("Unknown GetSysColor value");
		break;
	}
}

HBRUSH GetSysColorBrush(int nIndex) {
	switch (nIndex) {
	case COLOR_WINDOW:
		return new CBrush::Impl(RGB(255, 255, 255));

	default:
		error("Unknown GetSysColorBrush value");
		break;
	}
}

bool DestroyMenu(HMENU hMenu) {
	error("TODO: DestroyMenu");
}

void SetActiveWindow(HWND hWnd) {
	error("TODO: SetActiveWindow");
}

} // namespace MFC
} // namespace Bagel
