
#include <jni.h>
#include <errno.h>

#include "osmroute.h"
#include "system.h"

#include <png.h>

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "sposmroute", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "sposmroute", __VA_ARGS__))

iPoint gfx_screensize;
struct font *drawfont = NULL;

int gfx_last_img_fbo = 0;
int gfx_last_alloc_img = 0;
void gfx_fbo_switch (struct image *img);

/*********************** GLES functions **************************************/
GLuint LoadShader (const char *shader_src, GLenum type) {
	GLuint shader;
	GLint compiled;

	shader = glCreateShader(type);
	if (shader == 0) return 0;

	glShaderSource (shader, 1, &shader_src, NULL);
	glCompileShader (shader);
	glGetShaderiv (shader, GL_COMPILE_STATUS, &compiled);
	if (!compiled) {
		GLint info_len = 0;
		glGetShaderiv (shader, GL_INFO_LOG_LENGTH, &info_len);
		if (info_len > 1) {
			char *info_log = malloc (info_len);

			glGetShaderInfoLog (shader, info_len, NULL, info_log);
			d_printf ("LoadShader compile error: '%s'", info_log);
			exit (1);
		}

		glDeleteShader (shader);
		return 0;
	}

	return shader;
};
	


/*****************************************************************************/
void gfx_init (int width, int height) {
	GLbyte vshader_str[] = {
        "attribute vec4 vPosition;                \n"
		"attribute vec2 vTexture;				  \n"
		"varying vec2 v_txcoordinate;			  \n"
		"uniform mat4 uMvp;						  \n"
        "void main() {                            \n"
		"  v_txcoordinate = vTexture;			  \n"
        "  gl_Position = vPosition * uMvp;        \n" 
        "}                                        \n"     };
	GLbyte fshader_str[] = {
        "precision mediump float;                    \n"
		"uniform vec4 vColor;                        \n"
		"uniform int txenabled;						 \n"
		"uniform sampler2D s_texture;				 \n"
		"varying mediump vec2 v_txcoordinate;		 \n"
        "void main() {                               \n"
          "  if (txenabled == 1)					 \n"
		  "    gl_FragColor = vColor * texture2D(s_texture, v_txcoordinate); \n"
		  "  else if (txenabled == 2)				 \n"
		  "    gl_FragColor = vColor * vec4 (1.0, 1.0, 1.0, texture2D(s_texture, v_txcoordinate).a); \n"
		  "  else                                    \n"
		  "    gl_FragColor = vColor;				 \n"
        "}                                           \n"     };
	GLuint vshader;
	GLuint fshader;
	GLuint prgobject;
	GLint linked;

	d_printf ("gfx_init");

	vshader = LoadShader (vshader_str, GL_VERTEX_SHADER);
	fshader = LoadShader (fshader_str, GL_FRAGMENT_SHADER);
	prgobject = glCreateProgram ();
	if (prgobject == 0) {
		d_printf ("glCreateProgramm failed..");
		exit (1);
	}
	glAttachShader (prgobject, vshader);
	glAttachShader (prgobject, fshader);
	glBindAttribLocation (prgobject, 0, "vPosition");
	glLinkProgram (prgobject);
	glGetProgramiv(prgobject, GL_LINK_STATUS, &linked);
	if (!linked) {
		GLint info_len;
		
		glGetProgramiv (prgobject, GL_INFO_LOG_LENGTH, &info_len);
		if (info_len > 0) {
			char *info_log = malloc (info_len);
			glGetProgramInfoLog(prgobject, info_len, NULL, info_log);
			d_printf ("Link Programm failed. : '%s'", info_log);
			exit (1);
		}
	}
	engine.gles_pos = glGetAttribLocation(prgobject, "vPosition");
	engine.gles_txcoord = glGetAttribLocation(prgobject, "vTexture");
	engine.gles_color = glGetUniformLocation(prgobject, "vColor");
	engine.gles_umvp = glGetUniformLocation(prgobject, "uMvp");
	engine.gles_txsampler = glGetUniformLocation(prgobject, "s_texture");
	engine.gles_txenabled = glGetUniformLocation(prgobject, "txenabled");
	engine.gles_prgobject = prgobject;

	glEnableVertexAttribArray (engine.gles_pos);
	glEnableVertexAttribArray (engine.gles_txcoord);

	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
    glEnable(GL_CULL_FACE);
    glDisable(GL_DEPTH_TEST);
	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 ();
	drawfont = font_load ("FreeSans.ttf");
};


