// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   This program 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 2 of the License, or
//   (at your option) any later version.
//  
//   This program 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 this program; if not, write to the Free Software
//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
//   USA
// -------------------------- END HEADER -------------------------------------

#include "../hacks/winhacks.h"

#include <iostream>
using namespace std;

#include <GL/gl.h>
#include <GL/glu.h>

#include "malfont.h"
#include "../texturemanager/texturemanager.h"
#include "../texturemanager/texture.h"
#include "../image/image.h"
#include "../console/console.h"
#include "../filesystem/filesystem.h"
using namespace Console;

// Utility functions...

// Determine if the given in an image pixel is pink. (#ff00ff)
inline static bool MalFont_pinkPixel(Image *image, int x, int y) {
	if(image) {
		// Get the pixel.
		const Pixel *p = image->getPixel(x, y);
		
		// Test.
		if(p && p->rgba.r == 255 && p->rgba.b == 255 && /*p->rgba.a == 255 &&*/ p->rgba.g == 0) {
			return true;
		}
	}
	return false;
}

// Set a pixel in an image transparent and black.
/*inline static void MalFont_unsetPixel(Image *image, int x, int y) {
	Pixel *p = image->getPixel(x, y);
	p->value = 0;
}*/

MalFont::MalFont(void) {
	setSpaceSize(8);
	setSquish(2);
	setVerticalSquish(2);
	tex = -1;
	origHeight = 1;
	
	adjustedWidth = 1;
	adjustedHeight = 1;
}

MalFont::~MalFont(void) {
	// We don't have to worry about the texture. The TextureManager
	// will handle that. We can't actually ensure the main
	// TextureManager is still there anyway. (Destructors of the
	// singleton managers can happen in whatever order.)
}

void MalFont::loadFont(const std::string &fileName) {

	TextureManager *manager = TextureManager::get();
	tex = manager->getTextureIndexByName(fileName);
	Texture *texture = manager->getTextureByIndex(tex);

	if(texture) {
		texture->waitForLoad();
		
		if(!texture->isLoaded()) {
			out("error") << "Could not load font \"" << fileName << "\"." << endl;
			
			tex = -1;
			
			// Don't leave these hanging around as something weird if
			// they've already been set.
			adjustedWidth = adjustedHeight = 1;
			
			return;
		}
		
        // FIXME: Metatextures will be broken here.

        // Now load our own copy of the image.
        int length = 0;
        char *tgaData = FileSystem::loadFile(fileName, &length);
        Image *tgaImage = NULL;

        if(tgaData) {
            tgaImage = loadTGA(tgaData, length, true);
            delete[] tgaData;
        }
        if(tgaImage) {

            setImage(tgaImage);
            delete tgaImage;

        } else {

            out("error") << "Couldn't load font \"" << fileName << "\". TGA reload error. (Did you use a metatexture?)" << endl;
            return;

        }

        // This isn't needed when we have the pink pixel filtering...
        //texture->reloadFromImage();

        // Make sure it's got a GL texture set up, so we can get the
        // adjusted size.
        texture->getGLTextureNumber();

        // Looks better for low-res bitmap fonts when they're scaled
        // up evenly. Scaled down or not evenly... not so nice.
        // FIXME: Make this go through TextureManager instead, anyway.
        //texture->setFilter(false);

        // Get the adjusted size.
		adjustedWidth  = texture->getAdjustedWidth();
		adjustedHeight = texture->getAdjustedHeight();

	} else {

        out("error") << "Couldn't load font \"" << fileName << "\". No texture." << endl;

    }
}

void MalFont::setImage(Image *image) {
	int x = 0;
	
	int currentCharacter = 0;
	while(currentCharacter <= 93 && x < image->getWidth()) {
		
		// Find the next letter.
		while(MalFont_pinkPixel(image, x, 0) && x < image->getWidth()) {
			//MalFont_unsetPixel(image, x, 0);
			x++;
		}
		
		// This character starts here...
		characters[currentCharacter].start = x;
		
		// Goes for a bit..
		while(!MalFont_pinkPixel(image, x, 0) && x < image->getWidth()) {
			x++;
		}
		
		// Ends here...
		characters[currentCharacter].end = x;
		
		currentCharacter++;
	}
	
	origHeight = image->getHeight();
	
}

