/*
 * Copyright 2010-2016 OpenXcom Developers.
 *
 * This file is part of OpenXcom.
 *
 * OpenXcom 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.
 *
 * OpenXcom 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 OpenXcom.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "Surface.h"
#include "ShaderDraw.h"
#include <vector>
#include <fstream>
#include <algorithm>
#include <SDL_gfxPrimitives.h>
#include <SDL_image.h>
#include <SDL_endian.h>
#include "../lodepng.h"
#include "Palette.h"
#include "Exception.h"
#include "Logger.h"
#include "ShaderMove.h"
#include "Unicode.h"
#include <stdlib.h>
#ifdef _WIN32
#include <malloc.h>
#endif
#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
#define _aligned_malloc __mingw_aligned_malloc
#define _aligned_free   __mingw_aligned_free
#endif //MINGW
#ifdef __MORPHOS__
#include <ppcinline/exec.h>
#endif

namespace OpenXcom
{


namespace
{

/**
 * Helper function counting pitch in bytes with 16byte padding
 * @param bpp bits per pixel
 * @param width number of pixel in row
 * @return pitch in bytes
 */
inline int GetPitch(int bpp, int width)
{
	return ((bpp/8) * width + 15) & ~0xF;
}

/**
 * Helper function creating aligned buffer
 * @param bpp bits per pixel
 * @param width number of pixel in row
 * @param height number of rows
 * @return pointer to memory
 */
inline void* NewAligned(int bpp, int width, int height)
{
	const int pitch = GetPitch(bpp, width);
	const int total = pitch * height;
	void* buffer = 0;

#ifndef _WIN32

	#ifdef __MORPHOS__

	buffer = calloc( total, 1 );
	if (!buffer)
	{
		throw Exception("Failed to allocate surface");
	}

	#else
	int rc;
	if ((rc = posix_memalign(&buffer, 16, total)))
	{
		throw Exception(strerror(rc));
	}
	#endif

#else

	// of course Windows has to be difficult about this!
	buffer = _aligned_malloc(total, 16);
	if (!buffer)
	{
		throw Exception("Failed to allocate surface");
	}

#endif

	memset(buffer, 0, total);
	return buffer;
}

/**
 * Helper function release aligned memory
 * @param buffer buffer to delete
 */
inline void DeleteAligned(void* buffer)
{
	if (buffer)
	{
#ifdef _WIN32
		_aligned_free(buffer);
#else
		free(buffer);
#endif
	}
}

} //namespace

/**
 * Sets up a blank 8bpp surface with the specified size and position,
 * with pure black as the transparent color.
 * @note Surfaces don't have to fill the whole size since their
 * background is transparent, specially subclasses with their own
 * drawing logic, so it just covers the maximum drawing area.
 * @param width Width in pixels.
 * @param height Height in pixels.
 * @param x X position in pixels.
 * @param y Y position in pixels.
 * @param bpp Bits-per-pixel depth.
 */
Surface::Surface(int width, int height, int x, int y, int bpp) : _x(x), _y(y), _visible(true), _hidden(false), _redraw(false), _tftdMode(false), _alignedBuffer(0)
{
	_alignedBuffer = NewAligned(bpp, width, height);
	_surface = SDL_CreateRGBSurfaceFrom(_alignedBuffer, width, height, bpp, GetPitch(bpp, width), 0, 0, 0, 0);

	if (_surface == 0)
	{
		throw Exception(SDL_GetError());
	}

	SDL_SetColorKey(_surface, SDL_SRCCOLORKEY, 0);

	_crop.w = 0;
	_crop.h = 0;
	_crop.x = 0;
	_crop.y = 0;
	_clear.x = 0;
	_clear.y = 0;
	_clear.w = getWidth();
	_clear.h = getHeight();
}

/**
 * Performs a deep copy of an existing surface.
 * @param other Surface to copy from.
 */
