////////////////////////////////////////////////////////////////////////////////
// Implementation of class Texture
// Copyright Björn Ganster
// Developed by Bjoern Ganster since 2000
////////////////////////////////////////////////////////////////////////////////

#include "GL/glew.h"

#include "GLWindow.h"
#include "MemoryBlock.h"
#include "Texture.h"

#include <algorithm>

#ifdef __APPLE__
#include "OpenGL/glu.h"
#else
#include "GL/glu.h"
#endif

// In order to use DevIL, define UseDevIL here and link the dll
#define UseDevIL

#ifdef UseDevIL
#include "IL/il2.h"
#include "IL/ilu2.h"
#include "IL/ilut.h"
#endif

#include <iostream>

#define FILE_NUMBER FILE_NO_TEXTURE_CPP

////////////////////////////////////////////////////////////////////////////////
// Default constructor

Texture::Texture (TextureDim sizex, TextureDim sizey, GLenum _format, 
	const char* name, void* data)
: VirtualClass(name, TextureID),
  GLHandle (0),
  texels (NULL),
  active (false)
{
	SET_SCOPE;
	resize (sizex, sizey, _format);

	if (data != nullptr) {
		char* source = (char*) data;
		char* target = (char*) texels;
		for (size_t y = 0; y < sizey; ++y) {
			size_t bytesToCopy = texelComponents * sizex;
			memcpy(target, source, bytesToCopy);
			source += bytesToCopy;
			target += texelComponents * potWidth;
		}
	}

	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Destructor

Texture::~Texture ()
{
	SET_SCOPE;
	clear();
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Read a word (i.e., two bytes) from a buffer

#ifndef UseDevIL

unsigned short readWord (unsigned char buffer[], int offset)
{
   return buffer[offset] + 256*buffer[offset+1];
}

////////////////////////////////////////////////////////////////////////////////
// Read a long (i.e., a 32-bit unsigned integer) from a buffer

unsigned int readLong (unsigned char buffer[], int offset)
{
   return readWord (buffer, offset) + 65536 * readWord (buffer, offset+2);
}

////////////////////////////////////////////////////////////////////////////////
// Load from file
// .bmp bitmaps are stored upside down and in bgr format for 24 bpp

bool Texture::loadFromFile (const PathChar* FileName)
{
    deactivate();

    // Load entire file into memory for processing
    MemoryBlock mb;
    if (!mb.loadFromFile(FileName)) {
        printf("Failed to open " PathCharMod "\n", FileName); 
        return false; 
    }
    
    size_t filesize = mb.getSize();
    if (filesize == 0) {
        return false;
    }

    // Read Header
    texels = NULL;
    bool LoadedSuccessfully = false;

   // Read rest of file and close
   char c0 = mb.get<char>(0);
   char c1 = mb.get<char>(1);
   if (c0 == 'B' && c1 == 'M') {
      size_t bmpWidth = mb.get<WORD> (18);
      size_t bmpHeight = mb.get<WORD> (22);
      size_t bpp = mb.get<WORD> (28) / 8;
      if (bpp == 3)
         resize (bmpWidth, bmpHeight, GL_RGB);
      else
         resize (bmpWidth, bmpHeight, GL_RGBA);
      size_t picOffs = mb.get<WORD> (10); // + 14;
      //unsigned int picSize = readLong (fileBytes, 34);
      size_t lineLen = (bpp*bmpWidth+3) / 4 * 4;

      if (bpp == 3) {
         resize (bmpWidth, bmpHeight, GL_RGBA);
         for (size_t y = 0; y < bmpHeight; y++)
            for (size_t x = 0; x < bmpWidth; x++) {
               size_t index = picOffs + bpp*x+y*lineLen;
               unsigned char r = mb.get<BYTE>(index + 2);
               unsigned char g = mb.get<BYTE>(index + 1);
               unsigned char b = mb.get<BYTE>(index + 0);
               setRGB (x, y, r, g, b);
            }
      } else {
         resize (bmpWidth, bmpHeight, GL_RGBA);
         for (size_t y = 0; y < bmpHeight; y++)
            for (size_t x = 0; x < bmpWidth; x++) {
               size_t index = picOffs + bpp*x+y*lineLen;
               unsigned char r = mb.get<BYTE>(index + 2);
               unsigned char g = mb.get<BYTE>(index + 1);
               unsigned char b = mb.get<BYTE>(index + 0);
               unsigned char a = mb.get<BYTE>(index + 3);
               setRGBA (x, y, r, g, b, a);
            }
      }

      LoadedSuccessfully = true;
   } else {
        printf(PathCharMod " is not a BMP\n", FileName); 
        printf("%c%c\n", c0, c1);
   }

   return LoadedSuccessfully;
}

#else

/*void logILError (ILimage* image, const char* msg)
{
   ILenum error = il2GetError(image);
   if (error != IL_NO_ERROR) {
      printf (msg);
      switch (error) {
      case ILUT_COULD_NOT_OPEN_FILE:
         printf ("Could not open the file specified. The file may already be open by another app or may not exist.");
         break;
      case ILUT_ILLEGAL_OPERATION:
         printf ("The operation attempted is not allowable in the current state. The function returns with no ill side effects.");
         break;
      case ILUT_INVALID_ENUM:
         printf ("An unacceptable enumerated value was passed to a function.");
         break;
      case ILUT_INVALID_PARAM:
         printf ("An invalid parameter was passed to a function, such as a NULL pointer.");
         break;
      case ILUT_INVALID_VALUE:
         printf ("An invalid value was passed to a function or was in a file.");
         break;
      case ILUT_NOT_SUPPORTED:
         printf ("A type is valid but not supported in the current build.");
         break;
      case ILUT_OUT_OF_MEMORY:
         printf ("Could not allocate enough memory in an operation.");
         break;
      case ILUT_STACK_OVERFLOW:
         printf ("One of the internal stacks was already filled, and the user tried to add on to the full stack.");
         break;
      case ILUT_STACK_UNDERFLOW:
         printf ("One of the internal stacks was empty, and the user tried to empty the already empty stack.");
         break;
//      case ILU_ILLEGAL_OPERATION:
//         printf ("The operation attempted is not allowable in the current state. The function returns with no ill side effects.");
//         break;
      case ILU_INTERNAL_ERROR:
         printf ("A serious error has occurred. Please e-mail an admin with the conditions leading up to this error being reported.");
         break;
//      case ILU_INVALID_ENUM:
//         printf ("An unacceptable enumerated value was passed to a function.");
//         break;
//      case ILU_INVALID_PARAM:
//         printf ("An invalid parameter was passed to a function, such as a NULL pointer.");
//         break;
//      case ILU_INVALID_VALUE:
//         printf ("An invalid value was passed to a function or was in a file.");
//         break;
//      case ILU_OUT_OF_MEMORY:
//         printf ("Could not allocate enough memory in an operation.");
//         break;
//      case IL_COULD_NOT_OPEN_FILE:
//         printf ("Could not open the file specified. The file may already be open by another app or may not exist.");
         //break;
      case IL_FILE_ALREADY_EXISTS:
         printf ("The filename specified already belongs to another file. To overwrite files by default read more at ilEnable function.");
         break;
      case IL_FORMAT_NOT_SUPPORTED:
         printf ("The format a function tried to use was not able to be used by that function.");
         break;
      case IL_ILLEGAL_FILE_VALUE:
         printf ("An illegal value was found in a file trying to be loaded.");
         break;
//      case IL_ILLEGAL_OPERATION:
//         printf ("The operation attempted is not allowable in the current state. The function returns with no ill side effects.");
//         break;
//      case IL_INTERNAL_ERROR:
//         printf ("A serious error has occurred. Please e-mail an admin with the conditions leading up to this error being reported.");
//         break;
      case IL_INVALID_CONVERSION:
         printf ("An invalid conversion attempt was tried.");
         break;
//      case IL_INVALID_ENUM:
//         printf ("An unacceptable enumerated value was passed to a function.");
//         break;
      case IL_INVALID_EXTENSION:
         printf ("The extension of the specified filename was not correct for the type of image-loading function.");
         break;
      case IL_INVALID_FILE_HEADER:
         printf ("A file's header was incorrect.");
         break;
//      case IL_INVALID_PARAM:
//         printf ("An invalid parameter was passed to a function, such as a NULL pointer.");
//         break;
//      case IL_INVALID_VALUE:
//         printf ("An invalid value was passed to a function or was in a file.");
//         break;
      case IL_LIB_JPEG_ERROR:
         printf ("An error occurred in the libjpeg library.");
         break;
      case IL_LIB_PNG_ERROR:
         printf ("An error occurred in the libpng library.");
         break;
      case IL_NO_ERROR:
         printf ("No detectable error has occured.");
         break;
      case IL_OUT_FORMAT_SAME:
         printf ("Tried to convert an image from its format to the same format.");
         break;
//      case IL_OUT_OF_MEMORY:
//         printf ("Could not allocate enough memory in an operation.");
//         break;
//      case IL_STACK_OVERFLOW:
//         printf ("One of the internal stacks was already filled, and the user tried to add on to the full stack.");
//         break;
//      case IL_STACK_UNDERFLOW:
//         printf ("One of the internal stacks was empty, and the user tried to empty the already empty stack.");
//         break;
      case IL_UNKNOWN_ERROR:
         printf ("No function sets this yet, but it is possible (not probable) it may be used in the future.");
      }
   }
}*/

int getILType (const TCHAR* FileName, bool& upsideDown)
{
	SET_SCOPE;
	size_t len = bgstrlen (FileName);
	upsideDown = false;

	if (len >= 4) {
		auto c1 = latinLowerCase (FileName[len-3]);
		auto c2 = latinLowerCase (FileName[len-2]);
		auto c3 = latinLowerCase (FileName[len-1]);

		if (c1 == 'b'
		&&  c2 == 'm'
		&&  c3 == 'p') {
			upsideDown = true;
			RETURN IL_BMP;
		} else
		if (c1 == 'p'
		&&  c2 == 'n'
		&&  c3 == 'g') {
			RETURN IL_PNG;
		} else
		if (c1 == 'j'
		&&  c2 == 'p'
		&&  c3 == 'g') {
			RETURN IL_JPG;
		} else
		if (c1 == 't'
		&&  c2 == 'i'
		&&  c3 == 'f') {
			RETURN IL_TIF;
		} else
		if (c1 == 'g'
		&&  c2 == 'i'
		&&  c3 == 'f') {
			RETURN IL_GIF;
		}
	}

	RETURN IL_BMP;
}

bool Texture::loadFromFile (const TCHAR* fileName)
{
	SET_SCOPE;
	MemoryBlock mb;
	if (!mb.loadFromFile(fileName)) {
		COUT << "Failed to read " << fileName << std::endl;
		RETURN false;
	}
	bool result = loadFromFile(mb.getPtr<char>(), mb.getSize());
	RETURN result;
}

bool Texture::loadFromFile (const char* data, size_t dataSize)
{
	SET_SCOPE;
	ILimage* image = il2GenImage();
	//logILError (image, "ilGenImage");

	bool upsideDown = (data[0] == 'B') && (data[1] == 'M');
	il2LoadL (image, IL_TYPE_UNKNOWN, data, dataSize);
	//logILError (image, "Load error: ");

	il2ConvertImage (image, IL_BGRA, IL_UNSIGNED_BYTE);
	//logILError (image, "Conversion error: ");
	int w = 0, h = 0;
	il2GetImageInteger (image, IL_IMAGE_WIDTH,  &w);
	il2GetImageInteger (image, IL_IMAGE_HEIGHT, &h);
	resize (w, h, GL_RGBA);

	unsigned char* picData = il2GetData(image);
	if (picData != nullptr) {
		if (upsideDown) {
			SET_OP;
			for (int y = 0; y < h; y++)
				for (int x = 0; x < w; x++) {
					size_t index = 4*(y*w+x);
					unsigned char b = picData[index];
					unsigned char g = picData[index+1];
					unsigned char r = picData[index+2];
					unsigned char a = picData[index+3]; 
					setRGBA (x, y, r, g, b, a);
				}
		} else {
			SET_OP;
			for (int y = 0; y < h; y++)
				for (int x = 0; x < w; x++) {
					size_t index = 4*(y*w+x);
					unsigned char r = picData[index+2];
					unsigned char g = picData[index+1];
					unsigned char b = picData[index];
					unsigned char a = picData[index+3]; 
					setRGBA (x, h-y-1, r, g, b, a);
				}
		}
	}

	SET_OP;
	il2DeleteImage(image);
	//logILError (image, "Cleanup error: ");
	RETURN true;
}

#endif

////////////////////////////////////////////////////////////////////////////////
// Write texture to BMP file

bool Texture::saveToFile (const PathChar* FileName, TextureDim left, TextureDim top, 
   TextureDim saveWidth, TextureDim saveHeight) const
{
	if (left+saveWidth > width
		||  top + saveHeight > height)
	{
		return false;
	}

	// Texture save support must copy pot-texture buffer to separate buffer of
	// fitting size
	SET_SCOPE;
	const unsigned int HeaderSize = 54;
	size_t LineLen = 4*saveWidth;
	int map[] = {0, 3, 2, 1};
	//size_t LineEndOverhead = map[LineLen & 3];
	//LineLen += LineEndOverhead;
	size_t BufferSize = LineLen*saveHeight;
	size_t TotalFileSize = HeaderSize + BufferSize;
	MemoryBlock mb(HeaderSize+BufferSize);
	unsigned char* header = mb.getPtr<unsigned char>(0);

	// Set header data
	for (size_t i = 0; i < HeaderSize; i++)
		header [i] = 0;
	header [ 0] = 'B'; // Magic number
	header [ 1] = 'M'; // Magic number
	mb.set<DWORD>(2, TotalFileSize);
	mb.set<DWORD>(10, (unsigned int) HeaderSize);
	header [10] = HeaderSize; // Offset to data
	header [14] = 40; // Size of info header
	mb.set<DWORD>(18, (unsigned int) saveWidth);
	mb.set<DWORD>(22, (unsigned int) saveHeight);
	header [26] = 1;  // Number of bit planes
	header [28] = 32; // Bits per pixel
	mb.set<DWORD>(34, (unsigned int) BufferSize);

	// Set buffer content
	size_t offset = HeaderSize;
	for (int y = 0; y < (signed) saveHeight; y++) {
		for (int x = 0; x < (signed) saveWidth; x++) {
			unsigned char r, g, b, a;
			getRGBA(left + x, top + y, r, g, b, a);
			mb.set<uint8_t>(offset, b); ++offset;
			mb.set<uint8_t>(offset, g); ++offset;
			mb.set<uint8_t>(offset, r); ++offset;
			mb.set<uint8_t>(offset, a); ++offset;
		}
		//offset += LineEndOverhead;
	}

	// Write file
	bool result = mb.saveToFile(FileName);
	RETURN result;
}

///////////////////////////////////////////////////////////////////////////////
// Prepare texture for rendering (use activate() first)

Texture* activeTexture = nullptr;

void Texture::use() 
{
	SET_SCOPE;
	if (!active) {
		// Activate texture in OpenGL
		SET_OP;
		glEnable(GL_TEXTURE_2D);
		glGenTextures(1, &GLHandle);
		glBindTexture(GL_TEXTURE_2D, GLHandle);
		glTexImage2D(GL_TEXTURE_2D, 0, texelComponents,
			(unsigned int)potWidth, (unsigned int)potHeight,
			0, format, GL_UNSIGNED_BYTE, getPicData());

		// Set texture parameters for that texture
		glTexEnvi(GL_TEXTURE, GL_TEXTURE_ENV_MODE, GL_BLEND);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		active = true;
		activeTexture = this;
	} else if (activeTexture != this) {
		// Activate texture by handle
		SET_OP;
		glBindTexture(GL_TEXTURE_2D, GLHandle);
		activeTexture = this;
	}
	RETURN;
}

void Texture::useMipmap()
{
	SET_SCOPE;
	if (!active) {
		// Activate texture in OpenGL
		SET_OP;
		glGenTextures(1, &GLHandle);
		glBindTexture(GL_TEXTURE_2D, GLHandle);
		gluBuild2DMipmaps(GL_TEXTURE_2D, texelComponents,
			(unsigned int)potWidth, (unsigned int)potHeight,
			format, GL_UNSIGNED_BYTE, getPicData());

		// Set texture parameters for that texture
		glTexEnvi(GL_TEXTURE, GL_TEXTURE_ENV_MODE, GL_BLEND);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		active = true;
		activeTexture = this;
	} else if (activeTexture != this) {
		// Activate texture by handle
		SET_OP;
		glBindTexture(GL_TEXTURE_2D, GLHandle);
		activeTexture = this;
	}
	RETURN;
}

// Inform OpenGL that the texels were updated
void Texture::update()
{
	SET_SCOPE;
    deactivate(); 
	use();
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Deactivate texture

void Texture::deactivate()
{
	SET_SCOPE;
	if (GLHandle != 0) {
		glDeleteTextures(1, &GLHandle);
		GLHandle = 0;
	}
	active = false;

	if (activeTexture == this) {
		activeTexture = nullptr;
	}
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Clear texture

void Texture::clear()
{
	deactivate();
	if (texels != NULL) {
		traceFree(texels, TextureTexelsID, potHeight*potWidth*texelComponents);
		texels = NULL;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Resize texture (content becomes undefined)

bool Texture::resize (TextureDim sizex, TextureDim sizey, GLenum _format)
{
	clear();

	try {
		// Determine data format
		format = _format;
		switch (_format) {
			case GL_LUMINANCE:
			case GL_RED:
				texelComponents = 1;
				redOffset = 0;
				greenOffset = 0;
				blueOffset = 0;
				alphaOffset = 0;
				break;
			case GL_RGB:  texelComponents = 3;
				redOffset = 0;
				greenOffset = 1;
				blueOffset = 2;
				alphaOffset = 0;
				break;
			case GL_RGBA: texelComponents = 4;
				redOffset = 0;
				greenOffset = 1;
				blueOffset = 2;
				alphaOffset = 3;
				break;
			case GL_BGR:  texelComponents = 3;
				redOffset = 2;
				greenOffset = 1;
				blueOffset = 0;
				alphaOffset = 0;
				break;
		}

		// Determine power-of-two texture sizes
		if (sizex > 0) {
			potWidth = 1;
			while (potWidth < sizex)
				potWidth += potWidth;
		} else
			potWidth = 0;

		if (sizey > 0) {
			potHeight = 1;
			while (potHeight < sizey)
				potHeight += potHeight;
		} else
			potHeight = 0;

		// Allocate buffer
		if (potWidth > 0
		&&  potHeight > 0)
		{
			texels = (unsigned char*)traceAlloc(potHeight*potWidth*texelComponents, "TextureTexels", TextureTexelsID);
		}

		if (texels == nullptr) {
			// Allocation failure?
			width = 0;
			height = 0;
			potWidth = 0;
			potHeight = 0;
		} else {
			width = sizex;
			height = sizey;
		}
		return (width == sizex && height == sizey);
	} 
	catch (...) {
		COUT << "Texture resizing failed" << std::endl;
		return false;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Read a texture from framebuffer at the given coordinates
// Parameters used to be x1, y1, x2, y2 and the texture was resized 
// automatically. This has been removed to prevent resizing the texture twice,
// once at creation and once at reading from frame buffer.

void Texture::readFromFrameBuffer (unsigned int /*x*/, unsigned int /*y*/)
{
   // Not currently supported!
   // glReadBuffer (GL_FRONT);
   // glReadPixels (x, y, width, height, GL_BGR, GL_UNSIGNED_BYTE, picData);
}

////////////////////////////////////////////////////////////////////////////////
// Copy part of another texture into this one
// x1, y1, x2, y2: range in source texture to copy
// tx, ty: start of range in target texture (this texture)

void Texture::copy (Texture* other, TextureDim x1, TextureDim y1, 
   TextureDim x2, TextureDim y2, TextureDim tx, TextureDim ty)
{
	SET_SCOPE;

	if (format == other->format
	&&  x1 <= x2
	&&  y1 <= y2
	&&  tx + x2 - x1 <= getWidth()
	&&  ty + y2 - y1 <= getHeight()
	&&  x2 <= other->getWidth()
	&&  y2 <= other->getHeight())
	{
		// If possible, use memcpy -> faster!
		for (TextureDim y = 0; y < y2-y1; y++) {
			size_t bytesToCopy = texelComponents * (x2-x1);
			TextureDim sourceIndex = texelComponents * (y+y1) * other->potWidth + texelComponents * x1;
			TextureDim targetIndex = texelComponents * (y+ty) * potWidth + texelComponents * tx;
			memcpy(texels + targetIndex, other->texels + sourceIndex, bytesToCopy);
		}
	} else {
		if (format == GL_RGBA) {
			for (TextureDim y = y1; y < y2; y++) {
				for (TextureDim x = x1; x < x2; x++) {
					unsigned char r, g, b, a;
					other->getRGBA (x, y, r, g, b, a);
					if (a > 0) {
						setRGBA (x-x1+tx, y-y1+ty, r, g, b, a);
					}
				}
			}
		} else {
			for (TextureDim y = y1; y < y2; y++) {
				for (TextureDim x = x1; x < x2; x++) {
					unsigned char r, g, b;
					other->getRGB (x, y, r, g, b);
					setRGB (x-x1+tx, y-y1+ty, r, g, b);
				}
			}
		}
	}

	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Fill part of target texture if the corresponding source texel is non-transparent
// Used for font rendering. No scaling is applied.
// x1, y1, x2, y2: range in source texture to copy
// tx, ty: start of range in target texture (this texture)

void Texture::copyNonTransparent (Texture* other, TextureDim x1, TextureDim y1, 
	TextureDim x2, TextureDim y2, TextureDim tx, TextureDim ty, unsigned char r, 
	unsigned char g, unsigned char b)
{
	for (TextureDim x = x1; x < x2; x++) {
		for (TextureDim y = y1; y < y2; y++) {
			unsigned char sr, sg, sb, sa;
			other->getRGBA (x, y, sr, sg, sb, sa);
			if (sa > 0) {
				setRGB (x-x1+tx, y-y1+ty, r, g, b);
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
// Render a line (includes line endpoint)

void Texture::renderLine (int x1, int y1, int x2, int y2, 
                 unsigned char r, unsigned char g, unsigned char b)
{
   if (x1 == x2) {
      if (y1 < y2)
         for (int y = y1; y <= y2; y++)
            setRGBA(x1, y, r, g, b, 255);
      else 
         for (int y = y2; y <= y1; y++)
            setRGBA(x1, y, r, g, b, 255);
   } else {
      if (y1 == y2) {
         if (x1 < x2)
            for (int x = x1; x <= x2; x++)
               setRGBA(x, y1, r, g, b, 255);
         else
            for (int x = x2; x <= x1; x++)
               setRGBA(x, y1, r, g, b, 255);
      } else {
         if (abs (x2-x1) < abs (y2-y1)) {
            if (y1 < y2) {
               for (int y = y1; y <= y2; y++) {
                  int x = x1 + (x2-x1) * (y-y1) / (y2-y1);
                  setRGBA(x, y, r, g, b, 255);
               }
            } else {
               for (int y = y2; y <= y1; y++) {
                  int x = x2 + (x1-x2) * (y-y2) / (y1-y2);
                  setRGBA(x, y, r, g, b, 255);
               }
            }
         } else {
            if (x1 < x2) {
               for (int x = x1; x <= x2; x++) {
                  int y = y1 + (y2-y1) * (x-x1) / (x2-x1);
                  setRGBA(x, y, r, g, b, 255);
               }
            } else {
               for (int x = x2; x <= x1; x++) {
                  int y = y2 + (y1-y2) * (x-x2) / (x1-x2);
                  setRGBA(x, y, r, g, b, 255);
               }
            }
         }
      }
   }
}

///////////////////////////////////////////////////////////////////////////////
// Fill triangle

void Texture::fillTriangle (int x1, int y1, int x2, int y2, int x3, int y3,
                            unsigned char r, unsigned char g, unsigned char b)
{
   int x[3], y[3];

   // Sort the points so that y[0] < y[1] < y[2]
   if (y1 < y2) {
      if (y1 < y3) {
         if (y2 < y3) {
            x[0] = x1; x[1] = x2; x[2] = x3; y[0] = y1; y[1] = y2; y[2] = y3; // 1 2 3
         } else {
            x[0] = x1; x[1] = x3; x[2] = x2; y[0] = y1; y[1] = y3; y[2] = y2; // 1 3 2
         }
      } else {
         x[0] = x3; x[1] = x1; x[2] = x2; y[0] = y3; y[1] = y1; y[2] = y2; // 3 1 2
      }
   } else {
      if (y1 < y3) {
         x[0] = x2; x[1] = x1; x[2] = x3; y[0] = y2; y[1] = y1; y[2] = y3; // 2 1 3
      } else {
         if (y2 < y3) {
            x[0] = x2; x[1] = x3; x[2] = x1; y[0] = y2; y[1] = y3; y[2] = y1; // 2 3 1
         } else {
            x[0] = x3; x[1] = x2; x[2] = x1; y[0] = y3; y[1] = y2; y[2] = y1; // 3 2 1
         }
      }
   }

   // Render upper half
   for (int yc = std::max (y[0], 0); yc < std::min (y[1], (int) height-1); yc++) {
      int lxa = x[0] + (x[1]-x[0]) * (yc-y[0]) / (y[1]-y[0]);
      int lxb = x[0] + (x[2]-x[0]) * (yc-y[0]) / (y[2]-y[0]);
      int lx1, lx2;
      if (lxa < lxb) {
         lx1 = putInRange (0, lxa, (int) width-1);
         lx2 = putInRange (0, lxb, (int) width-1);
      } else {
         lx1 = putInRange (0, lxb, (int) width-1);
         lx2 = putInRange (0, lxa, (int) width-1);
      }
      for (int xc = lx1; xc < lx2; xc++) 
         setRGB (xc, yc, r, g, b);
   }

   // Render lower half
   for (int yc = std::max (y[1], 0); yc < std::min (y[2], (int) height-1); yc++) {
      int lxa = x[1] + (x[2]-x[1]) * (yc-y[1]) / (y[2]-y[1]); // 2 1
      int lxb = x[0] + (x[2]-x[0]) * (yc-y[0]) / (y[2]-y[0]); // 0 2
      int lx1, lx2;
      if (lxa < lxb) {
         lx1 = putInRange (0, lxa, (int) width-1);
         lx2 = putInRange (0, lxb, (int) width-1);
      } else {
         lx1 = putInRange (0, lxb, (int) width-1);
         lx2 = putInRange (0, lxa, (int) width-1);
      }
      for (int xc = lx1; xc < lx2; xc++) 
         setRGB (xc, yc, r, g, b);
   }
}

///////////////////////////////////////////////////////////////////////////////
// Fill star-shaped polygon given as a vector

#ifdef TextureRasterizers
void Texture::fillPolygon (const vector <int>& polygon, unsigned char r,
                           unsigned char g, unsigned char b, int cx, int cy)
{
   size_t size = polygon.size();
   for (size_t i = 0; i < size-2; i+= 2)
      fillTriangle (polygon[i], polygon[i+1], polygon[i+2], polygon[i+3], cx, cy, r, g, b);
   fillTriangle (polygon[size-2], polygon[size-1], polygon[0], polygon[1], cx, cy, r, g, b);

/*   size_t size = polygon.size();
   for (size_t i = 0; i < size-4; i+= 2) {
      renderLine (polygon[i], polygon[i+1], polygon[i+2], polygon[i+3], r, g, b);
      renderLine (polygon[i], polygon[i+1], sx, sy, r, g, b);
   }
   renderLine (polygon[size-2], polygon[size-1], polygon[0], polygon[1], r, g, b);
   renderLine (polygon[size-2], polygon[size-1], sx, sy, r, g, b);*/
}
#endif

///////////////////////////////////////////////////////////////////////////////
// Fill square

void Texture::fillRectI(int x1, int y1, int x2, int y2, unsigned char r,
	unsigned char g, unsigned char b, unsigned char a)
{
	if (texels == nullptr) {
		return;
	}

	SET_SCOPE;
	if (y1 < 0)
		y1 = 0;
	if (x1 < 0)
		x1 = 0;
	if (y2 > (int) height)
		y2 = (int) height;
	if (x2 > (int) width)
		x2 = (int) width;

	if (format == GL_RGBA) {
		uint32_t v = r + (g << 8) + (b << 16) + (a << 24);
		#pragma omp parallel for
		for (int y = y1; y < y2; y++) {
			uint32_t* writeP = ((uint32_t*)texels) + (y*potWidth)+x1;
			uint32_t* stopP = ((uint32_t*)texels) + (y*potWidth)+x2;
			for (; writeP < stopP; ++writeP) {
				(*writeP) = v;
			}
		}
	} else {
		for (int y = y1; y < std::min ((int) height, y2); y++) {
			for (int x = x1; x < std::min ((int) width, x2); x++) {
				setRGBA (x, y, r, g, b, a);
			}
		}
	}
	RETURN;
}

void Texture::fillRectI(int x1, int y1, int x2, int y2, unsigned char r,
	unsigned char g, unsigned char b)
{
	for (int x = std::max (0, x1); x < std::min ((int) width, x2); x++) {
		for (int y = std::max (0, y1); y < std::min ((int) height, y2); y++) {
			setRGB (x, y, r, g, b);
		}
	}
}

void Texture::fillRectD(double x1, double y1, double x2, double y2, unsigned char r,
    unsigned char g, unsigned char b)
{
	int ix1 = (int)x1;
	int iy1 = (int)y1;
	int ix2 = (int)x2;
	int iy2 = (int)y2;
	fillRectI(ix1, iy1, ix2, iy2, r, g, b);
}

///////////////////////////////////////////////////////////////////////////////

void Texture::takeScreenshot(int x1, int y1, int x2, int y2)
{
   resize (x2-x1, y2-y1, GL_BGR);
   glPixelStorei(GL_PACK_ROW_LENGTH, potWidth);
   glPixelStorei(GL_PACK_ALIGNMENT, 1);
   glReadPixels(x1, y1, x2, y2, GL_BGR, GL_BYTE, texels);
}

void Texture::setRGB(TextureDim u, TextureDim v, unsigned char r,
	unsigned char g, unsigned char b)
{
	if (u < width && v < height && texels != nullptr) {
		size_t index = texelComponents * (v * potWidth + u);
		texels[index + redOffset] = r;
		texels[index + greenOffset] = g;
		texels[index + blueOffset] = b;
		if (format == GL_RGBA)
			texels[index + alphaOffset] = 255;
	}
}

void Texture::setRGBA(TextureDim u, TextureDim v, unsigned char r,
	unsigned char g, unsigned char b, unsigned char a)
{
	if (u < width && v < height && texels != nullptr || a == 0) {
		size_t index = texelComponents * (v * potWidth + u);
		texels[index + redOffset] = r;
		texels[index + greenOffset] = g;
		texels[index + blueOffset] = b;

		if (format == GL_RGBA)
			texels[index + alphaOffset] = a;
	}
}

void Texture::getRGB(TextureDim u, TextureDim v, unsigned char& r, unsigned char& g, unsigned char& b) const
{
	if (width > 0 && height > 0 && texels != nullptr) {
		//u = u % width;
		//v = v % height;

		// Mirror when passing an image edge -- does not work?
		u = u % (2*width);
		if (u >= width)
			u = 2*width-u;
		v = v % (2*height);
		if (v >= height)
			v = 2*height-v;

		size_t index = texelComponents * (v * potWidth + u);
		r = texels[index + redOffset];
		g = texels[index + greenOffset];
		b = texels[index + blueOffset];
	}
}

void Texture::getRGBA(TextureDim u, TextureDim v, unsigned char& r, unsigned char& g, unsigned char& b,
	unsigned char& a) const
{
	if (width > 0 && height > 0 && texels != nullptr) {
		//u = u % width;
		//v = v % height;

		// Mirror when passing an image edge -- does not work?
		u = u % (2*width);
		if (u >= width)
			u = 2*width-u;
		v = v % (2*height);
		if (v >= height)
			v = 2*height-v;

		size_t index = texelComponents * (v * potWidth + u);
		r = texels[index + redOffset];
		g = texels[index + greenOffset];
		b = texels[index + blueOffset];

		if (format == GL_RGBA)
			a = texels[index + alphaOffset];
		else
			a = 255;
	}
}

// Copy the texture area denoted by (su1, sv1, su2, sv2) from source to the area
// denoted by (tu1, tv1, tu2, tv2) into this
void Texture::stretchCopy(Texture* source, TextureDim su1, TextureDim sv1, TextureDim su2, TextureDim sv2,
	TextureDim tu1, TextureDim tv1, TextureDim tu2, TextureDim tv2, bool blendAlpha)
{
	if (source == nullptr || tu1 == tu2 || tv1 == tv2) {
		return;
	}

	int tustep = 1;
	int tvstep = 1;
	int tusteps = tu2-tu1;
	int tvsteps = tv2-tv1;
	int tubase = tu1;
	int tvbase = tv1;

	if (tu1 > tu2) {
		tustep = -1;
		tusteps = tu1-tu2;
		tubase = tu2;
	}

	if (tv1 > tv2) {
		tvstep = -1;
		tvsteps = tv1-tv2;
		tvbase = tv2;
	}

	double tuDelta = ((double)((int)su2-(int)su1)) / tusteps;
	double tvDelta = ((double)((int)sv2-(int)sv1)) / tvsteps;

	SET_SCOPE;
	for (int tv = tv1; tv != tv2; tv += tvstep) {
		for (int tu = tu1; tu != tu2; tu += tustep) {
			int su = (int)(su1 + (tu-tubase) * tuDelta);
			int sv = (int)(sv1 + (tv-tvbase) * tvDelta);
			uint8_t r, g, b, a;
			source->getRGBA(su, sv, r, g, b, a);

			if (a < 255 && blendAlpha) {
				uint8_t sr, sg, sb;
				getRGB(tu, tv, sr, sg, sb);
				r = (uint8_t)((a * r / 255.0) + ((255-a) * sr / 255.0));
				g = (uint8_t)((a * g / 255.0) + ((255-a) * sg / 255.0));
				b = (uint8_t)((a * b / 255.0) + ((255-a) * sb / 255.0));
				a = 255;
			}

			setRGBA(tu, tv, r, g, b, a);
		}
	}
	RETURN;
}

void Texture::operator=(const Texture& other)
{
	SET_SCOPE;
    deactivate();
    resize(other.width, other.height, other.format);
    memcpy(texels, other.texels, width * height* texelComponents);
    redOffset = other.redOffset;
    blueOffset = other.blueOffset;
    greenOffset = other.greenOffset;
    alphaOffset = other.alphaOffset;
    active = false;
	RETURN;
}

// Get the handle that currently represents this texture in OpenGL
GLuint Texture::getGLHandle() const
{
	return GLHandle;
}
