#include "image.h"

#define UPSCALE_1X \
for(int y = 0; y < s.y; ++y) {\
	for(int x = 0; x < s.x; ++x) {\
		color in, out;\
		in = img->get(vec2i(x, y));

#define UPSCALE_1X_END \
		_data[x + y * size().x] = out;\
	}\
}\
break;

#define UPSCALE_2X \
for(int y = 0; y < s.y; ++y) {\
	for(int x = 0; x < s.x; ++x) {\
		color in_t, in_b, in_l, in_r, in_m;\
		color in_tl, in_tr, in_bl, in_br;\
		color out_tl, out_tr, out_bl, out_br;\
		in_t = img->get_safe(vec2i(x, y - 1));\
		in_b = img->get_safe(vec2i(x, y + 1));\
		in_l = img->get_safe(vec2i(x - 1, y));\
		in_r = img->get_safe(vec2i(x + 1, y));\
		in_tl = img->get_safe(vec2i(x - 1, y - 1));\
		in_tr = img->get_safe(vec2i(x + 1, y - 1));\
		in_bl = img->get_safe(vec2i(x - 1, y + 1));\
		in_br = img->get_safe(vec2i(x + 1, y + 1));\
		in_m = img->get(vec2i(x, y));

#define UPSCALE_2X_END \
		_data[(x * 2 + 0) + (y * 2 + 0) * size().x] = out_tl;\
		_data[(x * 2 + 1) + (y * 2 + 0) * size().x] = out_tr;\
		_data[(x * 2 + 0) + (y * 2 + 1) * size().x] = out_bl;\
		_data[(x * 2 + 1) + (y * 2 + 1) * size().x] = out_br;\
	}\
}\
break;

#define UPSCALE_3X \
for(int y = 0; y < s.y; ++y) {\
	for(int x = 0; x < s.x; ++x) {\
		color in_t, in_b, in_l, in_r, in_m;\
		color in_tl, in_tr, in_bl, in_br;\
		color out_t, out_b, out_l, out_r, out_m;\
		color out_tl, out_tr, out_bl, out_br;\
		in_t = img->get_safe(vec2i(x, y - 1));\
		in_b = img->get_safe(vec2i(x, y + 1));\
		in_l = img->get_safe(vec2i(x - 1, y));\
		in_r = img->get_safe(vec2i(x + 1, y));\
		in_tl = img->get_safe(vec2i(x - 1, y - 1));\
		in_tr = img->get_safe(vec2i(x + 1, y - 1));\
		in_bl = img->get_safe(vec2i(x - 1, y + 1));\
		in_br = img->get_safe(vec2i(x + 1, y + 1));\
		in_m = img->get(vec2i(x, y));

#define UPSCALE_3X_END \
		_data[(x * 3 + 0) + (y * 3 + 0) * size().x] = out_tl;\
		_data[(x * 3 + 1) + (y * 3 + 0) * size().x] = out_t;\
		_data[(x * 3 + 2) + (y * 3 + 0) * size().x] = out_tr;\
		_data[(x * 3 + 0) + (y * 3 + 1) * size().x] = out_l;\
		_data[(x * 3 + 1) + (y * 3 + 1) * size().x] = out_m;\
		_data[(x * 3 + 2) + (y * 3 + 1) * size().x] = out_r;\
		_data[(x * 3 + 0) + (y * 3 + 2) * size().x] = out_bl;\
		_data[(x * 3 + 1) + (y * 3 + 2) * size().x] = out_b;\
		_data[(x * 3 + 2) + (y * 3 + 2) * size().x] = out_br;\
	}\
}\
break;

int upscaler_scale(upscaler upscaler) {
	switch(upscaler) {
	case upscaler::none:       return 1;
	case upscaler::scale2x:    return 2;
	case upscaler::scale3x:    return 3;
	case upscaler::lcd3x:      return 3;
	case upscaler::rgb3x:      return 3;
	case upscaler::bilinear3x: return 3;
	case upscaler::hatch3x:    return 3;
	}
}


void image::draw_upscaled(image const* img, upscaler upscaler) {
	draw_upscaled(img, img->size(), upscaler);
}

static std::vector<image*> scale_buffers;

void image::draw_upscaled(image const* img, std::vector<upscaler> upscalers) {
	if(upscalers.size() > scale_buffers.size() + 1)
		scale_buffers.resize(upscalers.size() - 1, nullptr);
	auto src = img;
	auto size = img->size();
	for(int i = 0; i + 1 < upscalers.size(); ++i) {
		auto osize = size * upscaler_scale(upscalers[i]);
		if(!scale_buffers[i] || scale_buffers[i]->size().x < osize.x || scale_buffers[i]->size().y < osize.y) {
			delete scale_buffers[i];
			scale_buffers[i] = new image(osize);
		}
		scale_buffers[i]->draw_upscaled(src, size, upscalers[i]);
		src = scale_buffers[i];
		size = osize;
	}
	this->draw_upscaled(src, size, upscalers.back());
}


