/* 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/textconsole.h"
#include "bagel/mfc/gfx/blitter.h"
#include "bagel/mfc/afxwin.h"

namespace Bagel {
namespace MFC {
namespace Gfx {

static inline void copyPixel(const byte *srcP, byte *destP, int mode, const byte &WHITE,
		bool isDestMonochrome, uint bgColor, const uint32 *paletteMap) {
	byte src = *srcP;
	if (paletteMap)
		src = paletteMap[src];

	switch (mode) {
	case SRCCOPY:
		*destP = src;
		break;
	case SRCAND:
		*destP &= src;
		break;
	case SRCINVERT:
		*destP ^= src;
		break;
	case SRCPAINT:
		*destP |= src;
		break;
	case NOTSRCCOPY:
		if (isDestMonochrome) {
			*destP = src == bgColor ? 0 : 0xff;
			return;
		}

		*destP = ~src;
		break;
	case DSTINVERT:
		*destP = ~*destP;
		return;
	case BLACKNESS:
		*destP = 0;
		return;
	case WHITENESS:
		*destP = WHITE;
		return;
	default:
		error("Unsupported blit mode");
		break;
	}

	if (isDestMonochrome)
		*destP = *destP == bgColor ? 0xff : 0;
}

static void blitInner(Gfx::Surface *srcSurface,
		Gfx::Surface *destSurface,
		const Common::Rect &srcRect, const Common::Point &destPos,
		uint bgColor, int mode, const uint32 *paletteMap) {
	const bool isDestMonochrome = destSurface->format.bytesPerPixel == 1 &&
		destSurface->format.aLoss == 255;
	const byte WHITE = 255;
	Surface::YIterator ySrc(srcSurface);
	Surface::XIterator xSrc(&ySrc);
	Surface::YIterator yDest(destSurface);
	Surface::XIterator xDest(&yDest);
	byte dummy = 0;
	byte *srcP, *destP;

	for (ySrc = srcRect.top, yDest = destPos.y; ySrc < srcRect.bottom; ++ySrc, ++yDest) {
		for (xSrc = srcRect.left, xDest = destPos.x; xSrc < srcRect.right; ++xSrc, ++xDest) {
			srcP = xSrc;
			destP = xDest;
			if (!srcP)
				srcP = &dummy;
			if (!destP)
				destP = &dummy;

			copyPixel(srcP, destP, mode, WHITE, isDestMonochrome, bgColor, paletteMap);
		}
	}
}

static void stretchBlitInner(Gfx::Surface *srcSurface,
		Gfx::Surface *destSurface,
		const Common::Rect &srcRect, const Common::Rect &dstRect,
		uint bgColor, int mode, const uint32 *paletteMap) {
	const bool isDestMonochrome = destSurface->format.bytesPerPixel == 1 &&
		destSurface->format.aLoss == 255;
	const byte WHITE = 255;
	const int srcWidth = srcRect.right - srcRect.left;
	const int srcHeight = srcRect.bottom - srcRect.top;
	const int dstWidth = dstRect.right - dstRect.left;
	const int dstHeight = dstRect.bottom - dstRect.top;
	Surface::YIterator ySrc(srcSurface);
	Surface::XIterator xSrc(&ySrc);
	Surface::YIterator yDest(destSurface);
	Surface::XIterator xDest(&yDest);

	if (srcWidth <= 0 || srcHeight <= 0 || dstWidth <= 0 || dstHeight <= 0)
		return; // Invalid rectangles

	for (int y = 0; y < dstHeight; ++y) {
		// Map destination y to source y using fixed-point arithmetic
		int srcY = srcRect.top + (y * srcHeight) / dstHeight;
		if (srcY >= srcSurface->h)
			continue;

		int dstY = dstRect.top + y;
		if (dstY >= destSurface->h)
			continue;

		for (int x = 0; x < dstWidth; ++x) {
			int srcX = srcRect.left + (x * srcWidth) / dstWidth;
			if (srcX >= srcSurface->w)
				continue;
			int dstX = dstRect.left + x;
			if (dstX >= destSurface->w)
				continue;

			xSrc = srcX;
			ySrc = srcY;
			byte *srcP = xSrc;
			xDest = dstX;
			yDest = dstY;
			byte *destP = xDest;

			copyPixel(srcP, destP, mode, WHITE, isDestMonochrome, bgColor, paletteMap);
		}
	}
}

void blit(Gfx::Surface *src, Gfx::Surface *dest,
		const Common::Rect &srcRect, const Common::Point &destPos,
		uint bgColor, int mode, const uint32 *paletteMap) {
	// For normal copying modes, the formats must match.
	// Other modes like DSTINVERT don't need a source,
	// so in that case the source can remain uninitialized
	assert(src->format.bytesPerPixel == dest->format.bytesPerPixel ||
		src->format.bytesPerPixel == 0);
	assert(dest->format.bytesPerPixel == dest->format.bytesPerPixel ||
		dest->format.bytesPerPixel == 0);

	blitInner(src, dest, srcRect, destPos, bgColor, mode, paletteMap);

	Common::Rect dirtyRect(destPos.x, destPos.y,
		destPos.x + srcRect.width(), destPos.y + srcRect.height());
	dest->addDirtyRect(dirtyRect);
}

void stretchBlit(Gfx::Surface *src, Gfx::Surface *dest,
		const Common::Rect &srcRect, const Common::Rect &destRect,
		uint bgColor, int mode, const uint32 *paletteMap) {
	assert(src->format.bytesPerPixel == dest->format.bytesPerPixel ||
		src->format.bytesPerPixel == 0);
	assert(dest->format.bytesPerPixel == dest->format.bytesPerPixel ||
		dest->format.bytesPerPixel == 0);

	stretchBlitInner(src, dest, srcRect, destRect, bgColor, mode, paletteMap);
}

static inline void rasterPixel(byte *pixel, byte) {
	// Currently only R2_NOT
	*pixel = ~*pixel;
}

void frameRect(Gfx::Surface *dest,
		const Common::Rect &r, byte color, int drawMode) {
	assert(dest->format.bytesPerPixel == 1);

	if (drawMode == R2_COPYPEN) {
		dest->frameRect(r, color);
		return;
	} else if (drawMode == R2_NOP) {
		return;
	}

	assert(drawMode == R2_NOT);

	const int w = r.right - r.left;
	const int h = r.bottom - r.top - 2;
	byte *pixel;

	// Top line
	pixel = (byte *)dest->getBasePtr(r.left, r.top);
	for (int x = 0; x < w; ++x, ++pixel)
		rasterPixel(pixel, color);

	// Bottom line
	pixel = (byte *)dest->getBasePtr(r.left, r.bottom - 1);
	for (int x = 0; x < w; ++x, ++pixel)
		rasterPixel(pixel, color);

	// Left edge
	pixel = (byte *)dest->getBasePtr(r.left, r.top + 1);
	for (int y = 0; y < h; ++y, pixel += dest->pitch)
		rasterPixel(pixel, color);

	// Right edge
	pixel = (byte *)dest->getBasePtr(r.right - 1, r.top + 1);
	for (int y = 0; y < h; ++y, pixel += dest->pitch)
		rasterPixel(pixel, color);

	// Mark the rectangle area as dirty
	dest->addDirtyRect(r);
}

} // namespace Gfx
} // namespace MFC
} // namespace Bagel