void gfx_resize (int width, int height) {
	GLfloat matrix[] = { 
				2.0/(float)width,	 0.0,			 0.0,	  -1.0,
					 0.0,	-2.0/(float)height,		 0.0,	   1.0,
					 0.0,			 0.0,			 1.0, 	   0.0,
					 0.0, 			 0.0,			 0.0,	   1.0 };
	
	d_printf ("gfx_resize %d,%d", width, height);
    glViewport (0, 0, width, height);
	glUseProgram (engine.gles_prgobject);
	glUniformMatrix4fv(engine.gles_umvp, 1, GL_FALSE, matrix);

	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 () {
	if (engine.surface == NULL) return;
	gfx_fbo_switch (NULL);
	glFlush();
	glFinish ();
    eglSwapBuffers(engine.display, engine.surface);
};


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) {
		GLfloat matrix[] = { 
					2.0/(float)gfx_screensize.x,	 0.0,			 0.0,	  -1.0,
						 0.0,	-2.0/(float)gfx_screensize.y,		 0.0,	   1.0,
						 0.0,			 0.0,			 1.0, 	   0.0,
						 0.0, 			 0.0,			 0.0,	   1.0 };
	
		gfx_last_img_fbo = 0;
		glBindFramebuffer (GL_FRAMEBUFFER, 0);
	    glViewport (0, 0, gfx_screensize.x, gfx_screensize.y);
		glUseProgram (engine.gles_prgobject);
		glUniformMatrix4fv(engine.gles_umvp, 1, GL_FALSE, matrix);
		glEnableVertexAttribArray (engine.gles_pos);
		glEnableVertexAttribArray (engine.gles_txcoord);
	}
	else if (gfx_last_img_fbo == img->img_id) {
		return;
	}
	else {
		GLfloat matrix[] = { 
					2.0/(float)img->width,	 0.0,			 0.0,	  -1.0,
							 0.0,	-2.0/(float)img->height, 0.0,	  1.0,
							 0.0,			 0.0,			 1.0, 	   0.0,
							 0.0, 			 0.0,			 0.0,	   1.0 };
		gfx_last_img_fbo = img->img_id;
		glBindFramebuffer(GL_FRAMEBUFFER, img->fbo_id);
	    glViewport (0, 0, img->width, img->height);
		glUseProgram (engine.gles_prgobject);
		glUniformMatrix4fv(engine.gles_umvp, 1, GL_FALSE, matrix);
		glEnableVertexAttribArray (engine.gles_pos);
		glEnableVertexAttribArray (engine.gles_txcoord);
	}
};


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.elements.r = (float)r / (float)0xFFFF;
	c->c.elements.g = (float)g / (float)0xFFFF;
	c->c.elements.b = (float)b / (float)0xFFFF;
	c->c.elements.a = 1.0;
	return 1;
};


