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

  Copyright(c) 2003-2004 Aurelien Reynaud.

  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.

  The full GNU General Public License is included in this distribution in the
  file called COPYING.

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

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "ggiterm.h"
#include "debug.h"

/*#include <sys/ioctl.h>*/ /* for resize */


/* External functions */
void render_grid(int, int);
int render_init(char *, char *, int *, int *);
void render_exit(void);
int backend_init(char *, int, int*, int*);
void backend_exit();

/* Stubs */
wchar_t native_get_charmap();
int native_cache_load(void *, wchar_t, int, int, int);
int native_has_glyph(wchar_t);

#ifdef HAVE_FREETYPE
wchar_t freetype_get_charmap();
int freetype_cache_load(void *, wchar_t, int, int, int);
int freetype_has_glyph(wchar_t);
#endif /* HAVE_FREETYPE */

void setorigin_write_char(int, int, wchar_t, int, int, int);
void setorigin_delete_chars(int, int, unsigned int);
void setorigin_insert_chars(int, int, unsigned int);
void setorigin_erase_chars(int, int, unsigned int);
void setorigin_erase_line_from(int, int);
void setorigin_erase_line_to(int, int);
void setorigin_erase_line(int);
#ifndef JUMP_SCROLL
void setorigin_scroll_up(int);
#endif
void setorigin_scroll_up_region(int, int, int);
void setorigin_scroll_down_region(int, int, int);
void setorigin_erase_screen_from(int, int);
void setorigin_erase_screen_to(int, int);
void setorigin_erase_screen();
void setorigin_flush();
void setorigin_flush_area(int, int, int, int);
void setorigin_flash();
void setorigin_prepare();

void copybox_write_char(int, int, wchar_t, int, int, int);
void copybox_delete_chars(int, int, unsigned int);
void copybox_insert_chars(int, int, unsigned int);
void copybox_erase_chars(int, int, unsigned int);
void copybox_erase_line_from(int, int);
void copybox_erase_line_to(int, int);
void copybox_erase_line(int);
#ifndef JUMP_SCROLL
void copybox_scroll_up(int);
#endif
void copybox_scroll_up_region(int, int, int);
void copybox_scroll_down_region(int, int, int);
void copybox_erase_screen_from(int, int);
void copybox_erase_screen_to(int, int);
void copybox_erase_screen();
void copybox_flush();
void copybox_flush_area(int, int, int, int);
void copybox_flash();
void copybox_prepare();

/* Global variables */
void (*render_write_char)(int, int, wchar_t, int, int, int);
void (*render_delete_chars)(int, int, unsigned int);
void (*render_insert_chars)(int, int, unsigned int);
void (*render_erase_chars)(int, int, unsigned int);
void (*render_erase_line_from)(int, int);
void (*render_erase_line_to)(int, int);
void (*render_erase_line)(int);
#ifndef JUMP_SCROLL
void (*render_scroll_up)(int);
#endif
void (*render_scroll_up_region)(int, int, int);
void (*render_scroll_down_region)(int, int, int);
void (*render_erase_screen_from)(int, int);
void (*render_erase_screen_to)(int, int);
void (*render_erase_screen)();
void (*render_flush)();
void (*render_flush_area)(int, int, int, int);
void (*render_flash)();
void (*render_prepare)();
wchar_t (*lowlevel_get_charmap)() = NULL;
int (*lowlevel_has_glyph)(wchar_t) = NULL;
int (*backend_cache_load)(void *, wchar_t, int, int, int) = NULL;


void lowlevel_load_render_stubs(int method)
{
	debug(DEBUG_FUNCTION, "called with args (method=%d)", method);
	
	switch (method) {
		case RENDER_COPYBOX:
			debug(DEBUG_INIT, "Loading CopyBox renderers");
			render_write_char         = &copybox_write_char;
			render_delete_chars       = &copybox_delete_chars;
			render_insert_chars       = &copybox_insert_chars;
			render_erase_chars        = &copybox_erase_chars;
			render_erase_line_from    = &copybox_erase_line_from;
			render_erase_line_to      = &copybox_erase_line_to;
			render_erase_line         = &copybox_erase_line;
#ifndef JUMP_SCROLL
			render_scroll_up          = &copybox_scroll_up;
#endif
			render_scroll_up_region   = &copybox_scroll_up_region;
			render_scroll_down_region = &copybox_scroll_down_region;
			render_erase_screen_from  = &copybox_erase_screen_from;
			render_erase_screen_to    = &copybox_erase_screen_to;
			render_flush_area         = &copybox_flush_area;
			render_flash              = &copybox_flash;
			render_erase_screen       = &copybox_erase_screen;
			render_flush              = &copybox_flush;
			render_prepare            = &copybox_prepare;
			break;
		case RENDER_SETORIGIN:
			debug(DEBUG_INIT, "Loading SetOrigin renderers");
			render_write_char         = &setorigin_write_char;
			render_delete_chars       = &setorigin_delete_chars;
			render_insert_chars       = &setorigin_insert_chars;
			render_erase_chars        = &setorigin_erase_chars;
			render_erase_line_from    = &setorigin_erase_line_from;
			render_erase_line_to      = &setorigin_erase_line_to;
			render_erase_line         = &setorigin_erase_line;
#ifndef JUMP_SCROLL
			render_scroll_up          = &setorigin_scroll_up;
#endif
			render_scroll_up_region   = &setorigin_scroll_up_region;
			render_scroll_down_region = &setorigin_scroll_down_region;
			render_erase_screen_from  = &setorigin_erase_screen_from;
			render_erase_screen_to    = &setorigin_erase_screen_to;
			render_flush_area         = &setorigin_flush_area;
			render_flash              = &setorigin_flash;
			render_erase_screen       = &setorigin_erase_screen;
			render_flush              = &setorigin_flush;
			render_prepare            = &setorigin_prepare;
			break;
	}
	debug(DEBUG_FUNCTION, "Leaving");
}