Surface::Surface(const Surface& other)
{
	//if is native OpenXcom aligned surface
	if (other._alignedBuffer)
	{
		Uint8 bpp = other._surface->format->BitsPerPixel;
		int width = other.getWidth();
		int height = other.getHeight();
		int pitch = GetPitch(bpp, width);
		_alignedBuffer = NewAligned(bpp, width, height);
		_surface = SDL_CreateRGBSurfaceFrom(_alignedBuffer, width, height, bpp, pitch, 0, 0, 0, 0);
		SDL_SetColorKey(_surface, SDL_SRCCOLORKEY, 0);
		//cant call `setPalette` because its virtual function and it dont work correctly in constructor
		SDL_SetColors(_surface, other.getPalette(), 0, 255);
		memcpy(_alignedBuffer, other._alignedBuffer, height*pitch);
	}
	else
	{
		_surface = SDL_ConvertSurface(other._surface, other._surface->format, other._surface->flags);
		_alignedBuffer = 0;
	}

	if (_surface == 0)
	{
		throw Exception(SDL_GetError());
	}
	_x = other._x;
	_y = other._y;
	_crop.w = other._crop.w;
	_crop.h = other._crop.h;
	_crop.x = other._crop.x;
	_crop.y = other._crop.y;
	_clear.w = other._clear.w;
	_clear.h = other._clear.h;
	_clear.x = other._clear.x;
	_clear.y = other._clear.y;
	_visible = other._visible;
	_hidden = other._hidden;
	_redraw = other._redraw;
	_tftdMode = other._tftdMode;
}

/**
 * Deletes the surface from memory.
 */
Surface::~Surface()
{
	DeleteAligned(_alignedBuffer);
	SDL_FreeSurface(_surface);
}

/**
 * Performs a fast copy of a pixel array, accounting for pitch.
 * @param src Source array.
 */
template <typename T>
void Surface::rawCopy(const std::vector<T> &src)
{
	// Copy whole thing
	if (_surface->pitch == _surface->w)
	{
		size_t end = std::min(size_t(_surface->w * _surface->h * _surface->format->BytesPerPixel), src.size());
		std::copy(src.begin(), src.begin() + end, (T*)_surface->pixels);
	}
	// Copy row by row
	else
	{
		for (int y = 0; y < _surface->h; ++y)
		{
			size_t begin = y * _surface->w;
			size_t end = std::min(begin + _surface->w, src.size());
			if (begin >= src.size())
				break;
			std::copy(src.begin() + begin, src.begin() + end, (T*)getRaw(0, y));
		}
	}
}

/**
 * Loads a raw array of pixels into the surface. The pixels must be
 * in the same BPP as the surface.
 * @param bytes Pixel array.
 */
void Surface::loadRaw(const std::vector<unsigned char> &bytes)
{
	lock();
	rawCopy(bytes);
	unlock();
}

/**
 * Loads a raw array of pixels into the surface. The pixels must be
 * in the same BPP as the surface.
 * @param bytes Pixel array.
 */
void Surface::loadRaw(const std::vector<char> &bytes)
{
	lock();
	rawCopy(bytes);
	unlock();
}

/**
 * Loads the contents of an X-Com SCR image file into
 * the surface. SCR files are simply uncompressed images
 * containing the palette offset of each pixel.
 * @param filename Filename of the SCR image.
 * @sa http://www.ufopaedia.org/index.php?title=Image_Formats#SCR_.26_DAT
 */
void Surface::loadScr(const std::string &filename)
{
	// Load file and put pixels in surface
	std::ifstream imgFile(filename.c_str(), std::ios::binary);
	if (!imgFile)
	{
		throw Exception(filename + " not found");
	}

	std::vector<char> buffer((std::istreambuf_iterator<char>(imgFile)), (std::istreambuf_iterator<char>()));
	loadRaw(buffer);
}

/**
 * Loads the contents of an image file of a
 * known format into the surface.
 * @param filename Filename of the image.
 */
