/*
 *  ppui/Graphics_OGL.cpp
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker 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.
 *
 *  Milkytracker 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 Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "Graphics.h"
#include "Font.h"

#ifdef __OPENGL__

#ifdef __APPLE__
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif

static void setupOrtho(pp_uint32 width, pp_uint32 height)
{
	glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, width, height, 0, -1.0, 1.0);
    glTranslatef(0.5f, 0.5f, 0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

PPGraphics_OGL::PPGraphics_OGL(pp_int32 w, pp_int32 h) :
	PPGraphicsAbstract(w, h),
	fontCacheEntry(NULL)
{
	setupOrtho(w, h);
	
	//glDrawBuffer(GL_FRONT_AND_BACK);
	
	glEnable(GL_SCISSOR_TEST);
	
	glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);	
	
	glDisable(GL_POINT_SMOOTH);
	glDisable(GL_LINE_SMOOTH);
	glLineWidth(1.0f);

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
}

void PPGraphics_OGL::setPixel(pp_int32 x, pp_int32 y)
{
	glBegin(GL_POINTS);
	glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
	glVertex2i(x, y);
	glEnd();
}

void PPGraphics_OGL::setPixel(pp_int32 x, pp_int32 y, const PPColor& color)
{
	glBegin(GL_POINTS);
	glColor3ub(color.r, color.g, color.b);        
	glVertex2i(x, y);
	glEnd();
}

void PPGraphics_OGL::fill(PPRect rect)
{
	glBegin(GL_QUADS);
	glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
	glVertex2i(rect.x1, rect.y1);
	
	//glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
	glVertex2i(rect.x2, rect.y1);
	
	//glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
	glVertex2i(rect.x2, rect.y2);
	
	//glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
	glVertex2i(rect.x1, rect.y2);
    glEnd();
}

void PPGraphics_OGL::fill()
{
	fill(currentClipRect);
}

void PPGraphics_OGL::drawHLine(pp_int32 x1, pp_int32 x2, pp_int32 y)
{
	glBegin(GL_LINES);
	glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
	glVertex2i(x1, y);
	glVertex2i(x2, y);
	glEnd();
}

void PPGraphics_OGL::drawVLine(pp_int32 y1, pp_int32 y2, pp_int32 x)
{
	glBegin(GL_LINES);
	glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
	glVertex2i(x, y1);
	glVertex2i(x, y2);
	glEnd();
}

void PPGraphics_OGL::drawLine(pp_int32 x1, pp_int32 y1, pp_int32 x2, pp_int32 y2)
{
	glBegin(GL_LINES);
	glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
	glVertex2i(x1, y1);
	glVertex2i(x2, y2);
	glEnd();
}

void PPGraphics_OGL::drawAntialiasedLine(pp_int32 x1, pp_int32 y1, pp_int32 x2, pp_int32 y2)
{
	glEnable(GL_LINE_SMOOTH);
	glBegin(GL_LINES);
	glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
	glVertex2i(x1, y1);
	glVertex2i(x2, y2);
	glEnd();
	glDisable(GL_LINE_SMOOTH);
}

void PPGraphics_OGL::blit(const pp_uint8* src, const PPPoint& p, const PPSize& size, pp_uint32 pitch, pp_uint32 bpp, pp_int32 intensity/* = 256*/)
{
}


void PPGraphics_OGL::drawChar(pp_uint8 chr, pp_int32 x, pp_int32 y, bool underlined)
{
	if (!fontCacheEntry || !currentFont)
		return;
		
	glRasterPos2d(x, y);
	
	pp_uint32 offset = (pp_uint32)chr * fontCacheEntry->newWidth * fontCacheEntry->newHeight;
	
	glColor3ub(currentColor.r, currentColor.g, currentColor.b);
	glBitmap(currentFont->getCharWidth(), currentFont->getCharHeight(), 
			 0, currentFont->getCharHeight()-1,
			 0, 0, 
			 (GLubyte*)fontCacheEntry->oglBitmapData+offset);	
	
	if (underlined)
	{
		glBegin(GL_LINES);
		glVertex2i(x, y+currentFont->getCharHeight());
		glVertex2i(x+currentFont->getCharWidth(), y+currentFont->getCharHeight());
		glEnd();
	}
}