bool MalFont::bindFontTexture(void) {
	Texture *texture = TextureManager::get()->getTextureByIndex(tex);
	
	//if(!texture) return false;
	
	if(!texture || !texture->isLoaded()) {
		glBindTexture(GL_TEXTURE_2D, 0);
		return false;
	}
	
	texture->bindTexture();
	return true;
}

Image *MalFont::getOriginalImage(void) {
	Texture *texture = TextureManager::get()->getTextureByIndex(tex);
	if(!texture) return NULL;
	
	return texture->getOriginalImage();
}

struct TextRect {
	float x, y, w, h;
};

static void drawCursor(float x, float y, float scale, float origHeight) {
	// Draw the cursor.
	glDisable(GL_TEXTURE_2D);

	glBegin(GL_QUADS);

	TextRect dstRect;
	dstRect.x = x;
	dstRect.y = y;
	dstRect.h = origHeight * scale;
	
	// Note: This value (cursor width) is NOT SCALED.
	dstRect.w = 2;

	glVertex2f(dstRect.x,             dstRect.y + dstRect.h);
	glVertex2f(dstRect.x + dstRect.w, dstRect.y + dstRect.h);
	glVertex2f(dstRect.x + dstRect.w, dstRect.y);
	glVertex2f(dstRect.x,             dstRect.y);
	
	glEnd();
	glEnable(GL_TEXTURE_2D);
}