void Surface::loadImage(const std::string &filename)
{
	// Destroy current surface (will be replaced)
	DeleteAligned(_alignedBuffer);
	SDL_FreeSurface(_surface);
	_alignedBuffer = 0;
	_surface = 0;

	Log(LOG_VERBOSE) << "Loading image: " << filename;

	// Try loading with LodePNG first
	if (CrossPlatform::compareExt(filename, "png"))
	{
		std::vector<unsigned char> png;
		unsigned error = lodepng::load_file(png, filename);
		if (!error)
		{
			std::vector<unsigned char> image;
			unsigned width, height;
			lodepng::State state;
			state.decoder.color_convert = 0;
			error = lodepng::decode(image, width, height, state, png);
			if (!error)
			{
				LodePNGColorMode *color = &state.info_png.color;
				unsigned bpp = lodepng_get_bpp(color);
				if (bpp == 8)
				{
					_alignedBuffer = NewAligned(bpp, width, height);
					_surface = SDL_CreateRGBSurfaceFrom(_alignedBuffer, width, height, bpp, GetPitch(bpp, width), 0, 0, 0, 0);
					if (_surface)
					{
						loadRaw(image);
						setPalette((SDL_Color*)color->palette, 0, color->palettesize);
						int transparent = 0;
						for (int c = 0; c < _surface->format->palette->ncolors; ++c)
						{
							SDL_Color *palColor = _surface->format->palette->colors + c;
							if (palColor->unused == 0)
							{
								transparent = c;
								break;
							}
						}
						SDL_SetColorKey(_surface, SDL_SRCCOLORKEY, transparent);
					}
				}
			}
		}
	}

	// Otherwise default to SDL_Image
	if (!_surface)
	{
		std::string utf8 = Unicode::convPathToUtf8(filename);
		_surface = IMG_Load(utf8.c_str());
	}

	if (!_surface)
	{
		std::string err = filename + ":" + IMG_GetError();
		throw Exception(err);
	}

	_clear.w = getWidth();
	_clear.h = getHeight();
}

/**
 * Loads the contents of an X-Com SPK image file into
 * the surface. SPK files are compressed with a custom
 * algorithm since they're usually full-screen images.
 * @param filename Filename of the SPK image.
 * @sa http://www.ufopaedia.org/index.php?title=Image_Formats#SPK
 */
void Surface::loadSpk(const std::string &filename)
{
	// Load file and put pixels in surface
	std::ifstream imgFile (filename.c_str(), std::ios::in | std::ios::binary);
	if (!imgFile)
	{
		throw Exception(filename + " not found");
	}

	// Lock the surface
	lock();

	Uint16 flag;
	Uint8 value;
	int x = 0, y = 0;

	while (imgFile.read((char*)&flag, sizeof(flag)))
	{
		flag = SDL_SwapLE16(flag);

		if (flag == 65535)
		{
			imgFile.read((char*)&flag, sizeof(flag));
			flag = SDL_SwapLE16(flag);

			for (int i = 0; i < flag * 2; ++i)
			{
				setPixelIterative(&x, &y, 0);
			}
		}
		else if (flag == 65534)
		{
			imgFile.read((char*)&flag, sizeof(flag));
			flag = SDL_SwapLE16(flag);

			for (int i = 0; i < flag * 2; ++i)
			{
				imgFile.read((char*)&value, 1);
				setPixelIterative(&x, &y, value);
			}
		}
	}

	// Unlock the surface
	unlock();

	imgFile.close();
}

/**
 * Loads the contents of a TFTD BDY image file into
 * the surface. BDY files are compressed with a custom
 * algorithm.
 * @param filename Filename of the BDY image.
 * @sa http://www.ufopaedia.org/index.php?title=Image_Formats#BDY
 */
void Surface::loadBdy(const std::string &filename)
{
	// Load file and put pixels in surface
	std::ifstream imgFile (filename.c_str(), std::ios::in | std::ios::binary);
	if (!imgFile)
	{
		throw Exception(filename + " not found");
	}

	// Lock the surface
	lock();

	Uint8 dataByte;
	int pixelCnt;
	int x = 0, y = 0;
	int currentRow = 0;

	while (imgFile.read((char*)&dataByte, sizeof(dataByte)))
	{
		if (dataByte >= 129)
		{
			pixelCnt = 257 - (int)dataByte;
			imgFile.read((char*)&dataByte, sizeof(dataByte));
			currentRow = y;
			for (int i = 0; i < pixelCnt; ++i)
			{
				setPixelIterative(&x, &y, dataByte);
				if (currentRow != y) // avoid overscan into next row
					break;
			}
		}
		else
		{
			pixelCnt = 1 + (int)dataByte;
			currentRow = y;
			for (int i = 0; i < pixelCnt; ++i)
			{
				imgFile.read((char*)&dataByte, sizeof(dataByte));
				if (currentRow == y) // avoid overscan into next row
					setPixelIterative(&x, &y, dataByte);
			}
		}
	}

	// Unlock the surface
	unlock();

	imgFile.close();
}