void gfx_clear (struct image *dimg, struct color *c) {
	gfx_fbo_switch (dimg);
    glClearColor(c->c.elements.r, c->c.elements.g, c->c.elements.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) {
	GLfloat vp[] = {x1, y1, 0.0, x2, y2, 0.0};
	gfx_fbo_switch (dimg);

	if (style.width < 1.0) style.width = 1.0;
	glLineWidth (style.width);

	glUseProgram (engine.gles_prgobject);
	glUniform1i (engine.gles_txenabled, 0);
	glUniform4fv(engine.gles_color, 1, style.c.c.array);
	glVertexAttribPointer (engine.gles_pos, 3, GL_FLOAT, GL_FALSE, 0, vp);
    glDrawArrays(GL_LINES,0,2);
};


/*
 * draw polygon
 */
void gfx_draw_polygon (struct image *dimg, iPoint *p, int pcnt, struct line_style style, struct color *c) {
	static GLfloat *polygon = NULL;
	static int polygon_cnt = 0;
	int i;

	if (polygon_cnt < pcnt) {	/* copied from GTK port.. if here is a bug, there is one as well.. */
		polygon_cnt = pcnt;
		if (polygon != NULL)
			polygon = (GLfloat *) ml_realloc (polygon, sizeof (GLfloat) * polygon_cnt * 3);
		else
			polygon = (GLfloat *) ml_malloc (sizeof (GLfloat) * polygon_cnt * 3);
	}

	for (i = 0; i < pcnt; i++) {
		polygon[i*3] = p[i].x;
		polygon[i*3+1] = p[i].y;
		polygon[i*3+2] = 0.0;
	}

	gfx_fbo_switch (dimg);

	glUseProgram (engine.gles_prgobject);
	glUniform1i (engine.gles_txenabled, 0);
	glUniform4fv(engine.gles_color, 1, c->c.array);
	glVertexAttribPointer (engine.gles_pos, 3, GL_FLOAT, GL_FALSE, 0, polygon);
    glDrawArrays (GL_TRIANGLE_FAN, 0, pcnt);

//	if (style.width < 1.0) style.width = 1.0;
//	glLineWidth (style.width);
//
//	glUseProgram (engine.gles_prgobject);
//	glUniform1i (engine.gles_txenabled, 0);
//	glUniform4fv(engine.gles_color, 1, style.c.c.array);
//	glVertexAttribPointer (engine.gles_pos, 3, GL_FLOAT, GL_FALSE, 0, polygon);
//   glDrawArrays (GL_LINE_LOOP, 0, pcnt);

};


/*
 * text drawing functions
 */
void gfx_draw_text (struct image *dimg, int x, int y, char *text, struct color *c) {
	gfx_fbo_switch (dimg);
	glUniform4fv(engine.gles_color, 1, c->c.array);
	font_draw (drawfont, text, x, y+16, 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) {
	GLfloat vp[] = { 	dx, 	dy, 	0.0,
						dx, 	dy+dh,	0.0,
						dx+dw, 	dy+dh,	0.0,
						dx+dw, 	dy,		0.0};
	float fx = (float) sx/(float) simg->width;
	float fy = (float) sy/(float) simg->height;
	float fw = (float) dw/(float) simg->width;
	float fh = (float) dh/(float) simg->height;
	GLfloat vt[] = {	fx,		fy,
						fx,		fy+fh,
						fx+fw,	fy+fh,
						fx+fw,	fy	};
	GLfloat colwhite[] = { 1.0, 1.0, 1.0, 1.0 };
	int i;
	if (simg == NULL) {
		d_printf ("ERROR: gfx_draw_img simg:%p", simg);
		return;
	}
	gfx_fbo_switch (dimg);

	/* somehow frame buffered devices are upside down */
	if (simg->fbo_id) for (i = 0; i < 4; i++) vt[i*2+1] = 1.0-vt[i*2+1];

	glEnable (GL_TEXTURE_2D);
	glActiveTexture (GL_TEXTURE0);
	glBindTexture (GL_TEXTURE_2D, simg->txt_id);
	glUniform1i (engine.gles_txenabled, 1);
	glUniform4fv(engine.gles_color, 1, colwhite);
	glUniform1i(engine.gles_txsampler, 0);

	glVertexAttribPointer (engine.gles_pos, 3, GL_FLOAT, GL_FALSE, 0, vp);
	glVertexAttribPointer (engine.gles_txcoord, 2, GL_FLOAT, GL_FALSE, 0, vt);

	glDrawArrays(GL_TRIANGLE_FAN,0,4);
	glUniform1i (engine.gles_txenabled, 0);
	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 = (struct image *) ml_malloc (sizeof (struct image));
	img->width = w;
	img->height = h;
	img->rbo_id = 0;
	img->txt_id = 0;
	img->fbo_id = 0;
	img->srb_id = 0;
	img->data = NULL;
	img->img_id = (++gfx_last_alloc_img);

	d_printf ("gfx_img_alloc img_id:%d size: %d x %d", img->img_id, w, h);

	glGenFramebuffers(1, &img->fbo_id);
	glGenTextures(1, &img->txt_id);
	glGenRenderbuffers(1, &img->rbo_id);
	glBindFramebuffer(GL_FRAMEBUFFER, img->fbo_id);

	// initialize color texture
	glBindTexture(GL_TEXTURE_2D, img->txt_id);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, img->txt_id, 0);
	glBindTexture(GL_TEXTURE_2D, 0);

	// initialize depth renderbuffer
	glBindRenderbuffer(GL_RENDERBUFFER, img->rbo_id);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, w, h);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, img->rbo_id);

	/* 
	 * create renderbuffer oobject */
	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);
	}

	glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
	glHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
	glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
	glClearDepthf( 1.0f );

	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 img:%p data:%p", img, img == NULL? NULL : img->data);
	glDeleteFramebuffers (1, &img->fbo_id);
	glDeleteRenderbuffers (1, &img->rbo_id);
	glDeleteTextures (1, &img->txt_id);
	if (img->data) ml_free (img->data);
	if (img) ml_free (img);
};


