// ---------------------------------------------------------------------------
// 
//   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 "../console/console.h"
using namespace Console;

#include <GL/glew.h>

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

#include "widgetfbo.h"

WidgetFBO::WidgetFBO(void) {
	needNewFBO = true;
	fbo = 0;
	image = 0;
	depthBuffer = 0;
	alpha = 1;
	pow2FBOWidth = 1;
	pow2FBOHeight = 1;
}

WidgetFBO::~WidgetFBO(void) {
	destroyFBO();
}

void WidgetFBO::setAlpha(float alpha) {
	this->alpha = alpha;
}

float WidgetFBO::getAlpha(void) {
	return alpha;
}

void WidgetFBO::draw(void) {

	if(needNewFBO) {
		createFBO();
	}

	// TODO: We should check and reset the projection matrix here, but
	//   it's got those weird stack issues. To avoid those issues, we
	//   could just save and restore it, but it may be slower.

	if(child) {
		
		glPushMatrix();

			pushClipArea(0, 0, 
				width, height);

			// Render to the texture.
			int realFrameBuffer = 0;
			glGetIntegerv(GL_FRAMEBUFFER_BINDING, &realFrameBuffer);
			glPushAttrib(GL_VIEWPORT_BIT);
			
			glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo);
			glViewport(0, 0, pow2FBOWidth, pow2FBOHeight);

			// Clear out the buffer, but clear the stencil buffer to
			// whatever is currently passing stencil tests.
			glClearStencil(clippingStack.size());
			glDisable(GL_STENCIL_TEST);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
			glEnable(GL_STENCIL_TEST);
			glClearStencil(0);
            glEnable(GL_TEXTURE_2D);
			
			glPushMatrix();
			
				// Clear out the matrix and leave our simple widget
				// drawing 1:1 texel thinger.
				glLoadIdentity();
				gluOrtho2D(0, pow2FBOWidth, pow2FBOHeight, 0);
				
				// Draw us.
				WidgetSingleParent::draw();

				// Draw the child.
				child->draw();
			
			glPopMatrix();
			
			// Restore viewport and "real" FBO (which could just be
			// another FBO up the stack).
			glPopAttrib();
			glBindFramebuffer(GL_FRAMEBUFFER_EXT, realFrameBuffer);
			
			// Render that texture to the screen.
			glBindTexture(GL_TEXTURE_2D, image);
			
			float texW = width  / float(pow2FBOWidth);
			float texH = 1.0 - (height / float(pow2FBOHeight));
			
			glColor4f(1, 1, 1, alpha);
			
			glBegin(GL_QUADS);
				glTexCoord2f(0,    1);    glVertex2f(0,     0);
				glTexCoord2f(texW, 1);    glVertex2f(width, 0);
				glTexCoord2f(texW, texH); glVertex2f(width, height);
				glTexCoord2f(0,    texH); glVertex2f(0,     height);
			glEnd();
			
			glColor4f(1, 1, 1, 1);
			
			glBindTexture(GL_TEXTURE_2D, 0);
			
			popClipArea();
		
		glPopMatrix();
	}

}

bool WidgetFBO::handleEvent(const WidgetEvent *event) {
	
	if(WidgetSingleParent::handleEvent(event)) return true;
	
	bool ret = false;
	
	// TODO: If alpha < full, or some threshold, DON'T pass events to
	//   children?
	
	if(child) {
		ret |= child->handleEvent(event);
	}
	
	return ret;
	
}

void WidgetFBO::markSizeDirty(void) {
	
	WidgetSingleParent::markSizeDirty();
	
	needNewFBO = true;
	
}

void WidgetFBO::destroyFBO(void) {
	if(!fbo) return;
	
	glDeleteFramebuffers(1, &fbo);
	glDeleteTextures(1, &image);
	glDeleteRenderbuffers(1, &depthBuffer);
	
	fbo = 0;
	image = 0;
	depthBuffer = 0;
}

void WidgetFBO::createFBO(void) {
	
	// There's got to be a faster way to do this...
	pow2FBOWidth = 1;
	pow2FBOHeight = 1;
	
	while(float(pow2FBOWidth) < width) {
		pow2FBOWidth <<= 1;
	}
	while(float(pow2FBOHeight) < height) {
		pow2FBOHeight <<= 1;
	}
	
	destroyFBO();
	
	// We could be in another FBO right now, so save whatever we were
	// rendering to so we can restore it.
	int realFrameBuffer = 0;
	glGetIntegerv(GL_FRAMEBUFFER_BINDING, &realFrameBuffer);
	
	glGenFramebuffers(1, &fbo);
	glBindFramebuffer(GL_FRAMEBUFFER_EXT, fbo);

	// Create depth + stencil buffer.
	glGenRenderbuffers(1, &depthBuffer);
	glBindRenderbuffer(GL_RENDERBUFFER_EXT, depthBuffer);
	glRenderbufferStorage(GL_RENDERBUFFER_EXT, GL_DEPTH_STENCIL_EXT, pow2FBOWidth, pow2FBOHeight);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,   GL_RENDERBUFFER_EXT, depthBuffer);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer);

	// Create Color buffer
	glGenTextures(1, &image);
	glBindTexture(GL_TEXTURE_2D, image);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, pow2FBOWidth, pow2FBOHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	glGenerateMipmap(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, image, 0);

	// Make sure everything is sane.
	GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if(status != GL_FRAMEBUFFER_COMPLETE_EXT) {
		out("error") << "Framebuffer setup failed: " << status << endl;
	}

	// Back to reality.
	glBindFramebuffer(GL_FRAMEBUFFER, realFrameBuffer);
	
	needNewFBO = false;
}

float WidgetFBO::getMinWidth(void) {
	float childSize = child ? child->getMinWidth() : 0;
	float mySize    = WidgetSingleParent::getMinWidth();
	return mySize > childSize ? mySize : childSize;
}

float WidgetFBO::getMinHeight(void) {
	float childSize = child ? child->getMinHeight() : 0;
	float mySize    = WidgetSingleParent::getMinHeight();
	return mySize > childSize ? mySize : childSize;
}

void WidgetFBO::setWidth(float newWidth) {
	float oldSize = width;
	WidgetSingleParent::setWidth(newWidth);
	if(oldSize != width) {
		needNewFBO = true;
	}
	
	if(child) child->setWidth(newWidth);
}

void WidgetFBO::setHeight(float newHeight) {
	float oldSize = height;
	WidgetSingleParent::setHeight(newHeight);
	if(oldSize != height) {
		needNewFBO = true;
	}
	
	if(child) child->setHeight(newHeight);
}


