/*
 GNU Maverik - a system for managing display and interaction in 
               Virtual Environment applications.
 Copyright (C) 1999 Advanced Interfaces Group

 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 authors can be contacted via:
 www   - http://aig.cs.man.ac.uk
 email - maverik@aig.cs.man.ac.uk
 mail  - Advanced Interfaces Group, Room 2.90, Computer Science Building, 
         University of Manchester, Manchester, M13 9PL, UK
*/


#include "mavlib_windows.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>

int mavlib_defaultColours[][3]={
{148,   0, 211},             /* darkviolet */
{178,  34,  34},             /* firebrick */
{162, 205,  90},             /* darkolivegreen3 */
{135, 206, 255},             /* skyblue1 */
{112, 112, 112},             /* gray44 */
{255, 250, 205},             /* lemonchiffon1 */
{105, 105, 105},             /* gray41 */
{208,  32, 144},             /* violetred */
{255, 250, 205},             /* lemon chiffon */
{143, 143, 143},             /* grey56 */
{199,  21, 133},             /* mediumvioletred */
{132, 132, 132},             /* sgigrey52 */
{180, 238, 180},             /* darkseagreen2 */
{222, 222, 222},             /* grey87 */
{119, 136, 153},             /* lightslategray */
{ 64,  64,  64},             /* gray25 */
{255, 228, 196},             /* bisque1 */
{125,  38, 205},             /* purple3 */
{198, 113, 113},             /* sgi salmon */
{130, 130, 130},             /* gray51 */
{132, 112, 255},             /* light slate blue */
{137, 104, 205},             /* mediumpurple3 */
{ 51,  51,  51},             /* sgi gray 20 */
{240, 128, 128},             /* lightcoral */
{ 48,  48,  48},             /* gray19 */
{135, 206, 235},             /* sky blue */
{139, 123, 139},             /* thistle4 */
{238, 213, 210},             /* mistyrose2 */
{143, 143, 143},             /* gray56 */
{255, 174, 185},             /* lightpink1 */
{105, 105, 105},             /* dimgrey */
{255, 246, 143},             /* khaki1 */
{139,   0,   0},             /* darkred */
{238,  92,  66},             /* tomato2 */
{112, 112, 112},             /* sgi gray 44 */
{205, 129,  98},             /* lightsalmon3 */
{245, 245, 245},             /* white smoke */
{102, 102, 102},             /* grey40 */
{238, 130,  98},             /* salmon2 */
{105, 105, 105},             /* dimgray */
{ 84,  84,  84},             /* grey33 */
{175, 238, 238},             /* paleturquoise */
{169, 169, 169},             /* dark grey */
{198, 226, 255}              /* slategray1 */
};

extern int mavlib_texNum;
extern unsigned long mavlib_tex[][16384];

int mav_opt_noWins=MAV_FALSE;
int mav_opt_stereo=0;
int mav_opt_fullscreen=0;
int mav_opt_x=-1;
int mav_opt_y=-1;
int mav_opt_width=-1;
int mav_opt_height=-1;
char *mav_opt_name=NULL;
int mav_opt_right_x=-1;
int mav_opt_right_y=-1;
int mav_opt_right_width=-1;
int mav_opt_right_height=-1;
char *mav_opt_right_name=NULL;
#ifdef MAV_VOODOO
int mav_opt_restrictMouse= MAV_TRUE;
#else
int mav_opt_restrictMouse= MAV_FALSE;
#endif
MAV_class *mav_class_world;
MAV_class *mav_class_any;
MAV_class *mav_class_none;
MAV_object *mav_object_world;
MAV_object *mav_object_any;
MAV_object *mav_object_none;
MAV_ctrlF mav_ctrlF[15];
char *mav_ctrlF_desc[15];

MAV_window *mav_win_left=NULL;
MAV_window *mav_win_mono=NULL;
MAV_window *mav_win_right=NULL;

int mav_xres;
int mav_yres;
int mav_mouse_x;
int mav_mouse_y;
int mav_mouse_root_x;
int mav_mouse_root_y;
MAV_window *mav_win_mouse= NULL;
MAV_vector mav_mouse_pos;
MAV_vector mav_mouse_dir;

