/*
 * Mesa 3-D graphics library
 * Version:  6.5
 * Copyright (C) 1995-2006  Brian Paul
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Library for glut using mesa fbdev driver
 *
 * Written by Sean D'Epagnier (c) 2006
 */

/* these routines are written to access graphics memory directly, not using mesa
   to render the cursor, this is faster, it would be good to use a hardware
   cursor if it exists instead */

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>

#include <linux/fb.h>

#include <GL/glut.h>

#include "internal.h"
#include "cursors.h"

int CurrentCursor = GLUT_CURSOR_LEFT_ARROW;

static int LastMouseX, LastMouseY;
static unsigned char *MouseBuffer;

void InitializeCursor(void)
{
   if(!MouseBuffer && (MouseBuffer = malloc(CURSOR_WIDTH * CURSOR_HEIGHT
			    * VarInfo.bits_per_pixel / 8)) == NULL) {
      sprintf(exiterror, "malloc failure\n");
      exit(0);
   }

   MouseX = VarInfo.xres / 2;
   MouseY = VarInfo.yres / 2;
}

void EraseCursor(void)
{
   int off = LastMouseY * FixedInfo.line_length 
      + LastMouseX * VarInfo.bits_per_pixel / 8;
   int stride = CURSOR_WIDTH * VarInfo.bits_per_pixel / 8;
   int i;

   unsigned char *src = MouseBuffer;

   if(!MouseVisible || CurrentCursor < 0 || CurrentCursor >= NUM_CURSORS)
      return;

   for(i = 0; i<CURSOR_HEIGHT; i++) {
      memcpy(BackBuffer + off, src, stride);
      src += stride;
      off += FixedInfo.line_length;
   }
}

static void SaveCursor(int x, int y)
{
   int bypp, off, stride, i;
   unsigned char *src = MouseBuffer;

   if(x < 0)
      LastMouseX = 0;
   else
      if(x > (int)VarInfo.xres - CURSOR_WIDTH)
	 LastMouseX = VarInfo.xres - CURSOR_WIDTH;
      else
	 LastMouseX = x;

   if(y < 0)
      LastMouseY = 0;
   else
      if(y > (int)VarInfo.yres - CURSOR_HEIGHT)
	 LastMouseY = VarInfo.yres - CURSOR_HEIGHT;
      else
	 LastMouseY = y;

   bypp = VarInfo.bits_per_pixel / 8;
   off = LastMouseY * FixedInfo.line_length + LastMouseX * bypp;
   stride = CURSOR_WIDTH * bypp;
   for(i = 0; i<CURSOR_HEIGHT; i++) {
      memcpy(src, BackBuffer + off, stride);
      src += stride;
      off += FixedInfo.line_length;
   }
}