void image::draw_upscaled(image const* img, vec2i s, upscaler upscaler) {
	switch(upscaler) {
	case upscaler::none:
		UPSCALE_1X
			out = in;
		UPSCALE_1X_END
	case upscaler::scale2x:
		UPSCALE_2X
			out_tl = in_l == in_t && in_t != in_r && in_l != in_b ? in_l : in_m;
			out_tr = in_t == in_r && in_t != in_l && in_r != in_b ? in_r : in_m;
			out_bl = in_l == in_b && in_l != in_t && in_b != in_r ? in_l : in_m;
			out_br = in_b == in_r && in_l != in_b && in_t != in_r ? in_r : in_m;
		UPSCALE_2X_END
	case upscaler::scale3x:
		UPSCALE_3X
			out_tl = in_l == in_t && in_l != in_b && in_t != in_r ? in_l : in_m;
			out_t = (in_l == in_t && in_l != in_b && in_t != in_r && in_m != in_tr) ||
				(in_t == in_r && in_t != in_l && in_r != in_b && in_m != in_tl) ? in_t : in_m;
			out_tr = in_t == in_r && in_t != in_l && in_r != in_b ? in_r : in_m;
			out_l = (in_b == in_l && in_b != in_r && in_l != in_t && in_m != in_tl) ||
				(in_l == in_t && in_l != in_b && in_t != in_r && in_m != in_bl) ? in_l : in_m;
			out_m = in_m;
			out_r = (in_t == in_r && in_t != in_l && in_r != in_b && in_m != in_br) ||
				(in_r == in_b && in_r != in_t && in_b != in_l && in_m != in_tr) ? in_r : in_m;
			out_bl = in_b == in_l && in_b != in_r && in_l != in_t ? in_l : in_m;
			out_b = (in_r == in_b && in_r != in_t && in_b != in_l && in_m != in_bl) ||
				(in_b == in_l && in_b != in_r && in_l != in_t && in_m != in_br) ? in_b : in_m;
			out_br = in_r == in_b && in_r != in_t && in_b != in_l ? in_r : in_m;
		UPSCALE_3X_END
	case upscaler::lcd3x:
		UPSCALE_3X
			out_tl = out_t = out_l = out_m = in_m;
			out_tr = out_r = out_br = out_b = out_bl = (in_m & 0xfefefe) >> 1;
		UPSCALE_3X_END
	case upscaler::rgb3x:
		UPSCALE_3X
			out_tl = out_l = out_bl = in_m & 0xff0000;
			out_t  = out_m = out_b  = in_m & 0x00ff00;
			out_tr = out_r = out_br = in_m & 0x0000ff;
		UPSCALE_3X_END
	case upscaler::bilinear3x:
		UPSCALE_3X
			out_m = in_m;
			out_l = ((in_m & 0xfefefe) >> 1) + ((in_l & 0xfefefe) >> 1);
			out_r = ((in_m & 0xfefefe) >> 1) + ((in_r & 0xfefefe) >> 1);
			out_t = ((in_m & 0xfefefe) >> 1) + ((in_t & 0xfefefe) >> 1);
			out_b = ((in_m & 0xfefefe) >> 1) + ((in_b & 0xfefefe) >> 1);
			out_tl = ((in_m & 0xfcfcfc) >> 2) + ((in_t & 0xfcfcfc) >> 2) + ((in_l & 0xfcfcfc) >> 2) + ((in_tl & 0xfcfcfc) >> 2);
			out_tr = ((in_m & 0xfcfcfc) >> 2) + ((in_t & 0xfcfcfc) >> 2) + ((in_r & 0xfcfcfc) >> 2) + ((in_tr & 0xfcfcfc) >> 2);
			out_bl = ((in_m & 0xfcfcfc) >> 2) + ((in_b & 0xfcfcfc) >> 2) + ((in_l & 0xfcfcfc) >> 2) + ((in_bl & 0xfcfcfc) >> 2);
			out_br = ((in_m & 0xfcfcfc) >> 2) + ((in_b & 0xfcfcfc) >> 2) + ((in_r & 0xfcfcfc) >> 2) + ((in_br & 0xfcfcfc) >> 2);
		UPSCALE_3X_END
	case upscaler::hatch3x:
		UPSCALE_3X
			out_m = in_m;
			out_l = in_l;
			out_r = in_r;
			out_t = in_t;
			out_b = in_b;
			out_tl = in_tl;
			out_tr = in_tr;
			out_bl = in_bl;
			out_br = in_br;
		UPSCALE_3X_END
	}
}
/*
image const* image::upscale_eagle_2x() const {
	auto img = new image(size() * 2);
	for(int y = 0; y < size().y; ++y) {
		for(int x = 0; x < size().x; ++x) {
			color in_t, in_b, in_l, in_r, in_m, out_tl, out_tr, out_bl, out_br;
			in_t = get_safe(vec2i(x, y - 1));
			in_b = get_safe(vec2i(x, y + 1));
			in_l = get_safe(vec2i(x - 1, y));
			in_r = get_safe(vec2i(x + 1, y));
			in_m = get(vec2i(x, y));

			out_tl = in_l == in_t && in_t != in_r && in_l != in_b ? in_l : in_m;
			out_tr = in_t == in_r && in_t != in_l && in_r != in_b ? in_r : in_m;
			out_bl = in_l == in_b && in_l != in_t && in_b != in_r ? in_l : in_m;
			out_br = in_b == in_r && in_l != in_b && in_t != in_r ? in_r : in_m;

			img->_data[(x * 2 + 0) + (y * 2 + 0) * img->size().x] = out_tl;
			img->_data[(x * 2 + 1) + (y * 2 + 0) * img->size().x] = out_tr;
			img->_data[(x * 2 + 0) + (y * 2 + 1) * img->size().x] = out_bl;
			img->_data[(x * 2 + 1) + (y * 2 + 1) * img->size().x] = out_br;
		}
	}
	return img;
}
*/