/**
 * @file
 *
 * @ingroup libmisc_mouse Mouse Parser Engine
 *
 * @brief Opens a Serial Port Directly, and Interprets Serial Data
 */

/*
 * This code is derived from a UNIX Serial Port Mouse Driver with
 * the following notice:
 *
 * ==================================================================
 * Copyright (c) 1999 Greg Haerr <greg@censoft.com>
 * Portions Copyright (c) 1991 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 *
 * UNIX Serial Port Mouse Driver
 *
 * This driver opens a serial port directly, and interprets serial data.
 * Microsoft, PC, Logitech and PS/2 mice are supported.  The PS/2 mouse
 * is only supported if the OS runs the mouse byte codes through the
 * serial port.
 *
 * Mouse Types Supported: pc  ms, logi, ps2
 * ==================================================================
 *
 * It has been modified to support the concept of being just a parser
 * fed data from an arbitrary source.  It is independent of either
 * a PS/2 driver or a serial port.
 *
 * It was moved to cpukit/libmisc/mouse by Joel Sherrill.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>  /* strcmp */
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <rtems.h>
#include <rtems/bspIo.h>
#include <rtems/mouse_parser.h>
#include <rtems/mw_uid.h>

/* states for the mouse */
#define IDLE   0  /* start of byte sequence */
#define XSET   1  /* setting x delta */
#define YSET   2  /* setting y delta */
#define XADD   3  /* adjusting x delta */
#define YADD   4  /* adjusting y delta */

/* values in the bytes returned by the mouse for the buttons*/
#define PC_LEFT_BUTTON   4
#define PC_MIDDLE_BUTTON 2
#define PC_RIGHT_BUTTON  1

#define MS_LEFT_BUTTON   2
#define MS_RIGHT_BUTTON  1

#define PS2_CTRL_BYTE    0x08
#define PS2_LEFT_BUTTON    1
#define PS2_RIGHT_BUTTON  2

/* Bit fields in the bytes sent by the mouse.*/
#define TOP_FIVE_BITS      0xf8
#define BOTTOM_THREE_BITS  0x07
#define TOP_BIT            0x80
#define SIXTH_BIT          0x40
#define BOTTOM_TWO_BITS    0x03
#define THIRD_FOURTH_BITS  0x0c
#define BOTTOM_SIX_BITS    0x3f

/* local data */
static int     state;            /* IDLE, XSET, ... */
static BUTTON  buttons;          /* current mouse buttons pressed*/
static BUTTON  availbuttons;     /* which buttons are available */
static COORD   xd;               /* change in x */
static COORD   yd;               /* change in y */

static int     left;             /* because the button values change */
static int     middle;           /* between mice, the buttons are */
static int     right;            /* redefined */

static int     (*parse)( int );  /* parse routine */

/* local routines*/
static int ParsePC(int);    /* routine to interpret PC mouse */
static int ParseMS(int);    /* routine to interpret MS mouse */
static int ParsePS2(int);    /* routine to interpret PS/2 mouse */

/*
 * Open up the mouse device.
 * Returns the fd if successful, or negative if unsuccessful.
 */
int mouse_parser_initialize(const char *type)
{
  /* set button bits and parse procedure*/
  if (!strcmp(type, "pc") || !strcmp(type, "logi")) {
    /* pc or logitech mouse*/
    left = PC_LEFT_BUTTON;
    middle = PC_MIDDLE_BUTTON;
    right = PC_RIGHT_BUTTON;
    parse = ParsePC;
  } else if (strcmp(type, "ms") == 0) {
    /* microsoft mouse*/
    left = MS_LEFT_BUTTON;
    right = MS_RIGHT_BUTTON;
    middle = 0;
    parse = ParseMS;
  } else if (strcmp(type, "ps2") == 0) {
    /* PS/2 mouse*/
    left = PS2_LEFT_BUTTON;
    right = PS2_RIGHT_BUTTON;
    middle = 0;
    parse = ParsePS2;
  } else
    return -1;

  printk("Device: /dev/mouse -- mouse type is: %s\n", type );

  /* initialize data*/
  availbuttons = left | middle | right;
  state = IDLE;
  buttons = 0;
  xd = 0;
  yd = 0;
  return 0;
}

/*
 * Attempt to read bytes from the mouse and interpret them.
 * Returns -1 on error, 0 if either no bytes were read or not enough
 * was read for a complete state, or 1 if the new state was read.
 * When a new state is read, the current buttons and x and y deltas
 * are returned.  This routine does not block.
 */