/**
 * Clears the entire contents of the surface, resulting
 * in a blank image of the specified color. (0 for transparent)
 * @param color the colour for the background of the surface.
 */
void Surface::clear(Uint32 color)
{
	if (_surface->flags & SDL_SWSURFACE) memset(_surface->pixels, color, _surface->h*_surface->pitch);
	else SDL_FillRect(_surface, &_clear, color);
}

/**
 * Shifts all the colors in the surface by a set amount.
 * This is a common method in 8bpp games to simulate color
 * effects for cheap.
 * @param off Amount to shift.
 * @param min Minimum color to shift to.
 * @param max Maximum color to shift to.
 * @param mul Shift multiplier.
 */
void Surface::offset(int off, int min, int max, int mul)
{
	if (off == 0)
		return;

	// Lock the surface
	lock();

	for (int x = 0, y = 0; x < getWidth() && y < getHeight();)
	{
		Uint8 pixel = getPixel(x, y);
		int p;
		if (off > 0)
		{
			p = pixel * mul + off;
		}
		else
		{
			p = (pixel + off) / mul;
		}
		if (min != -1 && p < min)
		{
			p = min;
		}
		else if (max != -1 && p > max)
		{
			p = max;
		}

		if (pixel > 0)
		{
			setPixelIterative(&x, &y, p);
		}
		else
		{
			setPixelIterative(&x, &y, 0);
		}
	}

	// Unlock the surface
	unlock();
}

/**
 * Shifts all the colors in the surface by a set amount, but
 * keeping them inside a fixed-size color block chunk.
 * @param off Amount to shift.
 * @param blk Color block size.
 * @param mul Shift multiplier.
 */
void Surface::offsetBlock(int off, int blk, int mul)
{
	if (off == 0)
		return;

	// Lock the surface
	lock();

	for (int x = 0, y = 0; x < getWidth() && y < getHeight();)
	{
		Uint8 pixel = getPixel(x, y);
		int min = pixel / blk * blk;
		int max = min + blk;
		int p;
		if (off > 0)
		{
			p = pixel * mul + off;
		}
		else
		{
			p = (pixel + off) / mul;
		}
		if (min != -1 && p < min)
		{
			p = min;
		}
		else if (max != -1 && p > max)
		{
			p = max;
		}

		if (pixel > 0)
		{
			setPixelIterative(&x, &y, p);
		}
		else
		{
			setPixelIterative(&x, &y, 0);
		}
	}

	// Unlock the surface
	unlock();
}

/**
 * Inverts all the colors in the surface according to a middle point.
 * Used for effects like shifting a button between pressed and unpressed.
 * @param mid Middle point.
 */
void Surface::invert(Uint8 mid)
{
	// Lock the surface
	lock();

	for (int x = 0, y = 0; x < getWidth() && y < getHeight();)
	{
		Uint8 pixel = getPixel(x, y);
		if (pixel > 0)
		{
			setPixelIterative(&x, &y, pixel + 2 * ((int)mid - (int)pixel));
		}
		else
		{
			setPixelIterative(&x, &y, 0);
		}
	}

	// Unlock the surface
	unlock();
}

/**
 * Runs any code the surface needs to keep updating every
 * game cycle, like animations and other real-time elements.
 */
void Surface::think()
{

}

/**
 * Draws the graphic that the surface contains before it
 * gets blitted onto other surfaces. The surface is only
 * redrawn if the flag is set by a property change, to
 * avoid unnecessary drawing.
 */
void Surface::draw()
{
	_redraw = false;
	clear();
}

/**
 * Blits this surface onto another one, with its position
 * relative to the top-left corner of the target surface.
 * The cropping rectangle controls the portion of the surface
 * that is blitted.
 * @param surface Pointer to surface to blit onto.
 */
