/*
 * pal_blending.c - blitting functions that emulate PAL delay line accurately
 *
 * Copyright (C) 2013 Tomasz Krasuski
 * Copyright (C) 2013 Atari800 development team (see DOC/CREDITS)
 *
 * This file is part of the Atari800 emulator project which emulates
 * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers.

 * Atari800 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 2 of the License, or
 * (at your option) any later version.

 * Atari800 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 Atari800; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "pal_blending.h"

#include "artifact.h"
#include "atari.h"
#include "colours.h"
#include "colours_pal.h"
#include "platform.h"
#include "screen.h"

#if SUPPORTS_CHANGE_VIDEOMODE
#include "videomode.h"
#endif /* SUPPORTS_CHANGE_VIDEOMODE */

static union {
	UWORD bpp16[2][256];	/* 16-bit palette */
	ULONG bpp32[2][256];	/* 32-bit palette */
} palette;

static ULONG shift_mask;

void PAL_BLENDING_UpdateLookup(void)
{
	if (ARTIFACT_mode == ARTIFACT_PAL_BLEND) {
		double yuv_table[256*5];
		int even_pal[256];
		int odd_pal[256];
		int i;
		double *ptr = yuv_table;
		PLATFORM_pixel_format_t format;

		COLOURS_PAL_GetYUV(yuv_table);

		for (i = 0; i < 256; ++i) {
			double y = *ptr++;
			double even_u = *ptr++;
			double odd_u = *ptr++;
			double even_v = *ptr++;
			double odd_v = *ptr++;
			double r, g, b;
			Colours_YUV2RGB(y, even_u, even_v, &r, &g, &b);
			if (!COLOURS_PAL_external.loaded || COLOURS_PAL_external.adjust) {
				r = Colours_Gamma2Linear(r, COLOURS_PAL_setup.gamma);
				g = Colours_Gamma2Linear(g, COLOURS_PAL_setup.gamma);
				b = Colours_Gamma2Linear(b, COLOURS_PAL_setup.gamma);
				r = Colours_Linear2sRGB(r);
				g = Colours_Linear2sRGB(g);
				b = Colours_Linear2sRGB(b);
			}
			Colours_SetRGB(i, (int) (r * 255), (int) (g * 255), (int) (b * 255), even_pal);
			Colours_YUV2RGB(y, odd_u, odd_v, &r, &g, &b);
			if (!COLOURS_PAL_external.loaded || COLOURS_PAL_external.adjust) {
				r = Colours_Gamma2Linear(r, COLOURS_PAL_setup.gamma);
				g = Colours_Gamma2Linear(g, COLOURS_PAL_setup.gamma);
				b = Colours_Gamma2Linear(b, COLOURS_PAL_setup.gamma);
				r = Colours_Linear2sRGB(r);
				g = Colours_Linear2sRGB(g);
				b = Colours_Linear2sRGB(b);
			}
			Colours_SetRGB(i, (int) (r * 255), (int) (g * 255), (int) (b * 255), odd_pal);
		}
		PLATFORM_GetPixelFormat(&format);
		shift_mask = (format.rmask & ~(format.rmask << 1)) | (format.gmask & ~(format.gmask << 1)) | (format.bmask & ~(format.bmask << 1));
		switch (format.bpp) {
		case 16:
			PLATFORM_MapRGB(palette.bpp16[0], even_pal, 256);
			PLATFORM_MapRGB(palette.bpp16[1], odd_pal, 256);
			shift_mask |= shift_mask << 16;
			break;
		case 32:
			PLATFORM_MapRGB(palette.bpp32[0], even_pal, 256);
			PLATFORM_MapRGB(palette.bpp32[1], odd_pal, 256);
		}
		shift_mask = ~shift_mask;
	}
}

void PAL_BLENDING_Blit16(ULONG *dest, UBYTE *src, int pitch, int width, int height, int start_odd)
{
	register ULONG quad, quad_prev;
	register UBYTE c;
	register int pos;
	UBYTE *src_prev = src;
	int odd_prev = start_odd ^ 1;
	int width_32;
	if (width & 0x01)
		width_32 = width + 1;
	else
		width_32 = width;
	while (height > 0) {
		pos = width_32;
		do {
			pos--;
			c = src[pos];
			/* Make QUAD_PREV have the same Y component as the current line's pixel. */
			quad_prev = palette.bpp16[odd_prev][(src_prev[pos] & 0xf0) | (c & 0x0f)] << 16;
			quad = palette.bpp16[start_odd][c] << 16;
			pos--;
			c = src[pos];
			quad_prev |= palette.bpp16[odd_prev][(src_prev[pos] & 0xf0) | (c & 0x0f)];
			quad |= palette.bpp16[start_odd][c];
			/* Since QUAD_PREV and QUAD have the same Y component, computing
			   averages of even U/V and odd U/V is equal to computing averages
			   of even and odd RGB components. */
			/* dest[pos >> 1] = ((quad+quad_prev) & shift_mask)/2; */
			dest[pos >> 1] = (quad & quad_prev) + (((quad ^ quad_prev) & shift_mask) >> 1);
		} while (pos > 0);
		src_prev = src;
		src += Screen_WIDTH;
		dest += pitch;
		height--;
		start_odd ^= 1;
		odd_prev ^= 1;
	}
}

