/*
   rdesktop: A Remote Desktop Protocol client.
   User interface services - X-Windows
   Copyright (C) Matthew Chapman 1999-2001
   
   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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "rdesktop.h"

#include <SDL.h>
#include <errno.h>

extern int width;
extern int height;
extern BOOL sendmotion;
extern BOOL fullscreen;

static SDL_Surface *screen;

static int need_update;
static SDL_Rect update_rect;

static SDL_Rect clip_rect;

static void add_update_rect(int x, int y, int w, int h)
{
	if (need_update) {
		int x1 = x < update_rect.x ? x : update_rect.x;
		int y1 = y < update_rect.y ? y : update_rect.y;
		int x2 = x + w > update_rect.x + update_rect.w ?
			x + w :
			update_rect.x + update_rect.w;
		int y2 = y + h > update_rect.y + update_rect.h ?
			y + h :
			update_rect.y + update_rect.h;

		update_rect.x = x1;
		update_rect.y = y1;
		update_rect.w = x2 - x1;
		update_rect.h = y2 - y1;
	} else {
		SDL_Rect newrect = {x, y, w, h};
		update_rect = newrect;
		need_update = 1;
	}
}

BOOL
ui_create_window(char *title)
{
	unsigned int flags = 0;
	
	SDL_Init(SDL_INIT_VIDEO);

	if (fullscreen)
		flags |= SDL_FULLSCREEN;
	
	screen = SDL_SetVideoMode(width, height, 8, flags);

	if (!screen) {
		error("ui_create_window: unable to set video mode");
		return False;
	}

	// set title

	SDL_WM_SetCaption(title, NULL);
	
	return True;
}

void
ui_destroy_window()
{
	SDL_QuitSubSystem(SDL_INIT_VIDEO);

	screen = NULL;
}

static uint8
sdl_translate_key(SDL_keysym key)
{
	DEBUG(("KEY(code=0x%lx)\n", key.unicode));

	if ((key.scancode > 8) && (key.scancode <= 0x60))
		return (key.scancode - 8);

	switch (key.sym)
	{
		case SDLK_HOME:	/* home */
			return 0x47 | 0x80;
		case SDLK_UP:	/* up arrow */
			return 0x48 | 0x80;
		case SDLK_PAGEUP:	/* page up */
			return 0x49 | 0x80;
		case SDLK_LEFT:	/* left arrow */
			return 0x4b | 0x80;
		case SDLK_RIGHT:	/* right arrow */
			return 0x4d | 0x80;
		case SDLK_END:	/* end */
			return 0x4f | 0x80;
		case SDLK_DOWN:	/* down arrow */
			return 0x50 | 0x80;
		case SDLK_PAGEDOWN:	/* page down */
			return 0x51 | 0x80;
		case SDLK_INSERT:	/* insert */
			return 0x52 | 0x80;
		case SDLK_DELETE:	/* delete */
			return 0x53 | 0x80;
		case SDLK_KP_ENTER:	/* keypad enter */
			return 0x1c | 0x80;
		case SDLK_RCTRL:	/* right ctrl */
			return 0x1d | 0x80;
			//case SDLK_:	/* ctrl - print screen */
			//return 0x37 | 0x80;
		case SDLK_KP_DIVIDE:	/* keypad '/' */
			return 0x35 | 0x80;
		case SDLK_RALT:	/* right alt */
			return 0x38 | 0x80;
			//case 0x72:	/* ctrl break */
			//return 0x46 | 0x80;
			//case 0x73:	/* left window key */
			//return 0xff;	/* real scancode is 5b */
			//case 0x74:	/* right window key */
			//return 0xff;	/* real scancode is 5c */
			//case 0x75:	/* menu key */
			//return 0x5d | 0x80;
	}

	return 0;
}

static uint16
sdl_translate_mouse(unsigned long button)
{
	return
		button == SDL_BUTTON_LEFT ? MOUSE_FLAG_BUTTON1 :
		button == SDL_BUTTON_RIGHT ? MOUSE_FLAG_BUTTON2 :
		button == SDL_BUTTON_MIDDLE ? MOUSE_FLAG_BUTTON3 : 0;
}

