/*
     PLIB - A Suite of Portable Game Libraries
     Copyright (C) 1998,2002  Steve Baker

     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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

     For further information visit http://plib.sourceforge.net

     $Id: jsWindows.cxx 2164 2011-01-22 22:47:30Z fayjf $
*/

#include <string>

#include "js.h"

#include <Windows.h>

#include <cstring>
#include <RegStr.h> // for REGSTR_PATH_JOYCONFIG, etc

#include <simgear/debug/logstream.hxx>
#include <simgear/structure/exception.hxx>

#define _JS_MAX_AXES_WIN 8  /* X,Y,Z,R,U,V,POV_X,POV_Y */

struct os_specific_s {
  JOYCAPS      jsCaps ;
  JOYINFOEX    js     ;
  UINT         js_id  ;
  static bool getOEMProductName ( jsJoystick* joy, char *buf, int buf_sz ) ;
};

// Give a human-readable interpretation of joyGetDevCaps()'s return value
static std::string joyGetDevCaps_errorString(MMRESULT errorCode)
{
  switch (errorCode) {
  case MMSYSERR_NODRIVER:
    return "joystick driver not present, or specified joystick identifier is "
           "invalid";
  case MMSYSERR_INVALPARAM:
    return "invalid parameter passed to joyGetDevCaps()";
  // joyGetDevCaps() appears to return undocumented values, see
  // https://sourceforge.net/p/flightgear/mailman/message/36657149/
  case MMSYSERR_BADDEVICEID:    // fallthrough
  case JOYERR_PARMS:
    return "invalid joystick identifier";
  case JOYERR_UNPLUGGED:
    return "joystick not connected to the system";
  case JOYERR_NOERROR:
    return "no error";
  default:
    // Don't throw an exception, since joyGetDevCaps()'s documentation isn't
    // correct (we can't be sure to have covered all possible return values).
    return "unexpected value passed to joyGetDevCaps_errorString(): "
           + std::to_string(errorCode);
  }

  throw sg_exception("This code path should be unreachable; value "
                     "passed to joyGetDevCaps_errorString(): "
                     + std::to_string(errorCode));
}

// Inspired by
// http://msdn.microsoft.com/archive/en-us/dnargame/html/msdn_sidewind3d.asp

bool os_specific_s::getOEMProductName ( jsJoystick* joy, char *buf, int buf_sz )
{
  if ( joy->error )  return false ;

  union
  {
    char key   [ 256 ] ;
    char value [ 256 ] ;
  } ;
  char OEMKey [ 256 ] ;

  HKEY  hKey ;
  DWORD dwcb ;
  LONG  lr ;
  int hkcu = 0;

  // Open .. MediaResources\CurrentJoystickSettings
  sprintf ( key, "%s\\%s\\%s",
            REGSTR_PATH_JOYCONFIG, joy->os->jsCaps.szRegKey,
            REGSTR_KEY_JOYCURR ) ;

  lr = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE, &hKey) ;

  if ( lr != ERROR_SUCCESS )
  {
	hkcu = 1;
    // XP/Vista/7 seem to have moved it to "current user"
    lr = RegOpenKeyEx ( HKEY_CURRENT_USER, key, 0, KEY_QUERY_VALUE, &hKey) ;
    if ( lr != ERROR_SUCCESS ) return false ;
  }

  // Get OEM Key name
  dwcb = sizeof(OEMKey) ;

  // JOYSTICKID1-16 is zero-based; registry entries for VJOYD are 1-based.
  sprintf ( value, "Joystick%d%s", joy->os->js_id + 1, REGSTR_VAL_JOYOEMNAME ) ;

  lr = RegQueryValueEx ( hKey, value, 0, 0, (LPBYTE) OEMKey, &dwcb);
  RegCloseKey ( hKey ) ;

  if ( lr != ERROR_SUCCESS ) return false ;

  // Open OEM Key from ...MediaProperties
  sprintf ( key, "%s\\%s", REGSTR_PATH_JOYOEM, OEMKey ) ;

  if (!hkcu)
    lr = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, key, 0, KEY_QUERY_VALUE, &hKey) ;
  else
    lr = RegOpenKeyEx ( HKEY_CURRENT_USER, key, 0, KEY_QUERY_VALUE, &hKey) ;
  if ( lr != ERROR_SUCCESS )
  {
	  return false ;
  }

  // Get OEM Name
  dwcb = buf_sz ;

  lr = RegQueryValueEx ( hKey, REGSTR_VAL_JOYOEMNAME, 0, 0, (LPBYTE) buf,
                           &dwcb ) ;
  RegCloseKey ( hKey ) ;

  if ( lr != ERROR_SUCCESS ) return false ;

  return true ;
}


