#pragma once
#include "includes.h"
#include "config.h"

void set_pixel_rgb24(int x, int y, Uint32 pixel, SDL_Surface* surface) {
	Uint8* pixels_pointer = (Uint8*)surface->pixels;
	int pitch = surface->pitch;
	Uint8* p = pixels_pointer + y*pitch + x*surface->format->BytesPerPixel;
	#if SDL_BYTEORDER==SDL_BIG_ENDIAN
	p[0] = (pixel >> 16) & 0xff;
	p[1] = (pixel >> 8) & 0xff;
	p[2] = pixel & 0xff;
	#else
	p[0] = pixel & 0xff;
	p[1] = (pixel >> 8) & 0xff;
	p[2] = (pixel >> 16) & 0xff;
	#endif
}
Uint32 get_pixel_rgb24(int x, int y, SDL_Surface* surface) {
	Uint8* pixels_pointer = (Uint8*)surface->pixels;
	int pitch = surface->pitch;
	Uint8* p = pixels_pointer + y*pitch + x*surface->format->BytesPerPixel;
	Uint32 result = 0;
	#if SDL_BYTEORDER==SDL_BIG_ENDIAN
	result += (p[0]&0xff)<<16;
	result += (p[1]&0xff)<<8;
	result += (p[2]&0xff);
	#else
	result += (p[0]&0xff);
	result += (p[1]&0xff)<<8;
	result += (p[2]&0xff)<<16;
	#endif
	return result;
}
Uint32 get_pixel_rgb32(int x, int y, SDL_Surface* surface) {
	Uint8* pixels_pointer = (Uint8*)surface->pixels;
	int pitch = surface->pitch;
	Uint8* p = pixels_pointer + y*pitch + x*surface->format->BytesPerPixel;
	return *(Uint32*)p;
}

inline void sample_nearest(double u, double v, SDL_Surface* texture, double result[3]) {
	glLibMathVec::zero3(result);
	Uint8 r,g,b,a;
	SDL_GetRGBA(
		get_pixel_rgb32(
			glLibMathMisc::clamp(glLibMathMisc::rndint(u),0,texture->w),
			glLibMathMisc::clamp(glLibMathMisc::rndint(v),0,texture->h),
			texture
		),
		texture->format,
		&r,&g,&b,&a
	);
	result[0] += r/255.0;
	result[1] += g/255.0;
	result[2] += b/255.0;
}

double cubic_weight(double x) {
	static double A = -0.75;
	x = fabs(x);
	if (x<1.0) {
		return (A+2.0)*x*x*x - (A+3.0)*x*x + 1.0;
	}
	else if (x<2.0) {
		return A*x*x*x - 5.0*A*x*x + 8.0*A*x - 4*A;
	}
	return 0.0;
}
inline bool add_cubic_sample(double u, double v, double x, double y, SDL_Surface* texture, double result[3]) {
	glLibMathVec::zero3(result);
	double weight = cubic_weight(x)*cubic_weight(y);
	if (weight==0.0) return false;
	sample_nearest(u,v,texture,result);
	glLibMathVec::scale3(weight,result);
	return true;
}
void sample_cubic(double u, double v, SDL_Surface* texture, double result[3]) {
	//http://http.developer.nvidia.com/GPUGems/gpugems_ch24.html
	glLibMathVec::zero3(result);
	double fu = floor(u), fv = floor(v);
	for (int i=0;i<4;++i) {
		for (int j=0;j<4;++j) {
			double sample_x = fu - 1.0 + i;
			double sample_y = fv - 1.0 + j;
			double sample[3];
			if (add_cubic_sample(sample_x,sample_y, u-sample_x,v-sample_y, texture,sample)) {
				glLibMathVec::add3(sample,result,result);
			}
		}
	}
}

void sample_bilinear(double u, double v, SDL_Surface* texture, double result[3]) {
	glLibMathVec::zero3(result);
	double result2[3],result3[3],result23[3], result4[3],result5[3],result45[3];

	sample_nearest(floor(u),floor(v),texture,result2);
	sample_nearest( ceil(u),floor(v),texture,result3);
	glLibMathVec::scale3(ceil(u)-u, result2);
	glLibMathVec::scale3(u-floor(u),result3);
	glLibMathVec::add3(result2,result3,result23);

	sample_nearest(floor(u), ceil(v),texture,result4);
	sample_nearest( ceil(u), ceil(v),texture,result5);
	glLibMathVec::scale3(ceil(u)-u, result4);
	glLibMathVec::scale3(u-floor(u),result5);
	glLibMathVec::add3(result4,result5,result45);

	glLibMathVec::scale3(ceil(v)-v, result23);
	glLibMathVec::scale3(v-floor(v),result45);
	glLibMathVec::add3(result23,result45,result);
}

void sample_gauss(double u, double v, SDL_Surface* texture, double result[3], int radius) {
	static double recip_sqrt_two_pi_quantity_squared = pow(1.0/sqrt(2.0*glLibMathMisc::pi),2.0);
	glLibMathVec::zero3(result);
	double fu = floor(u), fv = floor(v);
	for (int i=-radius;i<=radius;++i) {
		for (int j=-radius;j<=radius;++j) {
			double sample_x = fu - 1.0 + i;
			double sample_y = fv - 1.0 + j;
			double sample[3];
			double weight = recip_sqrt_two_pi_quantity_squared*
				exp(-(u-sample_x)*(u-sample_x)*0.5)*
				exp(-(v-sample_y)*(v-sample_y)*0.5);
			sample_nearest(sample_x,sample_y,texture,sample);
			glLibMathVec::scale3(weight,sample);
			glLibMathVec::add3(sample,result,result);
		}
	}
}

inline void sample_default(double u, double v, SDL_Surface* texture, double result[3]) {
	#ifdef SAMPLE_TEXTURE_DEFAULT_NEAREST
		sample_nearest(u,v,texture,result);
	#elif defined SAMPLE_TEXTURE_DEFAULT_BILINEAR
		sample_bilinear(u,v,texture,result);
	#elif defined SAMPLE_TEXTURE_DEFAULT_BICUBIC
		sample_cubic(u,v,texture,result);
	#elif defined SAMPLE_TEXTURE_DEFAULT_GAUSS
		sample_gauss(u,v,texture,result,SAMPLES_GAUSS_DEFAULT);
	#else
		#error "Unknown default texture sampling type!"
	#endif
}

inline void save_surface(SDL_Surface* image, const char* path) {
	SDL_SaveBMP(image,path);
}