/*
 * 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
 */

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

#include <linux/fb.h>

#include <GL/glut.h>

#include "internal.h"

int GameMode;

static int ModePossible, DispChanged;
static struct fb_var_screeninfo NormVarInfo, GameVarInfo;

static GLFBDevContextPtr GameContext;
static GLFBDevVisualPtr NormVisual;

/* storage for non-gamemode callbacks */
void (*KeyFuncs[2])(unsigned char key, int x, int y);
static void (*NormFuncs[8])();

static const char*SetOpers(const char *p, unsigned int *min, unsigned int *max)
{
   char *endptr;
   int comp = *p, val, neq = 0;

   if(p[1] == '=') {
      neq = 0;
      p++;
   }

   val = strtol(p+1, &endptr, 10);
   if(endptr == p+1)
      return p;

   switch(comp) {
   case '=':
      *min = *max = val;
      break;
   case '!':
      *min = val + 1;
      *max = val - 1;
      break;
   case '<':
      *max = val - neq;
      break;
   case '>':
      *min = val + neq;
      break;
   }
   return endptr;
}

void glutGameModeString(const char *string)
{
   const char *p = string;
   unsigned int minb = 15, maxb = 32;
   unsigned int minw = 0, maxw = -1;
   unsigned int minh, maxh = -1;
   unsigned int minf = 0, maxf = MAX_VSYNC;
   char *endptr;
   int count = -1, val;

   ModePossible = 0;

   if(DisplayMode & GLUT_INDEX)
      minb = maxb = 8;

 again:
   count++;
   if((val = strtol(p, &endptr, 10)) && *endptr=='x') {
      maxw = minw = val;
      p = endptr + 1;
      maxh = minh = strtol(p, &endptr, 10);
      p = endptr;
      goto again;
   }

   if(*p == ':') {
      minb = strtol(p+1, &endptr, 10);
      p = endptr;
      if(DisplayMode & GLUT_INDEX) {
	 if(minb != 8)
	    return;
      } else
	 if(minb != 15 && minb != 16 && minb != 24 && minb != 32)
	    return;
      maxb = minb;
      goto again;
   }

   if(*p == '@') {
      minf = strtol(p+1, &endptr, 10) - 5;
      maxf = minf + 10;
      p = endptr;
      goto again;
   }

   if(count == 0)
      while(*p) {
	 if(*p == ' ')
	    p++;
	 else
	 if(memcmp(p, "bpp", 3) == 0)
	    p = SetOpers(p+3, &minb, &maxb);
	 else
	 if(memcmp(p, "height", 6) == 0)
	    p = SetOpers(p+6, &minh, &maxh);
	 else
	 if(memcmp(p, "hertz", 5) == 0)
	    p = SetOpers(p+5, &minf, &maxf);
	 else
	 if(memcmp(p, "width", 5) == 0)
	    p = SetOpers(p+5, &minw, &maxw);
	 else
	 if(p = strchr(p, ' '))
	    p++;
	 else
	    break;
   }

   NormVarInfo = VarInfo;
   if(!ParseFBModes(minw, maxw, minh, maxh, minf, maxf))
      return;

   GameVarInfo = VarInfo;
   VarInfo = NormVarInfo;

   /* determine optimal bitdepth, make sure we have enough video memory */
   if(VarInfo.bits_per_pixel && VarInfo.bits_per_pixel <= maxb)
      GameVarInfo.bits_per_pixel = VarInfo.bits_per_pixel;
   else
      GameVarInfo.bits_per_pixel = maxb;

   while(FixedInfo.smem_len < GameVarInfo.xres * GameVarInfo.yres
	 * GameVarInfo.bits_per_pixel / 8) {
      if(GameVarInfo.bits_per_pixel < minb)
	 return;
      GameVarInfo.bits_per_pixel = ((GameVarInfo.bits_per_pixel+1)/8)*8-8;
   }
  
   ModePossible = 1;
}
   