void Surface::blit(Surface *surface)
{
	if (_visible && !_hidden)
	{
		if (_redraw)
			draw();

		SDL_Rect* cropper;
		SDL_Rect target;
		if (_crop.w == 0 && _crop.h == 0)
		{
			cropper = 0;
		}
		else
		{
			cropper = &_crop;
		}
		target.x = getX();
		target.y = getY();
		SDL_BlitSurface(_surface, cropper, surface->getSurface(), &target);
	}
}

/**
 * Copies the exact contents of another surface onto this one.
 * Only the content that would overlap both surfaces is copied, in
 * accordance with their positions. This is handy for applying
 * effects over another surface without modifying the original.
 * @param surface Pointer to surface to copy from.
 */
void Surface::copy(Surface *surface)
{
	/*
	SDL_BlitSurface uses colour matching,
	and is therefor unreliable as a means
	to copy the contents of one surface to another
	instead we have to do this manually

	SDL_Rect from;
	from.x = getX() - surface->getX();
	from.y = getY() - surface->getY();
	from.w = getWidth();
	from.h = getHeight();
	SDL_BlitSurface(surface->getSurface(), &from, _surface, 0);
	*/
	const int from_x = getX() - surface->getX();
	const int from_y = getY() - surface->getY();

	lock();

	for (int x = 0, y = 0; x < getWidth() && y < getHeight();)
	{
		Uint8 pixel = surface->getPixel(from_x + x, from_y + y);
		setPixelIterative(&x, &y, pixel);
	}

	unlock();
}

/**
 * Draws a filled rectangle on the surface.
 * @param rect Pointer to Rect.
 * @param color Color of the rectangle.
 */
void Surface::drawRect(SDL_Rect *rect, Uint8 color)
{
	if (rect->w == 0 || rect->h == 0) return;

	SDL_FillRect(_surface, rect, color);
}

/**
 * Draws a filled rectangle on the surface.
 * @param x X position in pixels.
 * @param y Y position in pixels.
 * @param w Width in pixels.
 * @param h Height in pixels.
 * @param color Color of the rectangle.
 */
void Surface::drawRect(Sint16 x, Sint16 y, Sint16 w, Sint16 h, Uint8 color)
{
	if (w == 0 || h == 0) return;

	SDL_Rect rect;
	rect.w = w;
	rect.h = h;
	rect.x = x;
	rect.y = y;
	SDL_FillRect(_surface, &rect, color);
}

/**
 * Draws a line on the surface.
 * @param x1 Start x coordinate in pixels.
 * @param y1 Start y coordinate in pixels.
 * @param x2 End x coordinate in pixels.
 * @param y2 End y coordinate in pixels.
 * @param color Color of the line.
 */
void Surface::drawLine(Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2, Uint8 color)
{
	lineColor(_surface, x1, y1, x2, y2, Palette::getRGBA(getPalette(), color));
}

/**
 * Draws a filled circle on the surface.
 * @param x X coordinate in pixels.
 * @param y Y coordinate in pixels.
 * @param r Radius in pixels.
 * @param color Color of the circle.
 */
void Surface::drawCircle(Sint16 x, Sint16 y, Sint16 r, Uint8 color)
{
	filledCircleColor(_surface, x, y, r, Palette::getRGBA(getPalette(), color));
}

/**
 * Draws a filled polygon on the surface.
 * @param x Array of x coordinates.
 * @param y Array of y coordinates.
 * @param n Number of points.
 * @param color Color of the polygon.
 */
void Surface::drawPolygon(Sint16 *x, Sint16 *y, int n, Uint8 color)
{
	filledPolygonColor(_surface, x, y, n, Palette::getRGBA(getPalette(), color));
}

/**
 * Draws a textured polygon on the surface.
 * @param x Array of x coordinates.
 * @param y Array of y coordinates.
 * @param n Number of points.
 * @param texture Texture for polygon.
 * @param dx X offset of texture relative to the screen.
 * @param dy Y offset of texture relative to the screen.
 */