static int MOU_Data( int ch, COORD *dx, COORD *dy, COORD *dz, BUTTON *bptr)
{
  int b;

  if ( !parse ) {
    printk( "Mouse parser is not initialized!\n" );
    return -1;
  }

  /*
   * Loop over all the bytes read in the buffer, parsing them.
   * When a complete state has been read, return the results,
   * leaving further bytes in the buffer for later calls.
   */
  if ( (*parse)( ch ) ) {
    *dx = xd;
    *dy = yd;
    *dz = 0;
    b = 0;
    if (buttons & left)
      b |= LBUTTON;
    if (buttons & right)
      b |= RBUTTON;
    if (buttons & middle)
      b |= MBUTTON;
    *bptr = b;
    return 1;
  }
  return 0;
}

/*
 * Input routine for PC mouse.
 * Returns nonzero when a new mouse state has been completed.
 */
static int ParsePC(int byte)
{
  int  sign;      /* sign of movement */

  switch (state) {
    case IDLE:
      if ((byte & TOP_FIVE_BITS) == TOP_BIT) {
        buttons = ~byte & BOTTOM_THREE_BITS;
        state = XSET;
      }
      break;

    case XSET:
      sign = 1;
      if (byte > 127) {
        byte = 256 - byte;
        sign = -1;
      }
      xd = byte * sign;
      state = YSET;
      break;

    case YSET:
      sign = 1;
      if (byte > 127) {
        byte = 256 - byte;
        sign = -1;
      }
      yd = -byte * sign;
      state = XADD;
      break;

    case XADD:
      sign = 1;
      if (byte > 127) {
        byte = 256 - byte;
        sign = -1;
      }
      xd += byte * sign;
      state = YADD;
      break;

    case YADD:
      sign = 1;
      if (byte > 127) {
        byte = 256 - byte;
        sign = -1;
      }
      yd -= byte * sign;
      state = IDLE;
      return 1;
  }
  return 0;
}

/*
 * Input routine for Microsoft mouse.
 * Returns nonzero when a new mouse state has been completed.
 */
static int ParseMS(int byte)
{
  switch (state) {
    case IDLE:
      if (byte & SIXTH_BIT) {
        buttons = (byte >> 4) & BOTTOM_TWO_BITS;
        yd = ((byte & THIRD_FOURTH_BITS) << 4);
        xd = ((byte & BOTTOM_TWO_BITS) << 6);
        state = XADD;
      }
      break;

    case XADD:
      xd |= (byte & BOTTOM_SIX_BITS);
      state = YADD;
      break;

    case YADD:
      yd |= (byte & BOTTOM_SIX_BITS);
      state = IDLE;
      if (xd > 127)
        xd -= 256;
      if (yd > 127)
        yd -= 256;
      return 1;
  }
  return 0;
}

/*
 * Input routine for PS/2 mouse.
 * Returns nonzero when a new mouse state has been completed.
 */
static int ParsePS2(int byte)
{
  switch (state) {
    case IDLE:
      if (byte & PS2_CTRL_BYTE) {
        buttons = byte &
          (PS2_LEFT_BUTTON|PS2_RIGHT_BUTTON);
        state = XSET;
      }
      break;

    case XSET:
      if(byte > 127)
        byte -= 256;
      xd = byte;
      state = YSET;
      break;

    case YSET:
      if(byte > 127)
        byte -= 256;
      yd = -byte;
      state = IDLE;
      return 1;
  }
  return 0;
}

/* generic mouse parser */
void mouse_parser_enqueue( unsigned char *buffer, size_t size )
{
  COORD dx;
  COORD dy;
  COORD dz;
  BUTTON bptr;

  while( size-- ) {
    if ( MOU_Data( *buffer++, &dx, &dy, &dz, &bptr ) == 1 ) {
       struct MW_UID_MESSAGE m;

        m.type = MV_UID_REL_POS;
        /* buttons definitons have been selected to match */
        m.m.pos.btns = bptr;
        m.m.pos.x  = dx;
        m.m.pos.y  = dy;
        m.m.pos.z  = dz;
        /* printk( "Mouse: msg: dx=%d, dy=%d, btn=%X\n", dx, dy, bptr ); */
        uid_send_message( &m );
    }
  }
}