static void
sdl_process_events()
{
	uint32 ev_time;
	SDL_Event ev;
	int scancode, button;
	int mm_motion = 0, mm_x = -1, mm_y;
	
//	puts("process_events");
	
//	if (display == NULL)
	//return;
	while (SDL_PollEvent(&ev)) {

		ev_time = SDL_GetTicks();

		switch (ev.type) {
		case SDL_KEYDOWN:
			puts("keydown");
			scancode = sdl_translate_key(ev.key.keysym);
			if (scancode == 0)
				break;
			
			rdp_send_input(ev_time, RDP_INPUT_SCANCODE, 0,
				       scancode, 0);
			break;

		case SDL_KEYUP:
			scancode = sdl_translate_key(ev.key.keysym);
			if (scancode == 0)
				break;

			rdp_send_input(ev_time, RDP_INPUT_SCANCODE,
				       KBD_FLAG_DOWN | KBD_FLAG_UP,
				       scancode, 0);
			break;

		case SDL_MOUSEBUTTONDOWN:
			button = sdl_translate_mouse(ev.button.button);
			if (button == 0)
				break;
			
			rdp_send_input(ev_time, RDP_INPUT_MOUSE,
				       button | MOUSE_FLAG_DOWN,
				       ev.button.x,
				       ev.button.y);
			break;

		case SDL_MOUSEBUTTONUP:
			button = sdl_translate_mouse(ev.button.button);
			if (button == 0)
				break;

			rdp_send_input(ev_time, RDP_INPUT_MOUSE,
				       button,
				       ev.button.x, ev.button.y);
				       
			break;

		case SDL_MOUSEMOTION:
			mm_motion = 1;
			mm_x = ev.motion.x;
			mm_y = ev.motion.y;
			break;

/*			
		case EnterNotify:
		XGrabKeyboard(display, wnd, True, GrabModeAsync,
		GrabModeAsync, CurrentTime);
		break;

		case LeaveNotify:
		XUngrabKeyboard(display, CurrentTime);
		break;
		
		case Expose:
		XCopyArea(display, backstore, wnd, gc,
		event.xexpose.x, event.xexpose.y,
		event.xexpose.width, event.xexpose.height,
		event.xexpose.x, event.xexpose.y);
		break;
*/
		}
	}

	if (mm_motion) {
		rdp_send_input(ev_time, RDP_INPUT_MOUSE,
			       MOUSE_FLAG_MOVE,
			       mm_x, mm_y);
	}
}

void
ui_select(int rdp_socket)
{
	struct timeval tv;

	if (need_update) {
		SDL_UpdateRects(screen, 1, &update_rect);
		need_update = 0;
	}
	
	// TODO: update display here

	while (True) {
		fd_set rfds;
		
		FD_ZERO(&rfds);
		FD_SET(rdp_socket, &rfds);

		tv.tv_sec = 0;
		tv.tv_usec = 0;
		
		switch (select(rdp_socket+1, &rfds, NULL, NULL, &tv)) {
			case -1:
				error("select: %s\n", strerror(errno));

		}

		sdl_process_events();
		
		if (FD_ISSET(rdp_socket, &rfds))
			return;
	}
}

void
ui_move_pointer(int x, int y)
{
	SDL_WarpMouse(x, y);
}

HBITMAP
ui_create_bitmap(int width, int height, uint8 *data)
{
// puts("FIXME: create_bitmap");
	return SDL_CreateRGBSurfaceFrom(data, width, height, 4, width,
					0, 0, 0, 0);
}

void
ui_paint_bitmap(int x, int y, int cx, int cy,
		int width, int height, uint8 *data)
{
	SDL_Rect srcrect = {0, 0, cx, cy};
	SDL_Rect destrect = {x, y, cx, cy};
	SDL_Surface *s
		= SDL_CreateRGBSurfaceFrom(data, width, height, 8, width,
					   0, 0, 0, 0);

	SDL_SetPalette(s, SDL_LOGPAL,
		       screen->format->palette->colors,
		       0,screen->format->palette->ncolors);
	
	SDL_BlitSurface(s, &srcrect, screen, &destrect);

	SDL_FreeSurface(s);

	add_update_rect(x, y, cx, cy);
	//puts("FIXME: paint_bitmap");
}

void
ui_destroy_bitmap(HBITMAP bmp)
{
	//puts("FIXME: destroy_bitmap");
	SDL_FreeSurface((SDL_Surface *) bmp);
}

HGLYPH
ui_create_glyph(int width, int height, uint8 *data)
{
	unsigned char *glyph;
	int x, y;
	int bit = 7;
	int i = 0;

	//printf("new glyph: %i, %i\n", width, height);
	
	glyph = malloc(width * height);

	// bit packed, msb is the first pixel, then shift down to get
	// the next pixel
	
	for (y=0; y<height; ++y) {
		for (x=0; x<width; ++x) {
			glyph[i++] = (*data & (1 << bit)) != 0;
			
			//printf("%c", glyph->data[i] ? 'x' : ' ');

			bit--;
			if (bit < 0) {
				bit = 7;
				++data;
			}
		}

		// new lines start on byte boundaries

		//puts("");
		
		if (bit < 7) {
			bit = 7;
			++data;
		}
	}
	
	return glyph;	
}

void
ui_destroy_glyph(HGLYPH glyph)
{
	free(glyph);
}

