/*
Copyright (C) 1996-1997 Id Software, Inc.

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.

*/
// in_mouse.c -- dos mouse code

#include "quakedef.h"
#include "dosisms.h"

#define AUX_FLAG_FREELOOK 0x00000001

typedef struct
{
  long    interruptVector;
  char    deviceName[16];
  long    numAxes;
  long  numButtons;
  long  flags;
  
  vec3_t  viewangles;

// intended velocities
  float   forwardmove;
  float   sidemove;
  float   upmove;

  long  buttons;
} externControl_t;

/*
#define AUX_FLAG_FORCEFREELOOK  0x00000001  // r/o
#define AUX_FLAG_EXTENDED   0x00000002  // r/o
#define AUX_FLAG_RUN      0x00000004  // w/o
#define AUX_FLAG_STRAFE     0x00000008  // w/o
#define AUX_FLAG_FREELOOK   0x00000010  // w/o

#define AUX_MAP_UNDEFINED 0
#define AUX_MAP_PITCH   1
#define AUX_MAP_YAW     2
#define AUX_MAP_ROLL    3
#define AUX_MAP_FORWARD   4
#define AUX_MAP_SIDE    5
#define AUX_MAP_UP      6

typedef struct
{
  long    interruptVector;
  // r/o
  char    deviceName[16];
  // r/o
  long    numAxes;
      // r/o  1-6
  long  numButtons;     // r/o  0-32
  long  flags;        // see above
  byte  axisMapping[6];   // w/o  default = p,y,r,f,s,u
  float axisValue[6];   // r/w
  float sensitivity[6];   // w/o  default = 1.0
  long  buttons;      // r/o
  float last_frame_time;  // w/o
} externControl_t;
*/

cvar_t  m_filter = {"m_filter","1"};

qboolean  mouse_avail;
int   mouse_buttons;
int   mouse_oldbuttonstate;
int   mouse_buttonstate;
float mouse_x, mouse_y;
float old_mouse_x, old_mouse_y;


cvar_t  in_joystick = {"joystick","1"};
cvar_t  joy_numbuttons = {"joybuttons","4", true};

qboolean  joy_avail;
int   joy_oldbuttonstate;
int   joy_buttonstate;

int     joyxl, joyxh, joyyl, joyyh; 
int   joystickx, joysticky;

qboolean    need_center;

qboolean    extern_avail;
int       extern_buttons;
int       extern_oldbuttonstate;
int       extern_buttonstate;
cvar_t  aux_look = {"auxlook","1", true};
externControl_t *extern_control;
void IN_StartupExternal (void);
void IN_ExternalMove (usercmd_t *cmd);

void IN_StartupJoystick (void);
qboolean IN_ReadJoystick (void);


void Toggle_AuxLook_f (void)
{
  if (aux_look.value)
    Cvar_Set ("auxlook","0");
  else
    Cvar_Set ("auxlook","1");
}


void Force_CenterView_f (void)
{
  cl.viewangles[PITCH] = 0;
}


/*
===========
IN_StartupMouse
===========
*/
void IN_StartupMouse (void)
{
  if ( COM_CheckParm ("-nomouse") ) 
    return; 
 
// check for mouse
  regs.x.ax = 0;
  dos_int86(0x33);
  mouse_avail = regs.x.ax;
  if (!mouse_avail)
  {
    Con_Printf ("No mouse found\n");
    return;
  }
  
  mouse_buttons = regs.x.bx;
  if (mouse_buttons > 3)
    mouse_buttons = 3;
  Con_Printf("%d-button mouse available\n", mouse_buttons);
}

/*
===========
IN_Init
===========
*/
void IN_Init (void)
{
  int i;

  Cvar_RegisterVariable (&m_filter);
  Cvar_RegisterVariable (&in_joystick);
  Cvar_RegisterVariable (&joy_numbuttons);
  Cvar_RegisterVariable (&aux_look);
  Cmd_AddCommand ("toggle_auxlook", Toggle_AuxLook_f);
  Cmd_AddCommand ("force_centerview", Force_CenterView_f);

  IN_StartupMouse ();
  IN_StartupJoystick ();

  i = COM_CheckParm ("-control");
  if (i)
  {
    extern_control = real2ptr(Q_atoi (com_argv[i+1]));
    IN_StartupExternal ();
  }
}