void lowlevel_load_backend_stubs(int backend)
{
	debug(DEBUG_FUNCTION, "called with args (backend=%d)", backend);
	
	switch (backend) {
		case BACKEND_NATIVE:
			debug(DEBUG_INIT, "Loading Native backends");
			lowlevel_get_charmap = &native_get_charmap;
			lowlevel_has_glyph   = &native_has_glyph;
			backend_cache_load   = &native_cache_load;
			break;
		case BACKEND_FREETYPE:
#ifdef HAVE_FREETYPE
			debug(DEBUG_INIT, "Loading Freetype backends");
			lowlevel_get_charmap = &freetype_get_charmap;
			lowlevel_has_glyph   = &freetype_has_glyph;
			backend_cache_load   = &freetype_cache_load;
#endif /* HAVE_FREETYPE */
			break;
	}
	debug(DEBUG_FUNCTION, "Leaving");
}

int lowlevel_init(char *mode, char *file, int size, char *title, int *term_w, int *term_h)
{
	int render_mode, backend;
	int render_w, render_h;
	int backend_w, backend_h;
	
	debug(DEBUG_FUNCTION,
	      "called with args (file=%s, size=%d, title=%s, *term_w, *term_h",
	      file, size, title);

	debug(DEBUG_INIT, "Initializing GGI");
	render_mode = render_init(mode, title, &render_w, &render_h);
	if (render_mode == RENDER_ERROR) {
		error("Cannot complete GGI setup. Aborting.");
		debug(DEBUG_FUNCTION, "Leaving");
		return 1;
	}
	lowlevel_load_render_stubs(render_mode);
	debug(DEBUG_INIT, "GGI setup complete");

	debug(DEBUG_INIT, "Initializing backend");
	backend = backend_init(file, size, &backend_w, &backend_h);
	if (backend == BACKEND_ERROR) {
		error("Cannot initialize backend. Aborting.");
		render_exit();
		debug(DEBUG_FUNCTION, "Leaving");
		return 1;
	}
	lowlevel_load_backend_stubs(backend);
	debug(DEBUG_INIT, "Backend initialization complete");
	debug(DEBUG_INIT, "Characters are %dx%d pixels", backend_w, backend_h);
	
	render_prepare();

	*term_w = render_w / backend_w;
	*term_h = render_h / backend_h;
#ifdef DEBUG
	if (debuglevel) {
		render_grid(backend_w, backend_h);
	}
#endif /* DEBUG */

	debug(DEBUG_FUNCTION, "Leaving");
	return 0;
}

void lowlevel_exit()
{
	debug(DEBUG_FUNCTION, "called with no args");
	backend_exit();
	render_exit();
	debug(DEBUG_FUNCTION, "Leaving");
}

#ifndef DISABLE_RESIZE
int lowlevel_resize(int new_w, int new_h)
{
	int old_visible_w, old_visible_h;
	int err;
	int w, h;
	struct winsize ws;
	ggi_mode mode;
	
	debug(DEBUG_FUNCTION, "Entering");
	
	set_cursor_status(CURSOR_HIDE);
	
	old_visible_w = visible_w;
	old_visible_h = visible_h;
	visible_w = new_w;
	visible_h = new_h;
	debug(DEBUG_INIT, "Set visible area to %dx%d", visible_w, visible_h);
	
	if (fast_scroll_enabled) {
		virtual_w = new_w;
		virtual_h = new_h*2;
	} else {
		virtual_w = new_w;
		virtual_h = new_h;
	}
	debug(DEBUG_INIT, "Set virtual area to %dx%d", virtual_w, virtual_h);
	debug(DEBUG_INIT, "Set current_origin to %d", current_origin);
	
	ggiGetMode(vis, &mode);
	mode.visible.x = visible_w;
	mode.visible.y = visible_h;
	mode.virt.x = virtual_w;
	mode.virt.y = virtual_h;
	ggiSetMode(vis, &mode);
	
	err = terminal_resize(visible_w/cell_w, visible_h/cell_h);
	
	ggiFlush(vis);
	
	terminal_get_size(&w, &h);
	ws.ws_col = w;
	ws.ws_row = h;
	ws.ws_xpixel = visible_w;
	ws.ws_ypixel = visible_h;
	if (ioctl(terminal_fd, TIOCSWINSZ, &ws) < 0) {
		error("Error setting window size\n");
	}
	
	debug(DEBUG_FUNCTION, "Leaving");
	return 1;
}
#endif /* DISABLE_RESIZE */