void PAL_BLENDING_Blit32(ULONG *dest, UBYTE *src, int pitch, int width, int height, int start_odd)
{
	register ULONG quad, quad_prev;
	register UBYTE c;
	register int pos;
	UBYTE *src_prev = src;
	int odd_prev = start_odd ^ 1;
	while (height > 0) {
		pos = width;
		do {
			pos--;
			c = src[pos];
			/* Make QUAD_PREV have the same Y component as the current line's pixel. */
			quad_prev = palette.bpp32[odd_prev][(src_prev[pos] & 0xf0) | (c & 0x0f)];
			quad = palette.bpp32[start_odd][c];
			/* Since QUAD_PREV and QUAD have the same Y component, computing
			   averages of even U/V and odd U/V is equal to computing averages
			   of even and odd RGB components. */
			/* dest[pos] = ((quad+quad_prev) & shift_mask)/2; */
			dest[pos] = (quad & quad_prev) + (((quad ^ quad_prev) & shift_mask) >> 1);
		} while (pos > 0);
		src_prev = src;
		src += Screen_WIDTH;
		dest += pitch;
		height--;
		start_odd ^= 1;
		odd_prev ^= 1;
	}
}

void PAL_BLENDING_BlitScaled16(ULONG *dest, UBYTE *src, int pitch, int width, int height, int dest_width, int dest_height, int start_odd)
{
	register ULONG quad, quad_prev;
	register int x;
	int y = 0x10000;
	int w1 = dest_width / 2 - 1;
	int w = width << 16;
	int h = height << 16;
	int pos;
	register int dx = w / dest_width;
	int dy = h / dest_height;
	int init_x = (width << 16) - 0x4000;
	UBYTE *src_prev = src;
	int odd_prev = start_odd ^ 1;

	UBYTE c;

	while (dest_height > 0) {
		x = init_x;
		pos = w1;
		while (pos >= 0) {
			c = src[x >> 16];
			/* Make QUAD_PREV have the same Y component as the current line's pixel. */
			quad_prev = palette.bpp16[odd_prev][(src_prev[x >> 16] & 0xf0) | (c & 0x0f)] << 16;
			quad = palette.bpp16[start_odd][c] << 16;
			x -= dx;
			c = src[x >> 16];
			quad_prev |= palette.bpp16[odd_prev][(src_prev[x >> 16] & 0xf0) | (c & 0x0f)];
			quad |= palette.bpp16[start_odd][c];
			x -= dx;
			/* Since QUAD_PREV and QUAD have the same Y component, computing
			   averages of even U/V and odd U/V is equal to computing averages
			   of even and odd RGB components. */
			/* dest[pos] = ((quad+quad_prev) & shift_mask)/2; */
			dest[pos] = (quad & quad_prev) + (((quad ^ quad_prev) & shift_mask) >> 1);
			pos--;
		}
		dest += pitch;
		y -= dy;
		--dest_height;
		if (y < 0) {
			y += 0x10000;
			src_prev = src;
			src += Screen_WIDTH;
			start_odd ^= 1;
			odd_prev ^= 1;
		}
	}
}

void PAL_BLENDING_BlitScaled32(ULONG *dest, UBYTE *src, int pitch, int width, int height, int dest_width, int dest_height, int start_odd)
{
	register ULONG quad, quad_prev;
	register int x;
	int y = 0x10000;
	int w1 = dest_width - 1;
	int w = width << 16;
	int h = height << 16;
	int pos;
	register int dx = w / dest_width;
	int dy = h / dest_height;
	int init_x = w - 0x4000;
	UBYTE *src_prev = src;
	int odd_prev = start_odd ^ 1;

	UBYTE c;

	while (dest_height > 0) {
		x = init_x;
		pos = w1;
		while (pos >= 0) {
			c = src[x >> 16];
			/* Make QUAD_PREV have the same Y component as the current line's pixel. */
			quad_prev = palette.bpp32[odd_prev][(src_prev[x >> 16] & 0xf0) | (c & 0x0f)];
			quad = palette.bpp32[start_odd][c];
			x -= dx;
			/* Since QUAD_PREV and QUAD have the same Y component, computing
			   averages of even U/V and odd U/V is equal to computing averages
			   of even and odd RGB components. */
			/* dest[pos] = ((quad+quad_prev) & shift_mask)/2; */
			dest[pos] = (quad & quad_prev) + (((quad ^ quad_prev) & shift_mask) >> 1);
			pos--;
		}
		dest += pitch;
		y -= dy;
		--dest_height;
		if (y < 0) {
			y += 0x10000;
			src_prev = src;
			src += Screen_WIDTH;
			start_odd ^= 1;
			odd_prev ^= 1;
		}
	}
}