MAV_surfaceParams *mav_sp_default=NULL;
MAV_viewModifierParams mav_stp_default;



/* Routine to poll the windows device, i.e. get the mouse's position */

void mavlib_pollWindow(void)
{  
  if (mav_win_mouse) mav_mouseGet(mav_win_mouse, &mav_mouse_x, &mav_mouse_y, &mav_mouse_root_x, &mav_mouse_root_y);
}



/* Routine to calculate the world position of the mouse */

void mavlib_calcWindow(void)
{
  MAV_line ln;
  
  if (mav_win_mouse) {
    ln= mav_lineFrom2DPoint(mav_win_mouse, mav_mouse_x, mav_mouse_y);

    /* place at twice near clip plane distance so we can render at this position */
    mav_mouse_dir= ln.dir;
    mav_mouse_pos= mav_vectorAdd(ln.pt, mav_vectorScalar(ln.dir, mav_win_mouse->ncp*2));
  }
}



/* Routine to check the windows device for events, i.e. mouse, keyboard, resize etc... */

int mavlib_checkWindowEvents(void)
{
  int rv, rv2=0, info[20];

  /* get events from window manager */
  rv= mav_gfxWindowEventGet(info);

  switch (rv)  {

  case 1: /* keyboard event */
    rv2= mavlib_dealWithKeyboardEvent(info);
    break;
  case 2: /* mouse event */
    rv2= mavlib_dealWithMouseEvent(info);
    break;
  case 3: /* resize event (only deal with the last one) */
    while (mav_gfxWindowEventPeek()==rv) mav_gfxWindowEventGet(info);
    rv2= mavlib_dealWithResizeEvent(info);    
    break;
  case 4: /* map/unmap event (only deal with the last one) */
    while (mav_gfxWindowEventPeek()==rv) mav_gfxWindowEventGet(info);
    rv2= mavlib_dealWithMapEvent(info);
    break;
  case 5: /* enter/leave event (only deal with the last one) */
    while (mav_gfxWindowEventPeek()==rv) mav_gfxWindowEventGet(info);
    rv2= mavlib_dealWithCrossingEvent(info);
    break;
  case 6: /* expose event (only deal with the last one) */
    while (mav_gfxWindowEventPeek()==rv) mav_gfxWindowEventGet(info);
    rv2= mavlib_dealWithExposeEvent(info);
    break;
  }

  return rv2;
}



/* Routine to restrict mouse to window */

int mavlib_restrictLastX;
int mavlib_restrictLastY;

void mavlib_restrictMouse(void)
{
  if (mav_opt_restrictMouse) {
    /* Is pointer outside of window? */
    if (mav_mouse_x>mav_win_current->width  || mav_mouse_x<0 ||
	mav_mouse_y>mav_win_current->height || mav_mouse_y<0) 
    {
      /* If so move it to last safe position and resample mouse */
      mav_gfxWindowPointerSet(1, mavlib_restrictLastX, mavlib_restrictLastY);
      mavlib_pollWindow();
    }
    else
    {
      /* If not then update last safe position to be current position */
      mavlib_restrictLastX= mav_mouse_x;
      mavlib_restrictLastY= mav_mouse_y;
    }
  }
}



/* Reserved Ctrl F1 keypress */

int mavlib_fullscreen=1;
int mavlib_restrictMouseOpt=0;

void mavlib_cf1(void)
{
#ifdef MAV_LINUX
#ifdef MAV_VOODOO
  MAV_window *o, *w;
  
  mavlib_fullscreen=!mavlib_fullscreen;

  /* turn off/restore restrict mouse option */
  if (mavlib_fullscreen)
  {
    mav_opt_restrictMouse= mavlib_restrictMouseOpt;
  }
  else
  {
    mavlib_restrictMouseOpt= mav_opt_restrictMouse;
    mav_opt_restrictMouse= MAV_FALSE;
  }

  o= mav_win_current;
  mav_listPointerReset(mav_win_list);
  while (mav_listItemNext(mav_win_list, (void **) &w)) {
    mav_windowSet(w);
    mav_gfx3DfxModeSet(mavlib_fullscreen);
  }
  
  mav_windowSet(o);
#endif
#endif
}



/* Reserved Ctrl F12 keypress */

