/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */

#include "snes_ntsc.h"

/* Copyright (C) 2006-2007 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */

snes_ntsc_setup_t const snes_ntsc_monochrome = { 0,-1, 0, 0,.2,  0,.2,-.2,-.2,-1,  1, 0, 0 };
snes_ntsc_setup_t const snes_ntsc_composite  = { 0, 0, 0, 0, 0,  0, 0,  0,  0, 0,  1, 0, 0 };
snes_ntsc_setup_t const snes_ntsc_svideo     = { 0, 0, 0, 0,.2,  0,.2, -1, -1, 0,  1, 0, 0 };
snes_ntsc_setup_t const snes_ntsc_rgb        = { 0, 0, 0, 0,.2,  0,.7, -1, -1,-1,  1, 0, 0 };

#define alignment_count 3
#define burst_count     3
#define rescale_in      8
#define rescale_out     7

#define artifacts_mid   1.0f
#define fringing_mid    1.0f
#define std_decoder_hue 0

#define rgb_bits        7 /* half normal range to allow for doubled hires pixels */
#define gamma_size      32

#include "snes_ntsc_impl.h"

unsigned int snes_ntsc_scanline_offset = 0;
unsigned short snes_ntsc_scanline_mask = 0xffff;

/* 3 input pixels -> 8 composite samples */
pixel_info_t const snes_ntsc_pixels [alignment_count] = {
	{ PIXEL_OFFSET( -4, -9 ), { 1, 1, .6667f, 0 } },
	{ PIXEL_OFFSET( -2, -7 ), {       .3333f, 1, 1, .3333f } },
	{ PIXEL_OFFSET(  0, -5 ), {                  0, .6667f, 1, 1 } },
};