void DrawCursor(void)
{
   int i, j, px, py, xoff, xlen, yoff, ylen, bypp, cstride, dstride;
   unsigned char *c;
   const unsigned char *d;

   if(!MouseVisible || CurrentCursor < 0 || CurrentCursor >= NUM_CURSORS)
      return;

   px = MouseX - CursorsXOffset[CurrentCursor];
   py = MouseY - CursorsYOffset[CurrentCursor];

   SaveCursor(px, py);

   xoff = 0;
   if(px < 0)
      xoff = -px;

   xlen = CURSOR_WIDTH;
   if(px + CURSOR_WIDTH > VarInfo.xres)
      xlen = VarInfo.xres - px;

   yoff = 0;
   if(py < 0)
      yoff = -py;

   ylen = CURSOR_HEIGHT;
   if(py + CURSOR_HEIGHT > VarInfo.yres)
      ylen = VarInfo.yres - py;

   bypp = VarInfo.bits_per_pixel / 8;

   c = BackBuffer + FixedInfo.line_length * (py + yoff) + (px + xoff) * bypp;
   cstride = FixedInfo.line_length - bypp * (xlen - xoff);

   d = Cursors[CurrentCursor] + (CURSOR_WIDTH * yoff + xoff)*4;
   dstride = (CURSOR_WIDTH - xlen + xoff) * 4;

   switch(bypp) {
   case 1:
      {
	 const int shift = 8 - REVERSECMAPSIZELOG;
	 for(i = yoff; i < ylen; i++) {
	    for(j = xoff; j < xlen; j++) {
	       if(d[3] < 220)
		  *c = ReverseColorMap
		     [(d[0]+(((int)(RedColorMap[c[0]]>>8)*d[3])>>8))>>shift]
		     [(d[1]+(((int)(GreenColorMap[c[0]]>>8)*d[3])>>8))>>shift]
		     [(d[2]+(((int)(BlueColorMap[c[0]]>>8)*d[3])>>8))>>shift];
	       c++;
	       d+=4;
	    }
	    d += dstride;
	    c += cstride;
	 } 
      } break;
   case 2:
      {
	 uint16_t *e = (void*)c;
	 cstride /= 2;
	 for(i = yoff; i < ylen; i++) {
	    for(j = xoff; j < xlen; j++) {
	       if(d[3] < 220)
		  e[0] = ((((d[0] + (((int)(((e[0] >> 8) & 0xf8) 
		     	 | ((c[0] >> 11) & 0x7)) * d[3]) >> 8)) & 0xf8) << 8)
		     | (((d[1] + (((int)(((e[0] >> 3) & 0xfc)
	   		 | ((e[0] >> 5) & 0x3)) * d[3]) >> 8)) & 0xfc) << 3)
		     | ((d[2] + (((int)(((e[0] << 3) & 0xf8)
		      	 | (e[0] & 0x7)) * d[3]) >> 8)) >> 3));
		
	       e++;
	       d+=4;
	    }
	    d += dstride;
	    e += cstride;
	 }
      } break;
   case 3:
   case 4:
      for(i = yoff; i < ylen; i++) {
	 for(j = xoff; j < xlen; j++) {
	    if(d[3] < 220) {
	       c[0] = d[0] + (((int)c[0] * d[3]) >> 8);
	       c[1] = d[1] + (((int)c[1] * d[3]) >> 8);
	       c[2] = d[2] + (((int)c[2] * d[3]) >> 8);
	    }
		
	    c+=bypp;
	    d+=4;
	 }
	 d += dstride;
	 c += cstride;
      } break;
   }
}

#define MIN(x, y) x < y ? x : y
void SwapCursor(void)
{
   int px = MouseX - CursorsXOffset[CurrentCursor];
   int py = MouseY - CursorsYOffset[CurrentCursor];

   int minx = MIN(px, LastMouseX);
   int sizex = abs(px - LastMouseX);

   int miny = MIN(py, LastMouseY);
   int sizey = abs(py - LastMouseY);

   if(MouseVisible)
      DrawCursor();

   /* now update the portion of the screen that has changed, this is also
      used to hide the mouse if MouseVisible is 0 */
   if(DisplayMode & GLUT_DOUBLE && ((sizex || sizey) || !MouseVisible)) {
      int off, stride, i;
      if(minx < 0)
	 minx = 0;
      if(miny < 0)
	 miny = 0;
	
      if(minx + sizex > VarInfo.xres - CURSOR_WIDTH)
	 sizex = VarInfo.xres - CURSOR_WIDTH - minx;
      if(miny + sizey > VarInfo.yres - CURSOR_HEIGHT)
	 sizey = VarInfo.yres - CURSOR_HEIGHT - miny;
      off = FixedInfo.line_length * miny
	 + minx * VarInfo.bits_per_pixel / 8;
      stride = (sizex + CURSOR_WIDTH) * VarInfo.bits_per_pixel / 8;

      for(i = 0; i < sizey + CURSOR_HEIGHT; i++) {
	 memcpy(FrameBuffer+off, BackBuffer+off, stride);
	 off += FixedInfo.line_length;
      }
   }
}

void glutWarpPointer(int x, int y) 
{
   if(x < 0)
      x = 0;
   if(x >= VarInfo.xres) 
      x = VarInfo.xres - 1;
   MouseX = x;

   if(y < 0)
      y = 0;
   if(y >= VarInfo.yres) 
      y = VarInfo.yres - 1;
   MouseY = y;

   EraseCursor();
   SwapCursor();
}

void glutSetCursor(int cursor)
{
   if(cursor == GLUT_CURSOR_FULL_CROSSHAIR)
      cursor = GLUT_CURSOR_CROSSHAIR;

   EraseCursor();
   MouseVisible = 1;
   CurrentCursor = cursor;
   SwapCursor();
}