char *MalFont::makeWordWrappedBuffer(
	const char *text,
	float maxWidth,
	float scale,
	const char **cursor,
	const char **markStart,
	const char **markEnd,
	float pointerX,
	float pointerY,
	int *pointerIndex,
	bool dontReallyWrap) {
		
	// Parameters explanation, because this thing can be used in multiple ways:
	//   text:     Text to set up.
	//   maxWidth: The maximum width to wrap at.
	//   scale:    The pixel scale (maxWidth doesn't have this factored in).
	//   cursor:   Pointer to a const char*, which is pointing to a
	//             position inside the text buffer. This will then be
	//             CHANGED to point to the same position in the new
	//             text. (Pointer itself is not const, but points to a
	//             const pointer.)
	//   pointerX: Mouse position, relative to upper-left of image.
	//   pointerY:
	//   pointerIndex: Pointer to an int to store an index in the
	//             original buffer. That index will be where the mouse
	//             is pointing.
	//	 dontReallyWrap: Don't actually do any word wrapping.
	
	// currentCharacter will be our pointer as we move through text.
	// wordStart marks the beginning of a word. (Word wrapping at word
	//   boundaries.)
	const char *currentCharacter = text;
	const char *wordStart = currentCharacter;

	// Set up our new buffer.
	// TODO: Should probably make this a "new" instead of malloc.	
	char *adaptedText = (char*)malloc(strlen(text) * 2 + 1);
	char *currentAdaptedCharacter = adaptedText;
	
	// Count total line length in pixels.
	float lineLengthPx = 0;
	
	// Word length in characters.
	int wordLength = 0;
	
	// Word lenght in pixels.
	float wordLengthPx = 0;
	
	// Pixels to move for each line. (For cursor positioning.)
	float verticalOffset = (float(origHeight) - verticalSquish) * scale;
	float verticalPos = 0;
	
	bool doneCopying = false;
	
	bool foundMousePoint = false;
	if(!pointerIndex) foundMousePoint = true;

    bool markEndFound = false;
    bool markStartFound = false;
	
	while(!doneCopying) {
		
		// If we haven't found the mouse point, and we're now past the
		// point, then we need to set where ever we're at as the mouse
		// location.
		if(!foundMousePoint && ((lineLengthPx + wordLengthPx > pointerX && verticalPos + verticalOffset > pointerY) || verticalPos > pointerY)) {
			foundMousePoint = true;
			*pointerIndex = currentCharacter - text - 1;
		}
		
		if(*currentCharacter != ' ' && *currentCharacter != '\t' && *currentCharacter != '\n' && *currentCharacter != 0) {
			
			// Still in a word.
			wordLength++;
			wordLengthPx += float(characters[int(*currentCharacter) - 33].end - characters[int(*currentCharacter) - 33].start - squish) * scale;
			
		} else {
			
			if(wordLength) {
				
				// Just found the end of a word.
				if(lineLengthPx + wordLengthPx > maxWidth) {
					
					// Went past the end of the line. Insert a newline.
					// (But only if this isn't the first word on the
					// line.)
					if(lineLengthPx != 0) {
						
						if(!dontReallyWrap) {
							
							*currentAdaptedCharacter = '\n';
							lineLengthPx = 0;
							verticalPos += verticalOffset;                            
						}
						
						currentAdaptedCharacter++;
					}
					
					// Eat all whitespace for the rest of the line.
					//while(*currentCharacter == ' ' || *currentCharacter == '\t') {
					//	currentCharacter++;
					//}
				}
				
				// Insert the word.
				int i = 0;
				while(wordLength) {
					*currentAdaptedCharacter = wordStart[i];
					
					// Do this one character at a time instead of just
					// adding wordLengthPx so we can position the
					// cursor correctly.
					lineLengthPx += float(characters[int(wordStart[i]) - 33].end - characters[int(wordStart[i]) - 33].start - squish) * scale;
					
					// Cursor position needs to move into the new
					// buffer, because it points directly to the
					// character in question.
					if(cursor && &(wordStart[i]) == *cursor) {
						*cursor = currentAdaptedCharacter;
					}
					
					// Same with text marking.
					if(markStart && &(wordStart[i]) == *markStart) {
						*markStart = currentAdaptedCharacter;
                        markStartFound = true;
					}
					if(markEnd && &(wordStart[i]) == *markEnd) {
						*markEnd = currentAdaptedCharacter;
                        markEndFound = true;
					}
					
					// Mouse pointer silliness.
					if(!foundMousePoint && ((lineLengthPx > pointerX && verticalPos + verticalOffset > pointerY) || verticalPos > pointerY)) {
						foundMousePoint = true;
						*pointerIndex = &(wordStart[i]) - text;
					}
					
					currentAdaptedCharacter++;
					i++;
					wordLength--;
				}
			}
		
			if(*currentCharacter != 0) {
				// Not in a word.
				wordLength = 0;
				wordLengthPx = 0;
				wordStart = currentCharacter + 1;
				
				// Cursor position to new buffer.
				if(cursor && currentCharacter == *cursor) {
					*cursor = currentAdaptedCharacter;
				}
					
				// Natural newline.
				if(*currentCharacter == '\n') {
					lineLengthPx = 0;
					verticalPos += verticalOffset;
				} else {
					if(*currentCharacter == ' ') {
						lineLengthPx += spaceSize * scale;
					}
					if(*currentCharacter == '\t') {
						lineLengthPx += spaceSize * 4 * scale;
					}
					
					// FIXME: This isn't working. It would have
					//   picked the space size from the font.
					//lineLengthPx += characters[int(*currentCharacter) - 33].end - characters[int(*currentCharacter) - 33].start;

				}
				
			} else {
				
				doneCopying = true;
				
			}
			
			*currentAdaptedCharacter = *currentCharacter;

            // We aren't in a work, but a mark can start and end on
            // anything.
            if(markStart && currentCharacter == *markStart) {
                *markStart = currentAdaptedCharacter;
                markStartFound = true;
            }
            if(markEnd && currentCharacter == *markEnd) {
                *markEnd = currentAdaptedCharacter;
                markEndFound = true;
            }
			
			currentAdaptedCharacter++;
		}
		
		
		// Advance the pointer.
		currentCharacter++;
	}
	
	if(!foundMousePoint) {
		// If we haven't found a suitable index for the mouse at this
		// point, it's almost certainly pointing in an area after the
		// end of the buffer. So just point it to the end.
		*pointerIndex = strlen(text);
	}
	
    if(!markEndFound && markEnd) *markEnd = adaptedText + strlen(adaptedText);
    if(!markStartFound && markStart) *markStart = adaptedText;

	return adaptedText;
}

struct TextDrawQuad {
	float x1;
	float y1;
	float x2;
	float y2;
	
	float u1;
	float u2;
	float v1;
	float v2;
	
	bool skipForNormalDraw;
	bool skipForMarkDraw;
};