void PPGraphics_OGL::drawString(const char* str, pp_int32 x, pp_int32 y, bool underlined/* = false*/)
{
	if (!fontCacheEntry || !currentFont)
		return;

	glColor3ub(currentColor.r, currentColor.g, currentColor.b);

	pp_int32 charWidth = (signed)currentFont->getCharWidth();
	pp_int32 charHeight = (signed)currentFont->getCharHeight();
	const GLubyte* data = (const GLubyte*)fontCacheEntry->oglBitmapData;
	
	glRasterPos2d(x, y);

	// Testing - No underline or \n support
	// Note: Even when using display lists, the opengl renderer is desperately slow
	//       (on my Nvidia at least), it's unusable at fullscreen resolutions. Perhaps
	//       using point-sprites would be quicker..  - Chris
	
	//glPushAttrib(GL_LIST_BIT);
	glCallLists(strlen(str), GL_UNSIGNED_BYTE, (GLubyte *) str);
	//glPopAttrib();

/*	pp_int32 sx = x;

	while (*str) 
	{
		switch (*str)
		{
			case '\xf4':
				glBegin(GL_POINTS);
				glVertex2i(x+(charWidth>>1), y+(charHeight>>1));
				glEnd();
				break;
			case '\n':
				y+=charHeight;
				x=sx-charWidth;
				glRasterPos2d(x, y);
				break;
			default:
			{
				pp_uint32 offset = ((pp_uint8)*str) * size;
				//glRasterPos2d(x, y);
				glBitmap(charWidth, charHeight, 0, charHeight-1, charWidth, 0, data+offset);	
				if (underlined)
				{
					glBegin(GL_LINES);
					glVertex2i(x, y+charHeight);
					glVertex2i(x+charWidth, y+charHeight);
					glEnd();
				}
			}
		}
        x += charWidth;
        str++;
    }*/
}

void PPGraphics_OGL::drawStringVertical(const char* str, pp_int32 x, pp_int32 y, bool underlined/* = false*/)
{
	if (!fontCacheEntry || !currentFont)
		return;

	glColor3ub(currentColor.r, currentColor.g, currentColor.b);

	pp_int32 charWidth = (signed)currentFont->getCharWidth();
	pp_int32 charHeight = (signed)currentFont->getCharHeight();
	const GLubyte* data = (const GLubyte*)fontCacheEntry->oglBitmapData;
	const pp_uint32 size = fontCacheEntry->newWidth * fontCacheEntry->newHeight;

    while (*str) 
	{
		switch (*str)
		{
			case '\xf4':
				glBegin(GL_POINTS);
				glVertex2i(x+(charWidth>>1), y+(charHeight>>1));
				glEnd();
				break;
			default:
			{
				pp_uint32 offset = ((pp_uint8)*str) * size;
				glRasterPos2d(x, y);
				glBitmap(charWidth, charHeight, 0, charHeight-1, 0, 0, data+offset);	
				if (underlined)
				{
					glBegin(GL_LINES);
					glVertex2i(x, y+charHeight);
					glVertex2i(x+charWidth, y+charHeight);
					glEnd();
				}
			}
		}
        y += charHeight;
        str++;
    }
}