HCURSOR
ui_create_cursor(unsigned int x, unsigned int y, int width,
		 int height, uint8 *andmask, uint8 *xormask)
{
	puts("FIXME: create_cursor");
}

void
ui_set_cursor(HCURSOR cursor)
{
	puts("FIXME: set_cursor");
}

void
ui_destroy_cursor(HCURSOR cursor)
{
	puts("FIXME: destroy_cursor");
}

HCOLOURMAP
ui_create_colourmap(COLOURMAP *colours)
{
	SDL_Palette *pal = malloc(sizeof(*pal));
	int i;

	pal->ncolors = colours->ncolours;
	pal->colors = malloc(sizeof(*pal->colors) * pal->ncolors);

	for (i=0; i<pal->ncolors; ++i) {
		pal->colors[i].r = colours->colours[i].red;
		pal->colors[i].g = colours->colours[i].green;
		pal->colors[i].b = colours->colours[i].blue;
	}

	return pal;
}

void
ui_destroy_colourmap(HCOLOURMAP map)
{
	SDL_Palette *pal = (SDL_Palette *) map;

	free(pal->colors);
	free(pal);
}

void
ui_set_colourmap(HCOLOURMAP map)
{
	SDL_Palette *pal = (SDL_Palette *) map;
	SDL_SetPalette(screen, SDL_PHYSPAL, pal->colors, 0, pal->ncolors);
}

void
ui_set_clip(int x, int y, int cx, int cy)
{	
	clip_rect.x = x;
	clip_rect.y = y;
	clip_rect.w = cx;
	clip_rect.h = cy;

	SDL_SetClipRect(screen, &clip_rect);
}

void
ui_reset_clip()
{
	clip_rect.x = clip_rect.y = 0;
	clip_rect.w = screen->w;
	clip_rect.h = screen->h;
	
	SDL_SetClipRect(screen, NULL);
}

void
ui_bell()
{
	printf("\a");
}

// this is broken

void
ui_destblt(uint8 opcode,
	   /* dest */ int x, int y, int cx, int cy)
{
	SDL_Rect rect = {x, y, cx, cy};

	puts("FIXME: destblt");

	printf("opcode: %i\n", opcode);
	SDL_FillRect(screen, &rect, 255);

	add_update_rect(x, y, cx, cy);
}

static inline void ui_patblt_pattern(int x, int y, int cx, int cy,
				     int fgcolour, int bgcolour,
				     unsigned char *pattern)
{
	int x1, y1;
	unsigned char *data;
	unsigned int bit;
	
	SDL_LockSurface(screen);

	data = screen->pixels + y * screen->pitch + x;

	for (y1=0; y1<cy; ++y1) {
		unsigned char patbyte = pattern[(y+y1) % 8];
		unsigned char *data2 = data;

		bit = ~x & 7;
		
		for (x1=0; x1<cx; ++x1) {
			if (patbyte & (1 << bit))
				*data2++ = bgcolour;
			else
				*data2++ = fgcolour;
			bit = (bit - 1) & 7;
		}

		data += screen->pitch;
	}
		     
	SDL_UnlockSurface(screen);

}

// this is broken

void
ui_patblt(uint8 opcode,
	  /* dest */ int x, int y, int cx, int cy,
	  /* brush */ BRUSH *brush, int bgcolour, int fgcolour)
{
	SDL_Rect rect = {x, y, cx, cy};

	puts("FIXME: patblt");

	switch (brush->style) {
	case 0: /* solid */
		SDL_FillRect(screen, &rect, fgcolour);
		break;
	case 3: /* pattern */
		ui_patblt_pattern(x, y, cx, cy, fgcolour, bgcolour,
				  brush->pattern);
		break;
	default:
		printf("ui_patblt: %i not implemented!\n",
		       brush->style);

	}
	
	add_update_rect(x, y, cx, cy);
}

void
ui_screenblt(uint8 opcode,
	     /* dest */ int x, int y, int cx, int cy,
	     /* src */ int srcx, int srcy)
{
	SDL_Rect srcrect = {srcx, srcy, cx, cy};
	SDL_Rect destrect = {x, y, cx, cy};

	SDL_BlitSurface(screen, &srcrect, screen, &destrect);

	add_update_rect(x, y, cx, cy);
}

void
ui_memblt(uint8 opcode,
	  /* dest */ int x, int y, int cx, int cy,
	  /* src */ HBITMAP src, int srcx, int srcy)
{
	puts("FIXME: memblt");
}

void
ui_triblt(uint8 opcode,
	  /* dest */ int x, int y, int cx, int cy,
	  /* src */ HBITMAP src, int srcx, int srcy,
	  /* brush */ BRUSH *brush, int bgcolour, int fgcolour)
{
	/* This is potentially difficult to do in general. Until someone
	   comes up with a more efficient way of doing it I am using cases. */

	puts("FIXME: triblt");
}