void MalFont::drawText(
	float x, float y,
	const char *text,
	float maxXSize, float maxYSize,
	float scale,
	float *xSizePrinted, float *ySizePrinted,
	bool wordWrap,
	int cursorPos,
	int markStart,
	int markEnd,
	bool skipBindTexture,
	float *cursorColor, float *markColor) {
	
	if(xSizePrinted) *xSizePrinted = 0;
	if(ySizePrinted) *ySizePrinted = 0;
	
	// Bind the font texture.	
	if(!skipBindTexture) {
		bindFontTexture();
	}
	
	if(markStart > markEnd) {
		int tmp = markStart;
		markStart = markEnd;
		markEnd = tmp;
	}
	
    if(markStart == markEnd) {
        markStart = 0;
        markEnd = 0;
    }

	glEnable(GL_TEXTURE_2D);
	
	const char *currentCharacter = text;
	const char *actualCursorPos = (cursorPos == -1 ? NULL : text + cursorPos);
	const char *actualMarkStart = (markStart == -1 ? NULL : text + markStart);
	const char *actualMarkEnd   = (markEnd   == -1 ? NULL : text + markEnd);
	
	float originalx = x;
	float originaly = y;
	
	int numChars = 0;
	
	// Go through and insert newlines for word-wrapping.
	char *adaptedText = NULL;
	
	if(wordWrap) {
		adaptedText = makeWordWrappedBuffer(currentCharacter, maxXSize, scale, &actualCursorPos, &actualMarkStart, &actualMarkEnd);
		
		currentCharacter = adaptedText;
	}
	
	TextDrawQuad *quads = new TextDrawQuad[strlen(currentCharacter)];
	
	int quadNum = 0;
	
	int realMarkStart = -1;
	int realMarkEnd = -1;
	
	float cursorDrawLocX = 0;
	float cursorDrawLocY = 0;
	
	while(*currentCharacter) {
		
		if(actualCursorPos == currentCharacter) {
			cursorDrawLocX = x;
			cursorDrawLocY = y;
		}
		
		if(currentCharacter == actualMarkStart || currentCharacter == actualMarkEnd) {
			if(realMarkStart == -1) {
				realMarkStart = quadNum;
			} else {
				realMarkEnd = quadNum;
			}
		}
		
		if(*currentCharacter >= 33 && *currentCharacter <= 126) {
			
			// TODO: Condense this so we don't go through the TextRect
			//   intermediary that isn't needed for this anymore.
			
			TextRect srcRect;
			srcRect.x = float(characters[int(*currentCharacter) - 33].start);
			srcRect.y = 0;
			srcRect.w = float(characters[int(*currentCharacter) - 33].end - characters[int(*currentCharacter) - 33].start);
			srcRect.h = origHeight;
			
			TextRect dstRect;
			dstRect.x = x;
			dstRect.y = y;
			dstRect.w = srcRect.w * scale;
			dstRect.h = srcRect.h * scale;
			
			TextDrawQuad *quad = &(quads[quadNum++]);
			
			quad->u1 = float(srcRect.x)             / float(adjustedWidth);
			quad->u2 = float(srcRect.x + srcRect.w) / float(adjustedWidth);
			quad->v1 = float(srcRect.y + srcRect.h) / float(adjustedHeight);
			quad->v2 = float(srcRect.y)             / float(adjustedHeight);
			
			quad->x1 = dstRect.x;
			quad->x2 = dstRect.x + dstRect.w;
			quad->y1 = dstRect.y + dstRect.h;
			quad->y2 = dstRect.y;
			
			quad->skipForMarkDraw = false;
			quad->skipForNormalDraw = false;
			
			numChars++;
			
			x += float(characters[int(*currentCharacter) - 33].end - characters[int(*currentCharacter) - 33].start - squish) * scale;

		} else {
			
			float xStart = x;
			
			// Whitespace.
			if(*currentCharacter == ' ') {
				x += spaceSize * scale;
			} else if(*currentCharacter == '\t') {
				x += spaceSize * 4 * scale;
			} else if(*currentCharacter == '\n') {
				x = originalx;
				y += (float(origHeight) - verticalSquish) * scale;
			}
			
			// We still need a draw quad for this so that the marked
			// region can show with it.
			TextDrawQuad *quad = &(quads[quadNum++]);
			quad->skipForMarkDraw = false;
			quad->skipForNormalDraw = true;
			
			if(*currentCharacter == ' ' || *currentCharacter == '\t') {
				
				quad->x1 = xStart;
				quad->x2 = xStart + spaceSize * scale;
				quad->y1 = y + 0;
				quad->y2 = y + origHeight * scale;
				
			} else {
				
				// Whatever this is (probably a newline) isn't
				// something we need to highlight.
				quad->skipForMarkDraw = true;
			}
		}
		
		// Update discovered bounds.
		if(xSizePrinted && *xSizePrinted < x - originalx) {
			*xSizePrinted = x - originalx;
		}
		if(ySizePrinted && *ySizePrinted < y - originaly) {
			*ySizePrinted = y - originaly;
		}
		
		currentCharacter++;
	}

	//if(markColor && markStart < markEnd) {
    if(markColor && actualMarkStart < actualMarkEnd) {

        if(actualMarkStart && actualMarkEnd) {

            // Draw marking under text.
            glDisable(GL_TEXTURE_2D);
            glColor4f(markColor[0], markColor[1], markColor[2], markColor[3]);
		
            glBegin(GL_QUADS);
            for(const char *q = actualMarkStart; q < actualMarkEnd; q++) {
            
                // This feels pretty hacky. Get the number position of the
                // mark from the difference of the pointers.
                TextDrawQuad *quad = &(quads[int(q - (adaptedText ? adaptedText : text))]);

                if(!quad->skipForMarkDraw) {
                    glVertex2f(quad->x1, quad->y1);
                    glVertex2f(quad->x2, quad->y1);
                    glVertex2f(quad->x2, quad->y2);
                    glVertex2f(quad->x1, quad->y2);
                }
            }
            glEnd();
            glColor4f(1, 1, 1, 1);
            glEnable(GL_TEXTURE_2D);
        }
	}
	
	// Draw the text.
	if(quadNum) {

		bool started = false; // Don't do a glBegin/glEnd at all
							  // unless we REALLY want to draw
							  // something!

		for(int q = 0; q < quadNum; q++) {
			TextDrawQuad *quad = &(quads[q]);
			if(!quad->skipForNormalDraw) {

				if(!started) {
					started = true;
					glBegin(GL_QUADS);
				}	

				glTexCoord2f(quad->u1, quad->v1);
				glVertex2f(quad->x1, quad->y1);

				glTexCoord2f(quad->u2, quad->v1);
				glVertex2f(quad->x2, quad->y1);

				glTexCoord2f(quad->u2, quad->v2);
				glVertex2f(quad->x2, quad->y2);

				glTexCoord2f(quad->u1, quad->v2);
				glVertex2f(quad->x1, quad->y2);
			}
		}
		if(started) {
			glEnd();
		}
	}

	// This handles the case of being at the very end of the text.
	if(actualCursorPos && *actualCursorPos == 0) {
		cursorDrawLocX = x;
		cursorDrawLocY = y;
	}

	if(actualCursorPos && cursorColor) {
		glColor4f(cursorColor[0], cursorColor[1], cursorColor[2], cursorColor[3]);
		drawCursor(cursorDrawLocX, cursorDrawLocY, scale, origHeight);
		glColor4f(1, 1, 1, 1);
	}

	delete[] quads;
	
	// Need at least room to display one line.
	if(ySizePrinted) {
		if(text[0]) *ySizePrinted += (float(origHeight) - verticalSquish) * scale;
		
		// Add padding back to the bottom.
		*ySizePrinted += verticalSquish;
	}
	
	if(xSizePrinted) {
		// Add padding back to the right.
		*xSizePrinted += squish * scale;
	}
	
	if(adaptedText) free(adaptedText);
}

void MalFont::setSpaceSize(int spaceSize) {
	this->spaceSize = spaceSize;
}

void MalFont::setSquish(int squish) {
	this->squish = squish;
}

void MalFont::setVerticalSquish(int verticalSquish) {
	this->verticalSquish = verticalSquish;
}

int MalFont::getOrigHeight(void) {
    return origHeight;
}