void mavlib_cf12(void)
{
  char buf[500], filename[500], initName[100];
  void *dlh;
  MAV_moduleInitFn fn;
  int i;

  if (!getenv("MAV_HOME")) {
    if (mav_opt_output==MAV_VERBOSE) fprintf(stderr, "Warning: MAV_HOME variable not set, cant load module\n");
    return;
  }

  /* Prompt user for module */
  fprintf(stderr, "Enter name of module to load:\n");
  fgets(buf, 500, stdin);

  /* Remove new line */
  buf[strlen(buf)-1]=0;
  
  /* Try to load that module */
  sprintf(filename, "%s/lib/libmav_%s.so", getenv("MAV_HOME"), buf);
  fprintf(stderr, "Looking for library file %s... ", filename);

#ifdef MAV_SUNOS4
  dlh= dlopen(filename, 1);
#else
  dlh= dlopen(filename, RTLD_NOW);
#endif  

  if (dlh) 
  {
    fprintf(stderr, "found it\n");
  }
  else
  {
    fprintf(stderr, "\n%s\n", dlerror());
    return;
  }

  /* Find the initialise function */
  sprintf(initName, "mav_%sModuleInit", buf);
  fprintf(stderr, "Looking for function %s... ", initName);

  fn= (MAV_moduleInitFn) dlsym(dlh, initName);

  if (fn)
  {
    fprintf(stderr, "got it, executing\n");
    fn();
  }
  else
  {
    fprintf(stderr, "failed\n");

    /* Capitalise and re-try */
    for (i=0; i<strlen(buf); i++) if (buf[i]>=97 && buf[i]<=122) buf[i]-=32;
    sprintf(initName, "mav_%sModuleInit", buf);
    fprintf(stderr, "Looking for function %s... ", initName);

    fn= (MAV_moduleInitFn) dlsym(dlh, initName);
    
    if (fn)
    {
      fprintf(stderr, "got it, executing\n");
      fn();
    }
    else
    {
      fprintf(stderr, "failed\n");
      return;
    }
  }
}



/* Routines to initialise the windows module */

char *mav_windowsModuleID(void)
{
  return "Windows";
}