void jsJoystick::open ()
{
  name [0] = '\0' ;

  os->js . dwFlags = JOY_RETURNALL ;
  os->js . dwSize  = sizeof ( os->js ) ;

  memset ( &(os->jsCaps), 0, sizeof(os->jsCaps) ) ;

  const auto joyGetDevCaps_res = joyGetDevCaps(os->js_id, &(os->jsCaps),
                                               sizeof(os->jsCaps));
  error = (joyGetDevCaps_res != JOYERR_NOERROR);

  if (error) {
    SG_LOG(SG_INPUT, SG_DEBUG,
           "joyGetDevCaps reported: "
           << joyGetDevCaps_errorString(joyGetDevCaps_res));
  }

  num_buttons = os->jsCaps.wNumButtons ;
  if ( os->jsCaps.wNumAxes == 0 )
  {
    SG_LOG(SG_INPUT, SG_DEBUG,
           "Joystick reported zero axes currently in use (JOYCAPS.wNumAxes)");
    num_axes = 0 ;
    setError () ;
  }
  else
  {
    // Device name from jsCaps is often "Microsoft PC-joystick driver",
    // at least for USB.  Try to get the real name from the registry.
    if ( ! os->getOEMProductName ( this, name, sizeof(name) ) )
    {
      jsSetError ( SG_WARN,
                   "JS: Failed to read joystick name from registry" ) ;

      strncpy ( name, os->jsCaps.szPname, sizeof(name) ) ;
    }

    // Windows joystick drivers may provide any combination of
    // X,Y,Z,R,U,V,POV - not necessarily the first n of these.
    if ( os->jsCaps.wCaps & JOYCAPS_HASPOV )
    {
      num_axes = _JS_MAX_AXES_WIN ;
      min [ 7 ] = -1.0 ; max [ 7 ] = 1.0 ;  // POV Y
      min [ 6 ] = -1.0 ; max [ 6 ] = 1.0 ;  // POV X
    }
    else
      num_axes = 6 ;

    min [ 5 ] = (float) os->jsCaps.wVmin ; max [ 5 ] = (float) os->jsCaps.wVmax ;
    min [ 4 ] = (float) os->jsCaps.wUmin ; max [ 4 ] = (float) os->jsCaps.wUmax ;
    min [ 3 ] = (float) os->jsCaps.wRmin ; max [ 3 ] = (float) os->jsCaps.wRmax ;
    min [ 2 ] = (float) os->jsCaps.wZmin ; max [ 2 ] = (float) os->jsCaps.wZmax ;
    min [ 1 ] = (float) os->jsCaps.wYmin ; max [ 1 ] = (float) os->jsCaps.wYmax ;
    min [ 0 ] = (float) os->jsCaps.wXmin ; max [ 0 ] = (float) os->jsCaps.wXmax ;
  }

  for ( int i = 0 ; i < num_axes ; i++ )
  {
    center    [ i ] = ( max[i] + min[i] ) / 2.0f ;
    dead_band [ i ] = 0.0f ;
    saturate  [ i ] = 1.0f ;
  }
}


void jsJoystick::close ()
{
	delete os;
}



jsJoystick::jsJoystick ( int ident )
{
  id = ident ;
  os = new struct os_specific_s;

  if (ident >= 0 && static_cast<unsigned int>(ident) < joyGetNumDevs()) {
        os->js_id = JOYSTICKID1 + ident;
        open();
  }
  else {
        SG_LOG(SG_INPUT, SG_DEBUG,
               "Joystick identifier not in the range of valid ids");
        num_axes = 0;
        setError();
  }
}



void jsJoystick::rawRead ( int *buttons, float *axes )
{
  if ( error )
  {
    if ( buttons )
      *buttons = 0 ;

    if ( axes )
      for ( int i = 0 ; i < num_axes ; i++ )
        axes[i] = 1500.0f ;

    return ;
  }

  MMRESULT status = joyGetPosEx ( os->js_id, &(os->js) ) ;

  if ( status != JOYERR_NOERROR )
  {
    setError() ;
    return ;
  }

  if ( buttons != NULL )
    *buttons = (int) os->js.dwButtons ;

  if ( axes != NULL )
  {
    /* WARNING - Fall through case clauses!! */

    switch ( num_axes )
    {
      case 8:
        // Generate two POV axes from the POV hat angle.
        // Low 16 bits of js.dwPOV gives heading (clockwise from ahead) in
        //   hundredths of a degree, or 0xFFFF when idle.

        if ( ( os->js.dwPOV & 0xFFFF ) == 0xFFFF )
        {
          axes [ 6 ] = 0.0 ;
          axes [ 7 ] = 0.0 ;
        }
        else
        {
          // This is the contentious bit: how to convert angle to X/Y.
          //    wk: I know of no define for PI that we could use here:
          //    SG_PI would pull in sg, M_PI is undefined for MSVC
          // But the accuracy of the value of PI is very unimportant at
          // this point.

          float s = (float) sin ( ( os->js.dwPOV & 0xFFFF ) * ( 0.01 * 3.1415926535f / 180 ) ) ;
          float c = (float) cos ( ( os->js.dwPOV & 0xFFFF ) * ( 0.01 * 3.1415926535f / 180 ) ) ;

          // Convert to coordinates on a square so that North-East
          // is (1,1) not (.7,.7), etc.
          // s and c cannot both be zero so we won't divide by zero.
          if ( fabs ( s ) < fabs ( c ) )
          {
            axes [ 6 ] = ( c < 0.0 ) ? -s/c  : s/c  ;
            axes [ 7 ] = ( c < 0.0 ) ? -1.0f : 1.0f ;
          }
          else
          {
            axes [ 6 ] = ( s < 0.0 ) ? -1.0f : 1.0f ;
            axes [ 7 ] = ( s < 0.0 ) ? -c/s  : c/s  ;
          }
        }

      case 6: axes[5] = (float) os->js . dwVpos ;
      case 5: axes[4] = (float) os->js . dwUpos ;
      case 4: axes[3] = (float) os->js . dwRpos ;
      case 3: axes[2] = (float) os->js . dwZpos ;
      case 2: axes[1] = (float) os->js . dwYpos ;
      case 1: axes[0] = (float) os->js . dwXpos ;
              break;

      default:
        jsSetError ( SG_WARN, "PLIB_JS: Wrong num_axes. Joystick input is now invalid" ) ;
    }
  }
}

void jsInit() {}