/*
===========
IN_Shutdown
===========
*/
void IN_Shutdown (void)
{

}


/*
===========
IN_Commands
===========
*/
void IN_Commands (void)
{
  int   i;

  if (mouse_avail)
  {
    regs.x.ax = 3;    // read buttons
    dos_int86(0x33);
    mouse_buttonstate = regs.x.bx;
  
  // perform button actions
    for (i=0 ; i<mouse_buttons ; i++)
    {
      if ( (mouse_buttonstate & (1<<i)) &&
      !(mouse_oldbuttonstate & (1<<i)) )
      {
        Key_Event (K_MOUSE1 + i, true);
      }
      if ( !(mouse_buttonstate & (1<<i)) &&
      (mouse_oldbuttonstate & (1<<i)) )
      {
        Key_Event (K_MOUSE1 + i, false);
      }
    } 
    
    mouse_oldbuttonstate = mouse_buttonstate;
  }
  
  if (joy_avail)
  {
    joy_buttonstate = ((dos_inportb(0x201) >> 4)&15)^15;
  // perform button actions
    for (i=0 ; i<joy_numbuttons.value ; i++)
    {
      if ( (joy_buttonstate & (1<<i)) &&
      !(joy_oldbuttonstate & (1<<i)) )
      {
        Key_Event (K_JOY1 + i, true);
      }
      if ( !(joy_buttonstate & (1<<i)) &&
      (joy_oldbuttonstate & (1<<i)) )
      {
        Key_Event (K_JOY1 + i, false);
      }
    }
    
    joy_oldbuttonstate = joy_buttonstate;
  }

  if (extern_avail)
  {
    extern_buttonstate = extern_control->buttons;
  
  // perform button actions
    for (i=0 ; i<extern_buttons ; i++)
    {
      if ( (extern_buttonstate & (1<<i)) &&
      !(extern_oldbuttonstate & (1<<i)) )
      {
        Key_Event (K_AUX1 + i, true);
      }
      if ( !(extern_buttonstate & (1<<i)) &&
      (extern_oldbuttonstate & (1<<i)) )
      {
        Key_Event (K_AUX1 + i, false);
      }
    } 
    
    extern_oldbuttonstate = extern_buttonstate;
  }
  
}


/*
===========
IN_Move
===========
*/
void IN_MouseMove (usercmd_t *cmd)
{
  int   mx, my;

  if (!mouse_avail)
    return;

  regs.x.ax = 11;   // read move
  dos_int86(0x33);
  mx = (short)regs.x.cx;
  my = (short)regs.x.dx;
  
  if (m_filter.value)
  {
    mouse_x = (mx + old_mouse_x) * 0.5;
    mouse_y = (my + old_mouse_y) * 0.5;
  }
  else
  {
    mouse_x = mx;
    mouse_y = my;
  }
  old_mouse_x = mx;
  old_mouse_y = my;

  mouse_x *= sensitivity.value;
  mouse_y *= sensitivity.value;

// add mouse X/Y movement to cmd
  if ( (in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1) ))
    cmd->sidemove += m_side.value * mouse_x;
  else
    cl.viewangles[YAW] -= m_yaw.value * mouse_x;
  
  if (in_mlook.state & 1)
    V_StopPitchDrift ();
    
  if ( (in_mlook.state & 1) && !(in_strafe.state & 1))
  {
    cl.viewangles[PITCH] += m_pitch.value * mouse_y;
    if (cl.viewangles[PITCH] > 80)
      cl.viewangles[PITCH] = 80;
    if (cl.viewangles[PITCH] < -70)
      cl.viewangles[PITCH] = -70;
  }
  else
  {
    if ((in_strafe.state & 1) && noclip_anglehack)
      cmd->upmove -= m_forward.value * mouse_y;
    else
      cmd->forwardmove -= m_forward.value * mouse_y;
  }
}