void PPGraphics_OGL::fillVerticalShaded(PPRect r, const PPColor& colSrc, const PPColor& colDst, bool invertShading)
{
	if (invertShading)
	{
		glBegin(GL_QUADS);
		glColor3ub(colDst.r, colDst.g, colDst.b);        
		glVertex2i(r.x1, r.y1);
		
		//glColor3ub(colSrc.r, colSrc.g, colSrc.b);        
		glVertex2i(r.x2, r.y1);
		
		glColor3ub(colSrc.r, colSrc.g, colSrc.b);        
		glVertex2i(r.x2, r.y2);
		
		//glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
		glVertex2i(r.x1, r.y2);
		glEnd();
	}
	else
	{
		glBegin(GL_QUADS);
		glColor3ub(colSrc.r, colSrc.g, colSrc.b);        
		glVertex2i(r.x1, r.y1);
		
		//glColor3ub(colSrc.r, colSrc.g, colSrc.b);        
		glVertex2i(r.x2, r.y1);
		
		glColor3ub(colDst.r, colDst.g, colDst.b);        
		glVertex2i(r.x2, r.y2);
		
		//glColor3ub(currentColor.r, currentColor.g, currentColor.b);        
		glVertex2i(r.x1, r.y2);
		glEnd();
	}
}

void PPGraphics_OGL::validateRect()
{
	PPGraphicsAbstract::validateRect();

	glScissor(currentClipRect.x1, 
			  this->height-currentClipRect.y1-(currentClipRect.y2-currentClipRect.y1), 
			  currentClipRect.x2-currentClipRect.x1,
			  currentClipRect.y2-currentClipRect.y1);
}

void PPGraphics_OGL::setFont(PPFont* font)
{
	PPGraphicsAbstract::setFont(font);
	
	pp_int32 charWidth = (signed)font->getCharWidth();
	pp_int32 charHeight = (signed)font->getCharHeight();
	
	
	bool found = false;
	for (pp_int32 i = 0; i < sizeof(fontCache) / sizeof(FontCacheEntry); i++)
	{
		if (fontCache[i].font && fontCache[i].oldFontBits == font->fontBits)
		{
			found = true;
			fontCacheEntry = &fontCache[i];
			glListBase(fontCacheEntry->listOffset);
			break;
		}
	}

	if (!found)
	{
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		glPixelStorei(GL_UNPACK_LSB_FIRST, true);
		glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
		glPixelStorei(GL_UNPACK_ROW_LENGTH, ((font->getCharWidth()+7) / 8) * 8);

		pp_int32 slot = 0;
		for (pp_int32 i = 0; i < sizeof(fontCache) / sizeof(FontCacheEntry); i++)
		{
			if (!fontCache[i].font)
			{
				slot = i;
				break;
			}
		}
		
		fontCache[slot].createFromFont(font);
		fontCacheEntry = &fontCache[slot];
		
		fontCacheEntry->listOffset = glGenLists(256);
		glListBase(fontCacheEntry->listOffset);
		const GLubyte* data = (const GLubyte*)fontCacheEntry->oglBitmapData;
		const pp_uint32 size = fontCacheEntry->newWidth * fontCacheEntry->newHeight;
		

		for(pp_uint32 i = 0; i < 256; i++)
		{
			glNewList(fontCacheEntry->listOffset + i, GL_COMPILE);
			glBitmap(charWidth, charHeight, 0, charHeight-1, charWidth, 0, data+i*size);
			glEndList();
		}
	}

}

void PPGraphics_OGL::FontCacheEntry::createFromFont(PPFont* font)
{
	this->font = font;
	this->oldFontBits = font->fontBits;

	newWidth = (font->getCharWidth() + 7) / 8;
	newHeight = font->getCharHeight();
	
	pp_uint32 size = newWidth*newHeight*256;
	
	delete[] oglBitmapData;
	oglBitmapData = new pp_uint8[size];
	memset(oglBitmapData, 0, size);
	
	Bitstream stream(oglBitmapData, size);
	
	pp_uint32 dst = 0;
	for (pp_uint32 i = 0; i < 256; i++)
	{
		for (pp_uint32 y = 0; y < font->getCharHeight(); y++)
		{			
			if ((dst & 7) != 0)
				dst = ((dst+7)/8)*8;
			for (pp_uint32 x = 0; x < font->getCharWidth(); x++)
			{
				stream.write(dst, font->getPixelBit(i, x, font->getCharHeight() - 1 - y));
				dst++;
			}
		}
	}
}

PPGraphics_OGL::FontCacheEntry::~FontCacheEntry()
{
	delete[] oglBitmapData;
}

#endif