/***************************************************************************
 *            wince_gfx.c
 *
 *  December 2009, Steffen Pohle steffen@gulpe.de
 ****************************************************************************/

/*
 *  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.
 */


#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <png.h>

#include "osmroute.h"

#include "draw.h"
#include "memoryleak.h"
#include "system.h"

iPoint gfx_screensize = {0, 0};
int gfx_last_alloc_img = 0;
int gfx_last_img_fbo = 0;
struct font *font = NULL;
void gfx_fbo_switch (struct image *img);

/*****************************************************************************/

void gfx_init (int width, int height) {
	/* init viewport */
	glViewport( 0, 0, ( GLint )width, ( GLint )height );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity( );
    glOrtho( 0.0f, width, height, 0.0f, -1.0f, 1.0f );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
	
	/* init opengl */
	glShadeModel( GL_SMOOTH );
    glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
    glClearDepth( 1.0f );
    glHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

	gfx_screensize.x = width;
	gfx_screensize.y = height;
	gfx_resize (width, height);

	font_init ();
	font = font_load ("FreeSans.ttf");
};


void gfx_resize (int width, int height) {
    if (height==0) height = 1;
	gfx_screensize.x = width;
	gfx_screensize.y = height;
	gfx_last_img_fbo = 1;	/* force to switch fbo to default */
	gfx_fbo_switch (NULL);
	return;
};


void gfx_flip () {
	gfx_fbo_switch (NULL);
	glFlush();
	glFinish ();
	SDL_GL_SwapBuffers();
};


void gfx_fbo_switch (struct image *img) {
	/* check if we need to switch back to default frame buffer */
	if (img == NULL && gfx_last_img_fbo == 0) {
		return;
	}
	if (img == NULL) {
		gfx_last_img_fbo = 0;
		glBindFramebuffer (GL_FRAMEBUFFER, 0);
	    glViewport (0, 0, gfx_screensize.x, gfx_screensize.y);
		glMatrixMode( GL_PROJECTION );
		glLoadIdentity( );
		glOrtho( 0.0f, gfx_screensize.x, gfx_screensize.y, 0.0f, -1.0f, 1.0f );
		glMatrixMode( GL_MODELVIEW );
		glLoadIdentity( );
	}
	else if (gfx_last_img_fbo == img->img_id) return;
	else {
		gfx_last_img_fbo = img->img_id;
		glBindFramebuffer (GL_FRAMEBUFFER, img->fbo_id);
	    glViewport (0, 0, img->width, img->height);
		glMatrixMode( GL_PROJECTION );
		glLoadIdentity( );
		glOrtho( 0.0f, img->width, 0.0f, img->height, -1.0f, 1.0f );
		glMatrixMode( GL_MODELVIEW );
		glLoadIdentity( );
	}
};

int gfx_color_alloc (struct color *c, unsigned short int r, unsigned short int g, unsigned short int b) {
	c->r = r;
	c->g = g;
	c->b = b;
	c->alloc = 1;
	c->c.r = (float)r / (float)0xFFFF;
	c->c.g = (float)g / (float)0xFFFF;
	c->c.b = (float)b / (float)0xFFFF;
	return 1;
};


void gfx_clear (struct image *dimg, struct color *c) {
	gfx_fbo_switch (dimg);
    glClearColor(c->c.r, c->c.g, c->c.b, 1.0f );
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
};


/*
 * draw line */
void gfx_draw_line (struct image *dimg, int x1, int y1, int x2, int y2, struct line_style style) {
	/* polygon mit runden ecken....? bei width > 1 */
	gfx_fbo_switch (dimg);
	if (style.width < 1.0) style.width = 1.0;
	glColor4f(style.c.c.r, style.c.c.g, style.c.c.b, 1.0);
	glPointSize(style.width*0.5);
	glBegin (GL_POINTS);
	glVertex2i (x1, y1);
	glVertex2i (x2, y2);
	glEnd ();
	glLineWidth (style.width);
	glBegin (GL_LINES);
	glVertex2i (x1, y1);
	glVertex2i (x2, y2);
	glEnd ();
};


/*
 * draw polygon
 */
