/* vi: set sw=4 ts=4: */
/*
 * CCE - Console Chinese Environment -
 * Copyright (C) 1998-2003 Rui He (rhe@3eti.com)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE TERRENCE R. LAMBERT BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 */

#include	<stdio.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<fcntl.h>

#include	"defs.h"
#include	"errors.h"
#include	"vc.h"
#include	"vga.h"
#include	"term.h"
#include	"mouse.h"

#if defined(SUPPORT_GGILIB)

#include  	<ggi/ggi.h>
#include	<ggi/gii.h>
#include	"cceggi.h"

ggi_visual_t ggiVis;
static ggi_pixel    ggiPix[16];   /* 16 VGA colors converted to pixel format */

static int ggiPixBytes;  /* How many bytes per pixel? */
static unsigned char *ggiPixBuf;  /* will dynamically allocated */

#ifdef USE_DYNAMIC_GGILIB

 int  (*p_ggiInit)(void);
 int  (*p_ggiExit)(void);
 ggi_visual_t (*p_ggiOpen)(const char *display,...);
 int          (*p_ggiClose)(ggi_visual_t vis);

 int (*p_ggiSetMode)(ggi_visual_t visual,ggi_mode *tm);
 int (*p_ggiCheckGraphMode)(ggi_visual_t visual, int x, int y,
     int xv, int yv, ggi_graphtype type, ggi_mode *suggested_mode);

 const ggi_pixelformat *(*p_ggiGetPixelFormat)(ggi_visual_t vis);
 ggi_pixel (*p_ggiMapColor)(ggi_visual_t vis,ggi_color *col);
 int (*p_ggiSetGCForeground)(ggi_visual_t vis,ggi_pixel  color);
 int (*p_ggiDrawBox)(ggi_visual_t vis,int x,int y,int w,int h);
 int (*p_ggiPutBox)(ggi_visual_t vis,int x,int y,int w,int h,void *buf);
 int (*p_ggiGetBox)(ggi_visual_t vis,int x,int y,int w,int h,void *buf);

 int (*p_ggiEventSelect)(ggi_visual_t vis, ggi_event_mask *mask, int n,
	fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
    struct timeval *timeout);

 int (*p_ggiEventRead)(ggi_visual_t vis, gii_event *ev,
	gii_event_mask mask);

static void *ggiLibHandle;

static char *ggiLibSearchPath[] = {
#if defined(__WINDOWS__)
	"ggi.dll",
#else
	"libggi.so",
	"libggi.so.2",  /* sometimes there is no libSDL.so symbolic link ?*/
	"/usr/local/lib/libggi.so",
	"/usr/local/lib/libggi.so.2",
#endif
	NULL,
};

static int LoadDynamicGGILibrary(void)
{
	int i;

	for(i = 0; ggiLibSearchPath[i]; i++)
	{
		ggiLibHandle = dlopen(ggiLibSearchPath[i], RTLD_LAZY);
		if (ggiLibHandle) break;
	}

	if (!ggiLibHandle) 
	{
		message("Failed to load GGI library %s.\r\n", ggiLibSearchPath[0]);
		return FAILURE;
	}

	p_ggiInit  = dlsym(ggiLibHandle, SYMBOL_GGIINIT);
	p_ggiExit  = dlsym(ggiLibHandle, SYMBOL_GGIEXIT);
	p_ggiOpen  = dlsym(ggiLibHandle, SYMBOL_GGIOPEN);
	p_ggiClose = dlsym(ggiLibHandle, SYMBOL_GGICLOSE);
	p_ggiEventSelect = dlsym(ggiLibHandle, SYMBOL_GGIEVENTSELECT);
	p_ggiEventRead = dlsym(ggiLibHandle, SYMBOL_GGIEVENTREAD);
	p_ggiCheckGraphMode = dlsym(ggiLibHandle, SYMBOL_GGICHECKGRAPHMODE);
	p_ggiSetMode = dlsym(ggiLibHandle, SYMBOL_GGISETMODE);
	p_ggiGetBox = dlsym(ggiLibHandle, SYMBOL_GGIGETBOX);	
	p_ggiPutBox = dlsym(ggiLibHandle, SYMBOL_GGIPUTBOX);
	p_ggiDrawBox = dlsym(ggiLibHandle, SYMBOL_GGIDRAWBOX);
	p_ggiSetGCForeground = dlsym(ggiLibHandle, SYMBOL_GGISETGCFOREGROUND);
	p_ggiMapColor = dlsym(ggiLibHandle, SYMBOL_GGIMAPCOLOR);
	p_ggiGetPixelFormat = dlsym(ggiLibHandle, SYMBOL_GGIGETPIXELFORMAT);

	if (!p_ggiInit || !p_ggiExit || !p_ggiEventSelect || !p_ggiEventRead || !p_ggiOpen || 
		!p_ggiClose || !p_ggiCheckGraphMode || !p_ggiSetMode || !p_ggiGetBox || !p_ggiPutBox ||
	    !p_ggiDrawBox || !p_ggiSetGCForeground || !p_ggiMapColor || !p_ggiGetPixelFormat)
	{
		warn("Can't resolve all the required GGI routines.\r\n");
		dlclose(ggiLibHandle);
		ggiLibHandle = NULL;
		return FAILURE;
	}
	
	return SUCCESS;
}