void Surface::drawTexturedPolygon(Sint16 *x, Sint16 *y, int n, Surface *texture, int dx, int dy)
{
	texturedPolygon(_surface, x, y, n, texture->getSurface(), dx, dy);
}

/**
 * Draws a text string on the surface.
 * @param x X coordinate in pixels.
 * @param y Y coordinate in pixels.
 * @param s Character string to draw.
 * @param color Color of string.
 */
void Surface::drawString(Sint16 x, Sint16 y, const char *s, Uint8 color)
{
	stringColor(_surface, x, y, s, Palette::getRGBA(getPalette(), color));
}

/**
 * Changes the position of the surface in the X axis.
 * @param x X position in pixels.
 */
void Surface::setX(int x)
{
	_x = x;
}

/**
 * Changes the position of the surface in the Y axis.
 * @param y Y position in pixels.
 */
void Surface::setY(int y)
{
	_y = y;
}

/**
 * Changes the visibility of the surface. A hidden surface
 * isn't blitted nor receives events.
 * @param visible New visibility.
 */
void Surface::setVisible(bool visible)
{
	_visible = visible;
}

/**
 * Returns the visible state of the surface.
 * @return Current visibility.
 */
bool Surface::getVisible() const
{
	return _visible;
}

/**
 * Resets the cropping rectangle set for this surface,
 * so the whole surface is blitted.
 */
void Surface::resetCrop()
{
	_crop.w = 0;
	_crop.h = 0;
	_crop.x = 0;
	_crop.y = 0;
}

/**
 * Returns the cropping rectangle for this surface.
 * @return Pointer to the cropping rectangle.
 */
SDL_Rect *Surface::getCrop()
{
	return &_crop;
}

/**
 * Replaces a certain amount of colors in the surface's palette.
 * @param colors Pointer to the set of colors.
 * @param firstcolor Offset of the first color to replace.
 * @param ncolors Amount of colors to replace.
 */
void Surface::setPalette(SDL_Color *colors, int firstcolor, int ncolors)
{
	if (_surface->format->BitsPerPixel == 8)
		SDL_SetColors(_surface, colors, firstcolor, ncolors);
}

/**
 * This is a separate visibility setting intended
 * for temporary effects like window popups,
 * so as to not override the default visibility setting.
 * @note Do not confuse with setVisible!
 * @param hidden Shown or hidden.
 */
void Surface::setHidden(bool hidden)
{
	_hidden = hidden;
}

/**
 * Locks the surface from outside access
 * for pixel-level access. Must be unlocked
 * afterwards.
 * @sa unlock()
 */
void Surface::lock()
{
	SDL_LockSurface(_surface);
}

/**
 * Unlocks the surface after it's been locked
 * to resume blitting operations.
 * @sa lock()
 */
void Surface::unlock()
{
	SDL_UnlockSurface(_surface);
}

/**
 * help class used for Surface::blitNShade
 */
struct ColorReplace
{
	/**
	* Function used by ShaderDraw in Surface::blitNShade
	* set shade and replace color in that surface
	* @param dest destination pixel
	* @param src source pixel
	* @param shade value of shade of this surface
	* @param newColor new color to set (it should be offseted by 4)
	*/
	static inline void func(Uint8& dest, const Uint8& src, const int& shade, const int& newColor, const int&)
	{
		if (src)
		{
			const int newShade = (src&15) + shade;
			if (newShade > 15)
				// so dark it would flip over to another color - make it black instead
				dest = 15;
			else
				dest = newColor | newShade;
		}
	}

};

/**
 * help class used for Surface::blitNShade
 */
struct StandardShade
{
	/**
	* Function used by ShaderDraw in Surface::blitNShade
	* set shade
	* @param dest destination pixel
	* @param src source pixel
	* @param shade value of shade of this surface
	* @param notused
	* @param notused
	*/
	static inline void func(Uint8& dest, const Uint8& src, const int& shade, const int&, const int&)
	{
		if (src)
		{
			const int newShade = (src&15) + shade;
			if (newShade > 15)
				// so dark it would flip over to another color - make it black instead
				dest = 15;
			else
				dest = (src&(15<<4)) | newShade;
		}
	}

};