void gfx_draw_rect (struct image *dimg, int x1, int y1, int x2, int y2, struct color *c) {
	GLfloat vp[] = { 	x1, 	y1,		0.0, 
						x1, 	y2,		0.0,
						x2, 	y2,		0.0,
						x2, 	y1,		0.0		};
	gfx_fbo_switch (dimg);

	glUseProgram (engine.gles_prgobject);
	glUniform1i (engine.gles_txenabled, 0);
	glUniform4fv(engine.gles_color, 1, c->c.array);
	glVertexAttribPointer (engine.gles_pos, 3, GL_FLOAT, GL_FALSE, 0, vp);
    glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
};


#define HEADER_SIZE 8
AAsset* gfxpng_asset = NULL;
void png_asset_read(png_structp png, png_bytep data, png_size_t size) {
    int remain;
    AAsset_read(gfxpng_asset, data, size);
	remain = AAsset_getRemainingLength(gfxpng_asset);
};


struct image* gfx_img_load (char *name) {
	AAssetManager *mgr = engine.app->activity->assetManager;
	off_t size;
	int bytesremaining;
	int bytesread;
	int bit_depth, color_type;
	int transparency, i, rowbytes;
    png_uint_32 twidth, theight;
	png_byte* buffer = NULL;
	png_byte* image_data = 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;
	
    if ((gfxpng_asset = AAssetManager_open(mgr, name, AASSET_MODE_UNKNOWN)) == 0) {
        LOGW("Asset \"%s\" not found.", name);
        return NULL;
    }

    size = AAsset_getLength(gfxpng_asset);
    buffer = malloc (sizeof (png_byte)*HEADER_SIZE);

    bytesread = AAsset_read(gfxpng_asset, buffer, HEADER_SIZE);
    bytesremaining = AAsset_getRemainingLength(gfxpng_asset);

    if (png_sig_cmp(buffer, 0, 8)) {
		LOGW("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) {
        LOGW("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);
        LOGW("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);
        LOGW("Unable to create png end info : %s", name);
        return NULL;
    }

    if (setjmp(png_jmpbuf(png_ptr))) {
        LOGW("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_asset_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);
	LOGI("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);
        LOGW("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);
        LOGW("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);
    AAsset_close(gfxpng_asset);

	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;
};


/******************************************************************
 * convert polygon into triangles..
 */