static void UnloadDynamicGGILibrary(void)
{
	if (ggiLibHandle) 
	{
		dlclose(ggiLibHandle);
		ggiLibHandle = NULL;
	}
}

#endif  /* USE_DYNAMIC_GGILIB */

int IsGGIAvailable(void)
{
#ifdef USE_DYNAMIC_GGILIB
	if (LoadDynamicGGILibrary() != SUCCESS) return 0;
#endif
	
  	if (GGIINIT() != 0) 
	{
		warn("Failed to initialize GGI library.\n");
		goto err;
	}

	if ((ggiVis = GGIOPEN(NULL)) == NULL) /* NULL will open the default Visual */
	{
		warn("Failed to open default GGI visual.\n");
		GGIEXIT();
		goto err;
	}

	dispInfo.type = TYPE_GGI; 
	return 1;

err:
#ifdef USE_DYNAMIC_GGILIB
	UnloadDynamicGGILibrary();
#endif
	return 0;
}

static void GGIMapColors(u_short *red, u_short *green, u_short *blue)
{
	int i;
	ggi_color color;

	for(i = 0; i < 16; i++)
	{
		color.r = red[i];
		color.g = green[i];
		color.b = blue[i];   /* r/g/b is 16 bits */
		ggiPix[i] = GGIMAPCOLOR(ggiVis, &color); /* convert from ggi_color to ggi_pixel */

	}
}

int GGIInit(void)
{
	int res;
	const ggi_pixelformat   *ggiPixFormat;
	ggi_mode		ggiMode;

	if (GGIAvail && !ggiVis)  /* haven't test it yet, do it now */
		GGIAvail = IsGGIAvailable();
	if (!GGIAvail) goto err;
		
	res = GGICHECKGRAPHMODE(ggiVis, GGI_AUTO, GGI_AUTO, GGI_AUTO, GGI_AUTO, GT_AUTO, &ggiMode);

	if (res != 0 || GGISETMODE(ggiVis, &ggiMode))
	{
		warn("Failed to set default GGI graphics mode.\n");
		goto err;
	}

	ggiPixFormat = GGIGETPIXELFORMAT(ggiVis);

#ifdef DEBUG
	fprintf(stderr, "PixelFormat: depth=%d size=%d red_shift=%d green_shift=%d blue_shift=%d\n",
		ggiPixFormat->depth, ggiPixFormat->size, ggiPixFormat->red_shift,
		ggiPixFormat->green_shift, ggiPixFormat->blue_shift);
#endif

	ggiPixBytes = (ggiPixFormat->size + 7) >> 3;
	GGIMapColors(red16, green16, blue16);

	dispInfo.gxdim = ggiMode.visible.x;
	dispInfo.gydim = ggiMode.visible.y;
	dispInfo.bpp = ggiPixFormat->depth; 
	dispInfo.type = TYPE_GGI;
	dispInfo.glineByte = ggiPixBytes * dispInfo.gxdim;   /* glineByte is not used ? */

	ggiPixBuf = malloc(ggiPixBytes * (fontwidth*2+2) * (fontheight+2)); 

	if (!ggiPixBuf) goto err;

	InitDispInfo();

	message("Using %dx%d %d-bpp GGI screen, %dx%d terminal.\n",
		dispInfo.gxdim, dispInfo.gydim, dispInfo.bpp, dispInfo.txmax, dispInfo.tymax);

	mInfo.has_mouse = TRUE;  /* use GGI's mouse support */

	return SUCCESS;

err:
	if (ggiVis) 
	{
		GGICLOSE(ggiVis);  /* ggiClose() will restore the old video settings! */
		ggiVis = NULL;
	}
	GGIEXIT();

#ifdef USE_DYNAMIC_GGILIB
	UnloadDynamicGGILibrary();
#endif
	
	return FAILURE;
	
}

void GGIDetach(void)
{
	if (ggiVis)
	{
		GGICLOSE(ggiVis);
		GGIEXIT();
		ggiVis = NULL;
	}

#ifdef USE_DYNAMIC_GGILIB
    UnloadDynamicGGILibrary();
#endif
}

#ifdef SUPPORT_FREETYPE_FONT

#include "ftfont.h"