int glutEnterGameMode(void)
{
   if(ActiveMenu)
      return 0;

   if(!ModePossible)
      return 0;

   if(GameMode) {
      if(!memcmp(&GameVarInfo, &VarInfo, sizeof VarInfo)) {
	 DispChanged = 0;
	 return 1;
      }
      glutLeaveGameMode();
   }

   if (ioctl(FrameBufferFD, FBIOPUT_VSCREENINFO, &GameVarInfo))
      return 0;

   NormVarInfo = VarInfo;
   VarInfo = GameVarInfo;

   NormVisual = Visual;
   SetVideoMode();
   CreateVisual();
   CreateBuffer();

   if(!(GameContext = glFBDevCreateContext(Visual, NULL))) {
      sprintf(exiterror, "Failure to create Context\n");
      exit(0);
   }

   if(!glFBDevMakeCurrent( GameContext, Buffer, Buffer )) {
      sprintf(exiterror, "Failure to Make Game Current\n");
      exit(0);
   }

   InitializeCursor();

   KeyFuncs[0] = KeyboardFunc;
   KeyFuncs[1] = KeyboardUpFunc;

   NormFuncs[0] = DisplayFunc;
   NormFuncs[1] = ReshapeFunc;
   NormFuncs[2] = MouseFunc;
   NormFuncs[3] = MotionFunc;
   NormFuncs[4] = PassiveMotionFunc;
   NormFuncs[5] = VisibilityFunc;
   NormFuncs[6] = SpecialFunc;
   NormFuncs[7] = SpecialUpFunc;

   DisplayFunc = NULL;
   ReshapeFunc = NULL;
   KeyboardFunc = NULL;
   KeyboardUpFunc = NULL;
   MouseFunc = NULL;
   MotionFunc = NULL;
   PassiveMotionFunc = NULL;
   VisibilityFunc = NULL;
   SpecialFunc = SpecialUpFunc = NULL;

   DispChanged = 1;
   GameMode = 1;
   Visible = 1;
   VisibleSwitch = 1;
   Redisplay = 1;
   return 1;
}

void glutLeaveGameMode(void)
{
   if(!GameMode)
      return;

   glFBDevDestroyContext(GameContext);
   glFBDevDestroyVisual(Visual);

   VarInfo = NormVarInfo;
   Visual = NormVisual;
   
   if(Visual) {
      SetVideoMode();
      CreateBuffer();
   
      if(!glFBDevMakeCurrent( Context, Buffer, Buffer )) {
	 sprintf(exiterror, "Failure to Make Current\n");
	 exit(0);
      }
      
      Redisplay = 1;
   }

   KeyboardFunc = KeyFuncs[0];
   KeyboardUpFunc = KeyFuncs[1];

   DisplayFunc = NormFuncs[0];
   ReshapeFunc = NormFuncs[1];
   MouseFunc = NormFuncs[2];
   MotionFunc = NormFuncs[3];
   PassiveMotionFunc = NormFuncs[4];
   VisibilityFunc = NormFuncs[5];
   SpecialFunc = NormFuncs[6];
   SpecialUpFunc = NormFuncs[7];

   GameMode = 0;
}

int glutGameModeGet(GLenum mode) {
   switch(mode) {
   case GLUT_GAME_MODE_ACTIVE:
      return GameMode;
   case GLUT_GAME_MODE_POSSIBLE:
      return ModePossible;
   case GLUT_GAME_MODE_DISPLAY_CHANGED:
      return DispChanged;
   }

   if(!ModePossible)
      return -1;
   
   switch(mode) {
   case GLUT_GAME_MODE_WIDTH:
      return GameVarInfo.xres;
   case GLUT_GAME_MODE_HEIGHT:
      return GameVarInfo.yres;
   case GLUT_GAME_MODE_PIXEL_DEPTH:
      return GameVarInfo.bits_per_pixel;
   case GLUT_GAME_MODE_REFRESH_RATE:
      return 1E12/GameVarInfo.pixclock
	 / (GameVarInfo.left_margin + GameVarInfo.xres
	   + GameVarInfo.right_margin + GameVarInfo.hsync_len)
	 / (GameVarInfo.upper_margin + GameVarInfo.yres 
	   + GameVarInfo.lower_margin + GameVarInfo.vsync_len);
   }
}