static void merge_kernel_fields( snes_ntsc_rgb_t* io )
{
	int n;
	for ( n = burst_size; n; --n )
	{
		snes_ntsc_rgb_t p0 = io [burst_size * 0] + rgb_bias;
		snes_ntsc_rgb_t p1 = io [burst_size * 1] + rgb_bias;
		snes_ntsc_rgb_t p2 = io [burst_size * 2] + rgb_bias;
		/* merge colors without losing precision */
		io [burst_size * 0] =
				((p0 + p1 - ((p0 ^ p1) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
		io [burst_size * 1] =
				((p1 + p2 - ((p1 ^ p2) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
		io [burst_size * 2] =
				((p2 + p0 - ((p2 ^ p0) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
		++io;
	}
}

static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out )
{
	int n;
	for ( n = burst_count; n; --n )
	{
		unsigned i;
		for ( i = 0; i < rgb_kernel_size / 2; i++ )
		{
			snes_ntsc_rgb_t error = color -
					out [i    ] - out [(i+12)%14+14] - out [(i+10)%14+28] -
					out [i + 7] - out [i + 5    +14] - out [i + 3    +28];
			DISTRIBUTE_ERROR( i+3+28, i+5+14, i+7 );
		}
		out += alignment_count * rgb_kernel_size;
	}
}

void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup )
{
	int merge_fields;
	int entry;
	init_t impl;
	if ( !setup )
		setup = &snes_ntsc_composite;
	init( &impl, setup );
	
	merge_fields = setup->merge_fields;
	if ( setup->artifacts <= -1 && setup->fringing <= -1 )
		merge_fields = 1;
	
	for ( entry = 0; entry < snes_ntsc_palette_size; entry++ )
	{
		/* Reduce number of significant bits of source color. Clearing the
		low bits of R and B were least notictable. Modifying green was too
		noticeable. */
		int ir = entry >> 8 & 0x1E;
		int ig = entry >> 4 & 0x1F;
		int ib = entry << 1 & 0x1E;
		
		#if SNES_NTSC_BSNES_COLORTBL
			if ( setup->bsnes_colortbl )
			{
				int bgr15 = (ib << 10) | (ig << 5) | ir;
				unsigned long rgb16 = setup->bsnes_colortbl [bgr15];
				ir = rgb16 >> 11 & 0x1E;
				ig = rgb16 >>  6 & 0x1F;
				ib = rgb16       & 0x1E;
			}
		#endif
		
		{
			float rr = impl.to_float [ir];
			float gg = impl.to_float [ig];
			float bb = impl.to_float [ib];
			
			float y, i, q = RGB_TO_YIQ( rr, gg, bb, y, i );
			
			int r, g, b = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, r, g );
			snes_ntsc_rgb_t rgb = PACK_RGB( r, g, b );
			
			snes_ntsc_rgb_t* out = ntsc->table [entry];
			gen_kernel( &impl, y, i, q, out );
			if ( merge_fields )
				merge_kernel_fields( out );
			correct_errors( rgb, out );
		}
	}
}

#ifndef SNES_NTSC_NO_BLITTERS

void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
		int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
{
	int chunk_count = (in_width - 1) / snes_ntsc_in_chunk;
	for ( ; in_height; --in_height )
	{
		SNES_NTSC_IN_T const* line_in = input;
		SNES_NTSC_BEGIN_ROW( ntsc, burst_phase,
				snes_ntsc_black, snes_ntsc_black, SNES_NTSC_ADJ_IN( *line_in ) );
		snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
		int n;
		++line_in;
		
		for ( n = chunk_count; n; --n )
		{
			/* order of input and output pixels must not be altered */
			SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
			SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
			SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
			
			SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
			SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
			SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
			
			SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
			SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
			SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
			SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
			
			line_in  += 3;
			line_out += 7;
		}
		
		/* finish final pixels */
		SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
		SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
		SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
		
		SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
		SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
		SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
		
		SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
		SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
		SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
		SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
		
		burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
		input += in_row_width;
		rgb_out = (char*) rgb_out + out_pitch;
	}
}

void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
		int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
{
	int chunk_count = (in_width - 2) / (snes_ntsc_in_chunk * 2);
	for ( ; in_height; --in_height )
	{
		SNES_NTSC_IN_T const* line_in = input;
		SNES_NTSC_HIRES_ROW( ntsc, burst_phase,
				snes_ntsc_black, snes_ntsc_black, snes_ntsc_black,
				SNES_NTSC_ADJ_IN( line_in [0] ),
				SNES_NTSC_ADJ_IN( line_in [1] ) );
		snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
		int n;
		line_in += 2;
		
		for ( n = chunk_count; n; --n )
		{
			/* twice as many input pixels per chunk */
			SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
			SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
			
			SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
			SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
			
			SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
			SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
			
			SNES_NTSC_COLOR_IN( 3, SNES_NTSC_ADJ_IN( line_in [3] ) );
			SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
			
			SNES_NTSC_COLOR_IN( 4, SNES_NTSC_ADJ_IN( line_in [4] ) );
			SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
			
			SNES_NTSC_COLOR_IN( 5, SNES_NTSC_ADJ_IN( line_in [5] ) );
			SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
			SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
			
			line_in  += 6;
			line_out += 7;
		}
		
		SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
		SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
		
		SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
		SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
		
		SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
		SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
		
		SNES_NTSC_COLOR_IN( 3, snes_ntsc_black );
		SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
		
		SNES_NTSC_COLOR_IN( 4, snes_ntsc_black );
		SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
		
		SNES_NTSC_COLOR_IN( 5, snes_ntsc_black );
		SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
		SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
		
		burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
		input += in_row_width;
		rgb_out = (char*) rgb_out + out_pitch;
	}
}

/* 12.5% scanlines like in snes_ntsc example instead of zsnes's 25% */
#define PIXEL_OUT( x ) \
	SNES_NTSC_RGB_OUT( x, value, SNES_NTSC_OUT_DEPTH ); \
	line_outa[x] = value; \
	line_outb[x] = value - (value >> snes_ntsc_scanline_offset & snes_ntsc_scanline_mask);

void snes_ntsc_blit_scanlines( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
		int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
{
	unsigned value;
	int chunk_count = (in_width - 1) / snes_ntsc_in_chunk;
	for ( ; in_height; --in_height )
	{
		SNES_NTSC_IN_T const* line_in = input;
		SNES_NTSC_BEGIN_ROW( ntsc, burst_phase,
				snes_ntsc_black, snes_ntsc_black, SNES_NTSC_ADJ_IN( *line_in ) );
		snes_ntsc_out_t * restrict line_outa = (snes_ntsc_out_t *) rgb_out;
		snes_ntsc_out_t * restrict line_outb = (snes_ntsc_out_t *) ((char *) line_outa + out_pitch);
		int n;
		++line_in;

		for ( n = chunk_count; n; --n )
		{
			/* order of input and output pixels must not be altered */
			SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
			PIXEL_OUT (0);
			PIXEL_OUT (1);

			SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
			PIXEL_OUT (2);
			PIXEL_OUT (3);

			SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
			PIXEL_OUT (4);
			PIXEL_OUT (5);
			PIXEL_OUT (6);

			line_in  += 3;
			line_outa += 7;
			line_outb += 7;
		}

		/* finish final pixels */
		SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
		PIXEL_OUT (0);
		PIXEL_OUT (1);

		SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
		PIXEL_OUT (2);
		PIXEL_OUT (3);

		SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
		PIXEL_OUT (4);
		PIXEL_OUT (5);
		PIXEL_OUT (6);

		burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
		input += in_row_width;
		rgb_out = (char*) rgb_out + 2 * out_pitch;
	}
}

#define PIXEL_OUT_HIRES( x ) \
	SNES_NTSC_HIRES_OUT( x, value, SNES_NTSC_OUT_DEPTH ); \
	line_outa[x] = value; \
	line_outb[x] = value - (value >> snes_ntsc_scanline_offset & snes_ntsc_scanline_mask);

void snes_ntsc_blit_hires_scanlines( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
		int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
{
	int chunk_count = (in_width - 2) / (snes_ntsc_in_chunk * 2);
	unsigned value;
	for ( ; in_height; --in_height )
	{
		SNES_NTSC_IN_T const* line_in = input;
		SNES_NTSC_HIRES_ROW( ntsc, burst_phase,
				snes_ntsc_black, snes_ntsc_black, snes_ntsc_black,
				SNES_NTSC_ADJ_IN( line_in [0] ),
				SNES_NTSC_ADJ_IN( line_in [1] ) );
		snes_ntsc_out_t* restrict line_outa = (snes_ntsc_out_t*) rgb_out;
		snes_ntsc_out_t* restrict line_outb = (snes_ntsc_out_t*) ((char *) line_outa + out_pitch);
		int n;
		line_in += 2;

		for ( n = chunk_count; n; --n )
		{
			/* twice as many input pixels per chunk */
			SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
			PIXEL_OUT_HIRES (0);

			SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
			PIXEL_OUT_HIRES (1);

			SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
			PIXEL_OUT_HIRES (2);

			SNES_NTSC_COLOR_IN( 3, SNES_NTSC_ADJ_IN( line_in [3] ) );
			PIXEL_OUT_HIRES (3);

			SNES_NTSC_COLOR_IN( 4, SNES_NTSC_ADJ_IN( line_in [4] ) );
			PIXEL_OUT_HIRES (4);

			SNES_NTSC_COLOR_IN( 5, SNES_NTSC_ADJ_IN( line_in [5] ) );
			PIXEL_OUT_HIRES (5);
			PIXEL_OUT_HIRES (6);

			line_in  += 6;
			line_outa += 7;
			line_outb += 7;
		}

		SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
		PIXEL_OUT_HIRES (0);

		SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
		PIXEL_OUT_HIRES (1);

		SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
		PIXEL_OUT_HIRES (2);

		SNES_NTSC_COLOR_IN( 3, snes_ntsc_black );
		PIXEL_OUT_HIRES (3);

		SNES_NTSC_COLOR_IN( 4, snes_ntsc_black );
		PIXEL_OUT_HIRES (4);

		SNES_NTSC_COLOR_IN( 5, snes_ntsc_black );
		PIXEL_OUT_HIRES (5);
		PIXEL_OUT_HIRES (6);

		burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
		input += in_row_width;
		rgb_out = (char*) rgb_out + out_pitch * 2;
	}
}

#endif