void GGIDrawFreeTypeChar(int xpos, int ypos, FT_GlyphSlot glyph, int fc, int bc, int width, int height) 
{
	int i,j,k;
	u_char bits, *code;
	uint8 *dest = (uint8 *)ggiPixBuf;
	ggi_pixel pix;
    int top, left, row, col, pitch;

	if (glyph == NULL)
	{
		left=top=row=col=pitch=0;
		code = NULL;
	}
	else
	{
		left = ftXOrig + glyph->bitmap_left;  /* bitmap_left should be >= 0 ? */
		top = ftYOrig - glyph->bitmap_top;
		if (top < 0) top = 0;
		row = glyph->bitmap.rows;  /* rows/width >= 0 */
		col = glyph->bitmap.width;
		code = glyph->bitmap.buffer;
		pitch = glyph->bitmap.pitch; /* bitmap scan line bytes */
	}

	for(i = 0; i < height; i++)
	{
		k = 0;
		for(j = 0; j < width; j++)
		{
			if (i < top || i >= top+row || j < left || j >= left+col)
				pix = ggiPix[bc];
			else  /* in freetype font region */
			{
				if (glyph->bitmap.pixel_mode == ft_pixel_mode_mono) /* bit oriented */
				{
					if ((k & 0x7) == 0) bits = code[k/8];
				    pix = ggiPix[ bits & (0x80 >> (k & 0x7)) ? fc : bc ];
				}
				else  /* byte oriented ft_pixel_mode_grays */
				{
					ggi_color color;
					
					bits = code[k]; /* 0-255 gray scale */
					if (DoAntiAlias && ggiPixBytes > 1) /* TrueColor, do anti-aliasing */
					{
						color.r = red16[bc] + ((red16[fc]-red16[bc]) * bits)/255;
						color.g = green16[bc] + ((green16[fc]-green16[bc]) * bits)/255;
						color.b = blue16[bc] + ((blue16[fc]-blue16[bc]) * bits)/255;
						pix = GGIMAPCOLOR(ggiVis, &color);
					}
					else pix = ggiPix[ bits >= 128 ? fc: bc ]; /* do not do anti-alias */
				}
				k++;
			}
			
			switch(ggiPixBytes)
			{
			case 1:
				*dest++ = (uint8)(pix);  /* palette-based, no anti-alias */
				break;
			case 2:
				*((uint16 *)dest)++ = (uint16)(pix); 
				break;
			case 3:
				*dest++ = (uint8)(pix); pix >>= 8;
				*dest++ = (uint8)(pix); pix >>= 8;
				*dest++ = (uint8)(pix);
				break;
			case 4:
				*((uint32 *)dest)++ = (uint32)(pix);
				break;
			}
			
		}  /* for j */

		if (k > 0) /* k>0 means code is used somehow,  code could be NULL */
			code += pitch;
	}
	
	GGIPUTBOX(ggiVis, xpos, ypos, width, height, ggiPixBuf);
}

#endif /* SUPPORT_FREETYPE_FONT */

static inline void GGIGenPixelBuf(u_char *code, int fc, int bc, int width, int height)
{
	int i,j;
	u_char bits;
	uint8 *dest = (uint8 *)ggiPixBuf;
	ggi_pixel pix;
	
	for(i = 0; i < height; i++)
	{
		for(j = 0; j < width; j++)
		{
			if ((j & 0x7) == 0) bits = *code++;   /* j % 8 == 0 */
			pix = ggiPix[ bits & (0x80 >> (j & 0x7)) ? fc : bc ];
			switch(ggiPixBytes)
			{
			case 1:
				*dest++ = (uint8)(pix);
				break;
			case 2:
				*((uint16 *)dest)++ = (uint16)(pix); 
				break;
			case 3:
				*dest++ = (uint8)(pix); pix >>= 8;
				*dest++ = (uint8)(pix); pix >>= 8;
				*dest++ = (uint8)(pix);
				break;
			case 4:
				*((uint32 *)dest)++ = (uint32)(pix);
				break;
			}
		}
	}
}

void GGIOpDraw(int xpos, int ypos, u_char *code, int fc, int bc, int width, int height)
{
	GGIGenPixelBuf(code, fc, bc, width, height);
	GGIPUTBOX(ggiVis, xpos, ypos, width, height, ggiPixBuf);
}

void GGIOpClear(int y, int height, int color)
{    
	GGISETGCFOREGROUND(ggiVis, ggiPix[color]); 
    GGIDRAWBOX(ggiVis, 0, y, dispInfo.gxdim, height);
}

void GGIOpCursor(CursorInfo *ci, int xpos, int ypos)
{
	int width = fontwidth;
	int height = cursorBtm - cursorTop;
    int i;

	if (kanjiCursor && ci->kanji)
		width *= 2;

	GGIGETBOX(ggiVis, xpos, ypos, width, height, ggiPixBuf);
	
	for(i = 0; i < width * ggiPixBytes * height; i++)
	{

			ggiPixBuf[i] ^= 0xFF;
	}
	GGIPUTBOX(ggiVis, xpos, ypos, width, height, ggiPixBuf);
}

void GGIScreenSaver(bool blank)
{
}

void GGITextMode(void)
{
}

void GGIGraphMode(void)
{

	GGIOpClear(0, dispInfo.gydim - dispInfo.input, DEF_BGCOLOR);

}

void GGIStart(void)
{
	cursorTop = fontheight - 2;
	cursorBtm = fontheight - 1;
}

VideoInfo GGIInfo =
{
    GGIInit,
    GGIStart,
    GGITextMode,
    GGIGraphMode,
    CCEWput,
    CCEInputWput,
    CCESput,
    CCEInputSput,
	CCEInputDrawIcon,
    CCECursor,
    CCEClear,
    GGIScreenSaver,
    GGIDetach
};

#endif