void gfx_draw_polygon (struct image *dimg, iPoint *p, int pcnt, struct line_style style, struct color *c) {
	int i;

	gfx_fbo_switch (dimg);
	glBegin (GL_POLYGON);
	glColor4f (c->c.r, c->c.g, c->c.b, 1.0f);
	for (i = 0; i < pcnt; i++)
		glVertex2i (p[i].x, p[i].y);

	glEnd ();
};


/*
 * text drawing functions
 */
void gfx_draw_text (struct image *dimg, int x, int y, char *text, struct color *c) {
	gfx_fbo_switch (dimg);
	glColor4f (c->c.r, c->c.g, c->c.b, 1.0f);
	font_draw (font, text, x, y+12.0, 1.0, 1.0);
};


/*
 * drawing bitmap on the screen
 */
void gfx_draw_img (struct image *dimg, int dx, int dy, int dw, int dh, struct image *simg, int sx, int sy) {
	float fx, fy, fw, fh;

	gfx_fbo_switch (dimg);
	glEnable (GL_TEXTURE_2D);
	glBindTexture (GL_TEXTURE_2D, simg->txt_id);

	glBegin (GL_QUADS);
	glColor4f (1.0f, 1.0f, 1.0f, 1.0f);

	fx = (float) sx/(float) (simg->width);
	fy = (float) sy/(float) (simg->height);
	fw = (float) dw/(float) (simg->width);
	fh = (float) dh/(float) (simg->height);

	glTexCoord2d (fx, fy);
	glVertex2i (dx, dy);
	glTexCoord2d (fx, fy+fh);
	glVertex2i (dx, dy+dh);
	glTexCoord2d (fx+fw, fy+fh);
	glVertex2i (dx+dw, dy+dh);
	glTexCoord2d (fx+fw, fy);
	glVertex2i (dx+dw, dy);

	glEnd ();
	glBindTexture (GL_TEXTURE_2D, 0);
	glDisable (GL_TEXTURE_2D);
};


/*
 * allocate memory bitmap
 */
struct image *gfx_img_alloc (int w, int h) {
	struct image *img;
	GLenum status;

	img = ml_malloc (sizeof (struct image));
	img->width = w;
	img->height = h;
	img->rbo_id = 0;
	img->txt_id = 0;
	img->fbo_id = 0;
	img->img_id = (++gfx_last_alloc_img);
	d_printf ("gfx_img_alloc img_id:%d size: %d x %d", img->img_id, w, h);

	/* 
	 * create texture object */
	glGenTextures(1, &img->txt_id);
	glBindTexture(GL_TEXTURE_2D, img->txt_id);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); // automatic mipmap
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img->width, img->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
	glBindTexture(GL_TEXTURE_2D, 0);

	/* 
	 * create renderbuffer oobject */
	glGenRenderbuffers(1, &img->rbo_id);
	glBindRenderbuffer(GL_RENDERBUFFER, img->rbo_id);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, img->width, img->height);
	glBindRenderbuffer(GL_RENDERBUFFER, 0);

	/* 
	 * create framebuffer object */
	glGenFramebuffers(1, &img->fbo_id);
	glBindFramebuffer(GL_FRAMEBUFFER, img->fbo_id);
	
	/* attach renderbuffer and texture color to the framebuffer */
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, img->txt_id, 0);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, img->rbo_id);

	status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if(status != GL_FRAMEBUFFER_COMPLETE) {
		d_printf ("could not create framebuffer object w:%d h:%d txt_id:%d rbo_id:%d fbo_id:%d status:%d",
		          img->width, img->height, img->txt_id, img->rbo_id, img->fbo_id, status);
		errorexit (-1);
	}

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity( );
    glOrtho( 0.0f, w, h, 0.0f, -1.0f, 1.0f );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity( );
	
	/* init opengl */
	glShadeModel( GL_SMOOTH );
	glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
	glClearDepth( 1.0f );
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	/* switch back framebuffer to default */
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	
	return img;
};


void gfx_img_free (struct image *img) {
	d_printf ("gfx_img_free");
	glDeleteFramebuffers (1, &img->fbo_id);
	glDeleteRenderbuffers (1, &img->rbo_id);
	glDeleteTextures (1, &img->txt_id);
	if (img) ml_free (img);
};


#define HEADER_SIZE 8
int gfxpng_fd = 0;
void png_fd_read(png_structp png, png_bytep data, png_size_t size) {
    read(gfxpng_fd, data, size);
};