/**
 * Specific blit function to blit battlescape terrain data in different shades in a fast way.
 * Notice there is no surface locking here - you have to make sure you lock the surface yourself
 * at the start of blitting and unlock it when done.
 * @param surface to blit to
 * @param x
 * @param y
 * @param off
 * @param half some tiles are blitted only the right half
 * @param newBaseColor Attention: the actual color + 1, because 0 is no new base color.
 */
void Surface::blitNShade(Surface *surface, int x, int y, int off, bool half, int newBaseColor)
{
	ShaderMove<Uint8> src(this, x, y);
	if (half)
	{
		GraphSubset g = src.getDomain();
		g.beg_x = g.end_x/2;
		src.setDomain(g);
	}
	if (newBaseColor)
	{
		--newBaseColor;
		newBaseColor <<= 4;
		ShaderDraw<ColorReplace>(ShaderSurface(surface), src, ShaderScalar(off), ShaderScalar(newBaseColor));
	}
	else
		ShaderDraw<StandardShade>(ShaderSurface(surface), src, ShaderScalar(off));

}

/**
 * Specific blit function to blit battlescape terrain data in different shades in a fast way.
 * @param surface destination blit to
 * @param x
 * @param y
 * @param shade shade offset
 * @param range area that limit draw surface
 */
void Surface::blitNShade(Surface *surface, int x, int y, int shade, GraphSubset range)
{
	ShaderMove<Uint8> src(this, x, y);
	ShaderMove<Uint8> dest(surface);

	dest.setDomain(range);

	ShaderDraw<StandardShade>(dest, src, ShaderScalar(shade));
}

/**
 * Set the surface to be redrawn.
 * @param valid true means redraw.
 */
void Surface::invalidate(bool valid)
{
	_redraw = valid;
}

/**
 * Returns the help description of this surface,
 * for example for showing in tooltips.
 * @return String ID.
 */
std::string Surface::getTooltip() const
{
	return _tooltip;
}

/**
 * Changes the help description of this surface,
 * for example for showing in tooltips.
 * @param tooltip String ID.
 */
void Surface::setTooltip(const std::string &tooltip)
{
	_tooltip = tooltip;
}

/**
 * Recreates the surface with a new size.
 * Old contents will not be altered, and may be
 * cropped to fit the new size.
 * @param width Width in pixels.
 * @param height Height in pixels.
 */
void Surface::resize(int width, int height)
{
	// Set up new surface
	Uint8 bpp = _surface->format->BitsPerPixel;
	int pitch = GetPitch(bpp, width);
	void *alignedBuffer = NewAligned(bpp, width, height);
	SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(alignedBuffer, width, height, bpp, pitch, 0, 0, 0, 0);

	if (surface == 0)
	{
		throw Exception(SDL_GetError());
	}

	// Copy old contents
	SDL_SetColorKey(surface, SDL_SRCCOLORKEY, 0);
	SDL_SetColors(surface, getPalette(), 0, 256);
	SDL_BlitSurface(_surface, 0, surface, 0);

	// Delete old surface
	DeleteAligned(_alignedBuffer);
	SDL_FreeSurface(_surface);
	_alignedBuffer = alignedBuffer;
	_surface = surface;

	_clear.w = getWidth();
	_clear.h = getHeight();
}

/**
 * Changes the width of the surface.
 * @warning This is not a trivial setter!
 * It will force the surface to be recreated for the new size.
 * @param width New width in pixels.
 */
void Surface::setWidth(int width)
{
	resize(width, getHeight());
	_redraw = true;
}

/**
 * Changes the height of the surface.
 * @warning This is not a trivial setter!
 * It will force the surface to be recreated for the new size.
 * @param height New height in pixels.
 */
void Surface::setHeight(int height)
{
	resize(getWidth(), height);
	_redraw = true;
}

/**
 * TFTD mode: much like click inversion, but does a colour swap rather than a palette shift.
 * @param mode set TFTD mode to this.
 */
void Surface::setTFTDMode(bool mode)
{
	_tftdMode = mode;
}

/**
 * checks TFTD mode.
 * @return TFTD mode.
 */
bool Surface::isTFTDMode() const
{
	return _tftdMode;
}

}