int mav_windowsModuleInit(void)
{
  int lx, ly, lw=0, lh=0, rx, ry, rw, rh, i;

  /* add the new module */  
  mav_moduleNew(mav_windowsModuleID);

  /* add mouse/keyboard/window events as new devices */
  mav_deviceNew(mavlib_pollWindow, mavlib_calcWindow, mavlib_checkWindowEvents);

  /* define new classes for mouse/keyboard interaction */
  mav_class_world= mav_classNew();
  mav_class_any= mav_classNew();
  mav_class_none= mav_classNew();
  
  /* and their corresponding objects */
  mav_object_world= mav_objectNew(mav_class_world, NULL);
  mav_object_any= mav_objectNew(mav_class_any, NULL);
  mav_object_none= mav_objectNew(mav_class_none, NULL);

  /* define new callbacks for mouse/keyboard interaction and window events */
  mav_callback_keyboard= mav_callbackNew();
  mav_callback_sysKeyboard= mav_callbackNew();
  mav_callback_leftButton= mav_callbackNew();
  mav_callback_middleButton= mav_callbackNew();
  mav_callback_rightButton= mav_callbackNew();
  mav_callback_anyButton= mav_callbackNew();
  mav_callback_sysMouse= mav_callbackNew();
  mav_callback_resize= mav_callbackNew();
  mav_callback_map= mav_callbackNew();
  mav_callback_crossing= mav_callbackNew();
  mav_callback_expose= mav_callbackNew();
  
  /* define default handling routines for some event */
  mav_callbackResizeSet(mav_win_all, mav_resizeDefault);
  mav_callbackMapSet(mav_win_all, mav_mapDefault);
  mav_callbackExposeSet(mav_win_all, mav_exposeDefault);

  /* initialise ctrl-f key presses */
  for (i=0; i<15; i++) {
    mav_ctrlF[i]=NULL;
    mav_ctrlF_desc[i]=NULL;
  }

#ifdef MAV_LINUX
#ifdef MAV_VOODOO
  mav_ctrlF[1]= mavlib_cf1;
  mav_ctrlF_desc[1]= "Ctrl-F1 toggle between full screen and in-window rendering";
#endif
#endif

  mav_ctrlF[12]= mavlib_cf12;
  mav_ctrlF_desc[12]= "Ctrl-F12 load a module on the fly";

  /* store screen resolution */
  mav_gfxWindowResGet(&mav_xres, &mav_yres);

  /* open the requested number of windows */
  if (!mav_opt_noWins) {

    /* calculate windows size and position */
    if (mav_opt_x>=0)
    {
      lx= mav_opt_x;
    }
    else
    {
      lx= 0;
    }

    if (mav_opt_y>=0)
    {
      ly= mav_opt_y;
    }
    else
    {
#ifdef MAV_VOODOO
      ly= 0;
#else
#ifdef MAV_LINUX
      ly= mav_yres/2-35;
#else
      ly= mav_yres/2;
#endif      
#endif
    }

    if (mav_opt_width>=0)
    {
      lw= mav_opt_width;
    }
    else
    {
#ifdef MAV_VOODOO
      lw= 640;
#else
      lw= mav_xres/2.0;
#endif
    }

    if (mav_opt_height>=0)
    {
      lh= mav_opt_height;
    }
    else
    {
#ifdef MAV_VOODOO
      lh= 480;
#else
      lh= mav_yres/2.0;
#endif
    }
  
    if (mav_opt_fullscreen==MAV_TRUE) {
      lx= 0;
      ly= 0;
      lw= mav_xres;
      lh= mav_yres;
    }

    if (mav_opt_right_x>=0)
    {
      rx= mav_opt_right_x;
    }
    else
    {
      rx= mav_xres/2;
    }

    if (mav_opt_right_y>=0)
    {
      ry= mav_opt_right_y;
    }
    else
    {
#ifdef MAV_VOODOO
      ry=0;
#else
      ry= mav_yres/2;
#endif
    }

    if (mav_opt_right_width>=0)
    {
      rw= mav_opt_right_width;
    }
    else
    {
#ifdef MAV_VOODOO
      rw= 640;
#else
      rw= mav_xres/2.0;;
#endif
    }

    if (mav_opt_right_height>=0)
    {
      rh= mav_opt_right_height;
    }
    else
    {
#ifdef MAV_VOODOO
      rh= 480;
#else
      rh= mav_yres/2.0;
#endif
    }
  
    if (mav_opt_fullscreen==MAV_TRUE) {
      rx= 0;
      ry= 0;
      rw= mav_xres;
      rh= mav_yres;
    }

    /* open the windows with the correct name */
    if (mav_opt_stereo) 
    {
      if (mav_opt_name) 
      {
	mav_win_left= mav_windowNew(lx, ly, lw, lh, mav_opt_name);
      }
      else
      {
	mav_win_left= mav_windowNew(lx, ly, lw, lh, "Maverik: left");
      }

#ifdef MAV_VOODOO
      mav_gfx3DfxBoardSet(1);
#endif

      if (mav_opt_right_name) 
      {
	mav_win_right= mav_windowNew(rx, ry, rw, rh, mav_opt_right_name);  
      }
      else
      {
	mav_win_right= mav_windowNew(rx, ry, rw, rh, "Maverik: right");  
      }

      /* Set correct view */
      mav_stp_default.offset=0.0;
      mav_windowViewModifierSet(mav_win_left, &mav_stp_default, mav_eyeLeft);
      mav_windowViewModifierSet(mav_win_right, &mav_stp_default, mav_eyeRight);
    }
    else
    {
      if (mav_opt_name) 
      {
	mav_win_left= mav_windowNew(lx, ly, lw, lh, mav_opt_name);
      }
      else
      {
	mav_win_left= mav_windowNew(lx, ly, lw, lh, "Maverik: mono");
      }
    }

    mav_win_mono= mav_win_left;
    mav_win_mouse= mav_win_left;
  }

  /* Restrict mouse to left window if requested */

  if (mav_opt_restrictMouse) {
    mavlib_restrictLastX= lw/2;
    mavlib_restrictLastY= lh/2;
    mav_frameFn1Add(mavlib_restrictMouse);
  }

  /* Set default lighting model, light and material (now we have a valid gfx context) */
  if (mav_win_left) {
    mav_paletteLightingModelSet(mav_palette_default, 0.4, 0.4, 0.4, 1.0, MAV_TRUE);
    mav_paletteLightSet(mav_palette_default, 0,  0.0, 0.0, 0.0, 1.0,  1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
    mav_paletteLightPos(mav_palette_default, 0, mav_vectorSet(100,150,150));

    /* do not warn on redefine */
    mav_palette_default->lm.defined= MAV_REDEFINE_NOWARN;
    mav_palette_default->lightlist[0].defined= MAV_REDEFINE_NOWARN;

    for (i=0; i<20; i++) {
      mav_paletteColourSet(mav_palette_default, i, mavlib_defaultColours[i][0]/255.0, mavlib_defaultColours[i][1]/255.0, mavlib_defaultColours[i][2]/255.0, 1.0);

      mav_paletteMaterialSet(mav_palette_default, i, mavlib_defaultColours[i][0]/255.0, mavlib_defaultColours[i][1]/255.0, mavlib_defaultColours[i][2]/255.0, 1.0, mavlib_defaultColours[i][0]/255.0, mavlib_defaultColours[i][1]/255.0, mavlib_defaultColours[i][2]/255.0, 1.0, mavlib_defaultColours[i][0]/255.0, mavlib_defaultColours[i][1]/255.0, mavlib_defaultColours[i][2]/255.0, 1.0, 0.0, 0.0, 0.0, 1.0, 30.0);

      mav_palette_default->matlist[i].defined= MAV_REDEFINE_NOWARN;
      mav_palette_default->collist[i].defined= MAV_REDEFINE_NOWARN;
    }

    for (i=0; i<mavlib_texNum; i++) {
      mav_paletteTextureSetFromMem(mav_palette_default, i+1, 128, 128, mavlib_tex[i]);
      mav_palette_default->texlist[i+1].defined= MAV_REDEFINE_NOWARN;
    }

    mav_paletteFontSet(mav_palette_default, 0, "-adobe-helvetica-bold-r-*-*-14-140-*-*-*-*-*-*");
    mav_palette_default->fontlist[0].defined= MAV_REDEFINE_NOWARN;
  }

/* Define deault sp, should be valid even if palette has not been initialized */

  mav_sp_default= mav_surfaceParamsNew(MAV_MATERIAL, 0, 1, 0);

  return 1;
}



/* Routine to find a MAV_window pointer from its id number */

MAV_window *mavlib_getWindow(int id)
{
  MAV_window *win;

  mav_listPointerReset(mav_win_list);
  while (mav_listItemNext(mav_win_list, (void **) &win)) if (win->id==id) return win;
  
  return NULL;
}



/* Routine to calculate a line from a the mouse's position */

MAV_line mav_lineFrom2DPoint(MAV_window *w, int x, int y)
{
  MAV_line ln;
  MAV_vector up, right;
  float width, height, rx, ry;

  ln.pt= w->eye;

  /* get x and y in range 0.0 to 1.0 */
  rx= ((float) x)/w->width;
  ry= ((float) y)/w->height;

  /* get size of window */
  height= tan(w->fov*0.5*MAV_PI_OVER_180)*2.0;
  width= height * w->aspect;
  
  right= mav_vectorScalar(w->right, width*(rx-0.5));
  up= mav_vectorScalar(w->up, -height*(ry-0.5));

  ln.dir= mav_vectorAdd(w->view, mav_vectorAdd(right, up));
  ln.dir= mav_vectorNormalize(ln.dir);

  return ln;
}



/* Routines to perform stereo offset calculations */

void mav_eyeLeft(MAV_window *w)
{
  w->view= w->vp->trans_view;
  w->up= w->vp->trans_up;
  w->right= w->vp->trans_right;
  
  w->eye= mav_vectorSub(w->vp->trans_eye, mav_vectorScalar(w->right, w->vmp->offset*0.5));
}

void mav_eyeRight(MAV_window *w)
{
  w->view= w->vp->trans_view;
  w->up= w->vp->trans_up;
  w->right= w->vp->trans_right;

  w->eye= mav_vectorAdd(w->vp->trans_eye, mav_vectorScalar(w->right, w->vmp->offset*0.5));
}