struct image* gfx_img_load (char *name) {
	int bit_depth, color_type;
	int transparency, i, rowbytes;
    png_uint_32 twidth, theight;
	png_byte* buffer = NULL;
	png_bytep *row_pointers = NULL;
	png_structp png_ptr = NULL;
	png_infop info_ptr = NULL;
	png_infop end_info = NULL;
	struct image *img = NULL;
	char fname[LEN_FILENAME];
	struct stat st_buf;

	sprintf (fname, "%s/%s", cfg.appdatapath, name);
    if ((gfxpng_fd = open(fname, O_RDONLY)) == 0) {
        d_printf ("File %s not found.", name);
        return NULL;
    }
	fstat (gfxpng_fd, &st_buf);
    buffer = malloc (sizeof (png_byte)*HEADER_SIZE);

    read(gfxpng_fd, buffer, HEADER_SIZE);
    if (png_sig_cmp(buffer, 0, 8)) {
		d_printf ("File %s format is not PNG.", name);
        return NULL;
    }

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) {
        d_printf ("Unable to create PNG structure: %s", name);
        return NULL;
    }

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
        d_printf ("Unable to create png info : %s", name);
        return NULL;
    }

    end_info = png_create_info_struct(png_ptr);
    if (!end_info) {
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
        d_printf ("Unable to create png end info : %s", name);
        return NULL;
    }

    if (setjmp(png_jmpbuf(png_ptr))) {
        d_printf ("Error during setjmp : %s", name);
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        return NULL;
    }
    png_set_read_fn(png_ptr, NULL, png_fd_read);
    png_set_sig_bytes(png_ptr, 8);
    png_read_info(png_ptr, info_ptr);
    png_get_IHDR(png_ptr, info_ptr, &twidth, &theight, &bit_depth, &color_type,
            NULL, NULL, NULL);
    d_printf ("Width: %d, height: %d, Depth: %d", twidth, theight, bit_depth);

	transparency = FALSE;
	if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
		png_set_tRNS_to_alpha(png_ptr);
		transparency = TRUE;
	}

	if (bit_depth < 8)		png_set_packing (png_ptr);
	else if (bit_depth >16)	png_set_strip_16 (png_ptr);

	img = (struct image *) ml_malloc (sizeof (struct image));
	img->width = twidth;
	img->height = theight;
	img->rbo_id = 0;
	img->txt_id = 0;
	img->fbo_id = 0;
	img->img_id = (++gfx_last_alloc_img);

	switch (color_type) {
		case PNG_COLOR_TYPE_PALETTE:
			png_set_palette_to_rgb(png_ptr);
			img->data_fmt = transparency ? GL_RGBA : GL_RGB;
			break;
		case PNG_COLOR_TYPE_RGB:
			img->data_fmt = transparency ? GL_RGBA : GL_RGB;
			break;
		case PNG_COLOR_TYPE_RGBA:
			img->data_fmt = GL_RGBA;
			break;
		default:
			d_printf ("unknown color_type:%x", color_type);
			break;
	}

	png_read_update_info(png_ptr, info_ptr);

	rowbytes = png_get_rowbytes(png_ptr, info_ptr);
	d_printf ("Row size: %d bytes.", rowbytes);

	img->data = (uint8_t*) ml_malloc (sizeof (png_byte) * rowbytes * theight);
    if (!img->data) {
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        d_printf ("Unable to allocate image_data while loading %s ", name);
		exit (0);
    }
	
    row_pointers = malloc (sizeof (png_bytep) *theight);
    if (!row_pointers) {
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        ml_free (img->data);
        d_printf ("Unable to allocate row_pointer while loading %s ", name);
		exit (0);
    }
    for (i = 0; i < theight; ++i)
        row_pointers[i] = img->data + i * rowbytes;

    png_read_image(png_ptr, row_pointers);
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
    close(gfxpng_fd);

	free (buffer);
	free (row_pointers);

	glGenTextures (1, &img->txt_id);
	glBindTexture (GL_TEXTURE_2D, img->txt_id);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexImage2D (GL_TEXTURE_2D, 0, img->data_fmt, img->width, img->height, 0,
	              img->data_fmt, GL_UNSIGNED_BYTE, img->data);
	glBindTexture (GL_TEXTURE_2D, 0);

	d_printf ("image loaded");
	
    return img;
};