/*
===========
IN_JoyMove
===========
*/
void IN_JoyMove (usercmd_t *cmd)
{
  float speed, aspeed;

  if (!joy_avail || !in_joystick.value) 
    return; 
 
  IN_ReadJoystick (); 
  if (joysticky > joyyh*2 || joystickx > joyxh*2)
    return;   // assume something jumped in and messed up the joystick
          // reading time (win 95)

  if (in_speed.state & 1)
    speed = cl_movespeedkey.value;
  else
    speed = 1;
  aspeed = speed*host_frametime;

  if (in_strafe.state & 1)
  {
    if (joystickx < joyxl)
      cmd->sidemove -= speed*cl_sidespeed.value; 
    else if (joystickx > joyxh) 
      cmd->sidemove += speed*cl_sidespeed.value; 
  }
  else
  {
    if (joystickx < joyxl)
      cl.viewangles[YAW] += aspeed*cl_yawspeed.value;
    else if (joystickx > joyxh) 
      cl.viewangles[YAW] -= aspeed*cl_yawspeed.value;
    cl.viewangles[YAW] = anglemod(cl.viewangles[YAW]);
  }

  if (in_mlook.state & 1)
  {
    if (m_pitch.value < 0)
      speed *= -1;
    
    if (joysticky < joyyl) 
      cl.viewangles[PITCH] += aspeed*cl_pitchspeed.value;
    else if (joysticky > joyyh) 
      cl.viewangles[PITCH] -= aspeed*cl_pitchspeed.value;
  }
  else
  {
    if (joysticky < joyyl) 
      cmd->forwardmove += speed*cl_forwardspeed.value; 
    else if (joysticky > joyyh) 
      cmd->forwardmove -= speed*cl_backspeed.value;  
  }
}

/*
===========
IN_Move
===========
*/
void IN_Move (usercmd_t *cmd)
{
  IN_MouseMove (cmd);
  IN_JoyMove (cmd);
  IN_ExternalMove (cmd);
}

/* 
============================================================================ 
 
          JOYSTICK 
 
============================================================================ 
*/



qboolean IN_ReadJoystick (void)
{
  int   b;
  int   count;

  joystickx = 0;
  joysticky = 0;

  count = 0;

  b = dos_inportb(0x201);
  dos_outportb(0x201, b);

// clear counters
  while (++count < 10000)
  {
    b = dos_inportb(0x201);

    joystickx += b&1;
    joysticky += (b&2)>>1;
    if ( !(b&3) )
      return true;
  }
  
  Con_Printf ("IN_ReadJoystick: no response\n");
  joy_avail = false;
  return false;
}

/*
=============
WaitJoyButton
=============
*/
qboolean WaitJoyButton (void) 
{ 
  int             oldbuttons, buttons; 
 
  oldbuttons = 0; 
  do 
  {
    key_count = -1;
    Sys_SendKeyEvents ();
    key_count = 0;
    if (key_lastpress == K_ESCAPE)
    {
      Con_Printf ("aborted.\n");
      return false;
    }
    key_lastpress = 0;
    SCR_UpdateScreen ();
    buttons =  ((dos_inportb(0x201) >> 4)&1)^1; 
    if (buttons != oldbuttons) 
    { 
      oldbuttons = buttons; 
      continue; 
    }
  } while ( !buttons); 
 
  do 
  { 
    key_count = -1;
    Sys_SendKeyEvents ();
    key_count = 0;
    if (key_lastpress == K_ESCAPE)
    {
      Con_Printf ("aborted.\n");
      return false;
    }
    key_lastpress = 0;
    SCR_UpdateScreen ();
    buttons =  ((dos_inportb(0x201) >> 4)&1)^1; 
    if (buttons != oldbuttons) 
    { 
      oldbuttons = buttons; 
      continue; 
    } 
  } while ( buttons); 
 
  return true; 
} 
 
 
 