void
ui_line(uint8 opcode,
	/* dest */ int startx, int starty, int endx, int endy,
	/* pen */ PEN *pen)
{
	int dx = abs(startx-endx), dy = abs(starty-endy);

	puts("FIXME: line");
}

void
ui_rect( /* dest */ int x, int y, int cx, int cy,
	 /* brush */ int colour)
{

	SDL_Rect rect = {x, y, cx, cy};
	
	SDL_FillRect(screen, &rect, colour);

	add_update_rect(x, y, cx, cy);
}

void
ui_draw_glyph(int mixmode,
	      /* dest */ int x, int y, int cx, int cy,
	      /* src */ HGLYPH glyph, int srcx, int srcy, int bgcolour,
	      int fgcolour)
{
	unsigned char *data;
	unsigned char *glyphdata = glyph;
	
	int x1, y1;
//	puts("FIXME: draw_glyph");

	if (srcx || srcy) {
		puts("ui_draw_glyph: fixme: expecting srcx, srcy to be 0\n");
		return;
	}

	// check it is inside the clip area
	
	if (x + cx < clip_rect.x || x > clip_rect.x + clip_rect.w
	    || y + cy< clip_rect.y || y > clip_rect.y + clip_rect.h)
		return;
	
	//	printf("%i, %i, %i, %i, (%i, %i)\n", x, y, cx, cy);
	
	SDL_LockSurface(screen);

	data = screen->pixels + y * screen->pitch + x;

	if (mixmode == MIX_TRANSPARENT) {
		for (y1=0; y1<cy; ++y1) {
			unsigned char *data2 = data;
			
			for (x1=0; x1<cx; ++x1) {
				if (*glyphdata++)
					*data2 = fgcolour;

				++data2;
			}
			
			data += screen->pitch;
		}
	} else {
		for (y1=0; y1<cy; ++y1) {
			unsigned char *data2 = data;
			
			for (x1=0; x1<cx; ++x1) {
				*data2++ = *glyphdata++ ? fgcolour : bgcolour;
			}
			
			data += screen->pitch;
		}
	}
		
	
	SDL_UnlockSurface(screen);
}

void
ui_draw_text(uint8 font, uint8 flags, int mixmode, int x, int y,
	     int clipx, int clipy, int clipcx, int clipcy,
	     int boxx, int boxy, int boxcx, int boxcy,
	     int bgcolour, int fgcolour, uint8 *text, uint8 length)
{
	FONTGLYPH *glyph;
	SDL_Rect rect;
	int i, offset;

	if (boxcx > 1)
	{
		SDL_Rect rect = {boxx, boxy, boxcx, boxcy};
		SDL_FillRect(screen, &rect, bgcolour);
	}
	else if (mixmode == MIX_OPAQUE)
	{
		SDL_Rect rect = {clipx, clipy, clipcx, clipcy};
		SDL_FillRect(screen, &rect, bgcolour);
	}

	/* Paint text, character by character */
	for (i = 0; i < length; i++)
	{
		glyph = cache_get_font(font, text[i]);

		if (!(flags & TEXT2_IMPLICIT_X))
		{
			offset = text[++i];
			if (offset & 0x80)
				offset = ((offset & 0x7f) << 8) | text[++i];

			if (flags & TEXT2_VERTICAL)
				y += offset;
			else
				x += offset;
		}

		if (glyph != NULL)
		{
			ui_draw_glyph(mixmode, x + (short) glyph->offset,
				      y + (short) glyph->baseline,
				      glyph->width, glyph->height,
				      glyph->pixmap, 0, 0,
				      bgcolour, fgcolour);

			if (flags & TEXT2_IMPLICIT_X)
				x += glyph->width;
		}
	}
}

void
ui_desktop_save(uint32 offset, int x, int y, int cx, int cy)
{
	char *p;

	printf("save %i %i %i %i %i\n", offset, x, y, cx, cy);
	
	SDL_LockSurface(screen);

	p = screen->pixels + y * screen->pitch + x;
	
	cache_put_desktop(offset, cx, cy, screen->pitch,
			  1, p);
	
	SDL_UnlockSurface(screen);
}

void
ui_desktop_restore(uint32 offset, int x, int y, int cx, int cy)
{
	char *data, *p;
	int y1;
	
	printf("restore %i, %i, %i, %i, %i\n", offset, x, y, cx, cy);
	data = cache_get_desktop(offset, cx, cy, 1);

	if (!data)
		return;
	
	SDL_LockSurface(screen);

	p = screen->pixels + y * screen->pitch + x;

	for (y1=cy; y1 > 0; --y1,
		     p += screen->pitch,
		     data += cx) {
		memcpy(p, data, cx);
	}

	SDL_UnlockSurface(screen);

	add_update_rect(x, y, cx, cy);
}