/* 
=============== 
IN_StartupJoystick 
=============== 
*/  
void IN_StartupJoystick (void) 
{ 
  int     centerx, centery; 
 
  Con_Printf ("\n");

  joy_avail = false; 
  if ( COM_CheckParm ("-nojoy") ) 
    return; 
 
  if (!IN_ReadJoystick ()) 
  { 
    joy_avail = false; 
    Con_Printf ("joystick not found\n"); 
    return; 
  } 

  Con_Printf ("joystick found\n"); 
 
  Con_Printf ("CENTER the joystick\nand press button 1 (ESC to skip):\n"); 
  if (!WaitJoyButton ()) 
    return; 
  IN_ReadJoystick (); 
  centerx = joystickx; 
  centery = joysticky; 
 
  Con_Printf ("Push the joystick to the UPPER LEFT\nand press button 1 (ESC to skip):\n"); 
  if (!WaitJoyButton ()) 
    return; 
  IN_ReadJoystick (); 
  joyxl = (centerx + joystickx)/2; 
  joyyl = (centerx + joysticky)/2; 
 
  Con_Printf ("Push the joystick to the LOWER RIGHT\nand press button 1 (ESC to skip):\n"); 
  if (!WaitJoyButton ()) 
    return; 
  IN_ReadJoystick (); 
  joyxh = (centerx + joystickx)/2; 
  joyyh = (centery + joysticky)/2; 

  joy_avail = true; 
  Con_Printf ("joystick configured.\n"); 

  Con_Printf ("\n");
} 
 
 
/* 
============================================================================ 
 
          EXTERNAL 
 
============================================================================ 
*/


/* 
=============== 
IN_StartupExternal 
=============== 
*/  
void IN_StartupExternal (void) 
{ 
  if (extern_control->numButtons > 32)
    extern_control->numButtons = 32;

  Con_Printf("%s Initialized\n", extern_control->deviceName);
  Con_Printf("  %u axes  %u buttons\n", extern_control->numAxes, extern_control->numButtons);

  extern_avail = true;
  extern_buttons = extern_control->numButtons;
}


/*
===========
IN_ExternalMove
===========
*/
void IN_ExternalMove (usercmd_t *cmd)
{
  qboolean freelook;

  if (! extern_avail)
    return;

  extern_control->viewangles[YAW] = cl.viewangles[YAW];
  extern_control->viewangles[PITCH] = cl.viewangles[PITCH];
  extern_control->viewangles[ROLL] = cl.viewangles[ROLL];
  extern_control->forwardmove = cmd->forwardmove;
  extern_control->sidemove = cmd->sidemove;
  extern_control->upmove = cmd->upmove;

Con_DPrintf("IN:  y:%f p:%f r:%f f:%f s:%f u:%f\n", extern_control->viewangles[YAW], extern_control->viewangles[PITCH], extern_control->viewangles[ROLL], extern_control->forwardmove, extern_control->sidemove, extern_control->upmove);

  dos_int86(extern_control->interruptVector);

Con_DPrintf("OUT: y:%f p:%f r:%f f:%f s:%f u:%f\n", extern_control->viewangles[YAW], extern_control->viewangles[PITCH], extern_control->viewangles[ROLL], extern_control->forwardmove, extern_control->sidemove, extern_control->upmove);

  cl.viewangles[YAW] = extern_control->viewangles[YAW];
  cl.viewangles[PITCH] = extern_control->viewangles[PITCH];
  cl.viewangles[ROLL] = extern_control->viewangles[ROLL];
  cmd->forwardmove = extern_control->forwardmove;
  cmd->sidemove = extern_control->sidemove;
  cmd->upmove = extern_control->upmove;

  if (cl.viewangles[PITCH] > 80)
    cl.viewangles[PITCH] = 80;
  if (cl.viewangles[PITCH] < -70)
    cl.viewangles[PITCH] = -70;

  freelook = (extern_control->flags & AUX_FLAG_FREELOOK || aux_look.value || in_mlook.state & 1);

  if (freelook)
    V_StopPitchDrift ();
}

