/*
 * general purpose mouse (gpm)
 *
 * Copyright (c) 2008        Nico Schottelius <nico-gpm2008 at schottelius.org>
 *
 *   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.
 *
 * call getMouseData to get hardware device data, call mouse device's fun()
 * to retrieve the hardware independent event data, then optionally repeat
 * the data via repeat_fun() to the repeater device
 *
 ********/

#include <sys/stat.h>               /* stat              */
#include <linux/kd.h>               /* KD                */
#include <linux/vt.h>               /* vt                */
#include <fcntl.h>                  /* open              */
#include <unistd.h>                 /* close             */
#include <time.h>                   /* time              */
#include <sys/time.h>               /* gettimeofday      */

#include "message.h"        /* messaging in gpm */
#include "daemon.h"         /* daemon internals */

#define abs(value)              ((value) < 0 ? -(value) : (value))
#define GET_TIME(tv) (gettimeofday(&tv, (struct timezone *)NULL))
#define DIF_TIME(t1,t2) ((t2.tv_sec -t1.tv_sec) *1000 + \
                         (t2.tv_usec-t1.tv_usec)/1000)


int processMouse(int fd, Gpm_Event *event, int kd_mode)
{
   unsigned char *data;
   static int  fine_dx,
               fine_dy,
               i, j, m,
               newB=0,  /* old buttons and Type to chain events */
               oldB=0,
               oldT=0;

   static Gpm_Event        nEvent;
   static struct vt_stat   stat;
   static struct timeval   tv1={0,0}, tv2; /* tv1==0: first click is single */
   static struct timeval   timeout={0,0};
   fd_set                  fdSet;
   int                     tempx, tempy;
   static int              oldx, oldy;

   oldT = event->type;

   if(eventFlag) {
      eventFlag=0;

      if((which_mouse->m_type)->absolute) {     /* a pen or other absolute device */
         event->x=nEvent.x;
         event->y=nEvent.y;
      }
      event->dx=nEvent.dx;
      event->dy=nEvent.dy;
      event->buttons=nEvent.buttons;
   } else {
      event->dx=event->dy   = 0;
      event->wdx=event->wdy = 0;
      nEvent.modifiers      = 0; /* some mice set them */
      i                     = 0;

      FD_ZERO(&fdSet);
      FD_SET(fd,&fdSet);

      /* use uncalibrated values as base values */
      if((which_mouse->opt_calib!=NULL)&&(which_mouse->m_type->absolute)){
        nEvent.x = oldx;
        nEvent.y = oldy;
      }

      do { /* cluster loop */
         if(((data=getMouseData(fd, (which_mouse->m_type), kd_mode)) == NULL)
            || ((*((which_mouse->m_type)->fun))(&nEvent,data)==-1) ) {

            if (!i) {
               return 0;
            } else {
               break;
            }
         }

         event->modifiers = nEvent.modifiers; /* propagate modifiers */

         /* propagate buttons */
         nEvent.buttons = ((which_mouse->opt_sequence)[nEvent.buttons&7]&7) |
            (nEvent.buttons & ~7); /* change the order */
         oldB = newB;
         newB = nEvent.buttons;

         if (!i) event->buttons = nEvent.buttons;

         if (oldB != newB) {
            eventFlag = (i!=0)*(which_mouse-mouse_table); /* 1 or 2 */
            break;
         }

         /* propagate movement */
         if (!((which_mouse->m_type)->absolute)) { /* mouse */
            if (abs(nEvent.dx)+abs(nEvent.dy) > (which_mouse->opt_delta))
               nEvent.dx*=(which_mouse->opt_accel), nEvent.dy*=(which_mouse->opt_accel);

            /* increment the reported dx,dy */
            event->dx+=nEvent.dx;
            event->dy+=nEvent.dy;
         } else { /* a pen */
            /* get dx,dy to check if there has been movement */
            event->dx = (nEvent.x) - (event->x);
            event->dy = (nEvent.y) - (event->y);
         }

         /* propagate wheel */
         event->wdx += nEvent.wdx;
         event->wdy += nEvent.wdy;

         select(fd+1,&fdSet,(fd_set *)NULL,(fd_set *)NULL,&timeout/* zero */);

      } while (i++ <(which_mouse->opt_cluster) && nEvent.buttons==oldB && FD_ISSET(fd,&fdSet));

      /* apply calibration */
      if((which_mouse->opt_calib != NULL) &&
         (which_mouse->m_type->absolute)) {

        /* save uncalibrated values for use next time around */
        oldx = nEvent.x;
        oldy = nEvent.y;

        /* do calculations in a larger variable */
        tempx = nEvent.x;
        tempy = nEvent.y;
        tempx -= which_mouse->opt_dminx;
        tempx *= ( which_mouse->opt_omaxx - which_mouse->opt_ominx );
        tempx /= ( which_mouse->opt_dmaxx - which_mouse->opt_dminx );
        tempx += which_mouse->opt_ominx;

        tempy -= which_mouse->opt_dminy;
        tempy *= ( which_mouse->opt_omaxy - which_mouse->opt_ominy );
        tempy /= ( which_mouse->opt_dmaxy - which_mouse->opt_dminy );
        tempy += which_mouse->opt_ominy;

        nEvent.x = tempx;
        nEvent.y = tempy;

        event->dx = (nEvent.x) - (event->x);
        event->dy = (nEvent.y) - (event->y);
      }
   } /* if(eventFlag) */

/*....................................... update the button number */

   if ((event->buttons & GPM_B_MIDDLE) && !(which_mouse->opt_three))
      (which_mouse->opt_three)++;

/*....................................... we're a repeater, aren't we? */

   if (kd_mode!=KD_TEXT) {
      if (fifofd != -1 && ! opt_rawrep) {
         if ((which_mouse->m_type)->absolute) { /* hof Wed Feb  3 21:43:28 MET 1999 */
            /* prepare the values from a absolute device for repeater mode */
            static struct timeval rept1,rept2;
            gettimeofday(&rept2, (struct timezone *)NULL);
            if (((rept2.tv_sec -rept1.tv_sec)
                  *1000+(rept2.tv_usec-rept1.tv_usec)/1000)>250) {
               event->dx=0;
               event->dy=0;
            }
            rept1=rept2;

            /* if the values are calibrated, this is not necessary */
            if(which_mouse->opt_calib==NULL)
              event->dy=event->dy*((win.ws_col/win.ws_row)+1);

            event->x=nEvent.x;
            event->y=nEvent.y;
         }

         /* not all relative repeaters can handle big changes,
            so repackage into several smaller updates */
         if (!repeated_type->absolute) {
           int remx, remy;
           remx = event->dx;
           remy = event->dy;

           do {
             if (remx<0) {
               if (remx>=-127) {
                 event->dx = remx;
                 remx = 0;
               } else {
                 event->dx = -127;
                 remx += 127;
               }
             }
             if (remx>0) {
               if (remx<=127) {
                 event->dx = remx;
                 remx = 0;
               } else {
                 event->dx = 127;
                 remx -= 127;
               }
             }
             if (remy<0) {
               if (remy>=-127) {
                 event->dy = remy;
                 remy = 0;
               } else {
                 event->dy = -127;
                 remy += 127;
               }
             }
             if (remy>0) {
               if (remy<=127) {
                 event->dy = remy;
                 remy = 0;
               } else {
                 event->dy = 127;
                 remy -= 127;
               }
             }
             repeated_type->repeat_fun(event, fifofd);
           } while((remx!= 0)||(remy!=0));
         }
         else
           repeated_type->repeat_fun(event, fifofd); /* itz Jan 11 1999 */
      }
      return 0; /* no events nor information for clients */
   } /* first if of these three */

/*....................................... no, we arent a repeater, go on */

   /* use fine delta values now, if delta is the information */
   if (!((which_mouse->m_type))->absolute) {
      fine_dx+=event->dx;             fine_dy+=event->dy;
      event->dx=fine_dx/(which_mouse->opt_scale);    event->dy=fine_dy/(which_mouse->opt_scaley);
      fine_dx %= (which_mouse->opt_scale);           fine_dy %= (which_mouse->opt_scaley);
   }

   /* up and down, up and down, ... who does a do..while(0) loop ???
      and then makes a break into it... argh ! */

   /* rodney 13/mar/2008 wheel movement similar to mouse movement
    * must also be excluded from time (click) processing */
   if (!event->dx && !event->dy
       && !event->wdx  && !event->wdy
       && (event->buttons==oldB) )
      do { /* so to break */
         static long awaketime;
         /*
          * Ret information also if never happens, but enough time has elapsed.
          * Note: return 1 will segfault due to missing event->vc; FIXME!
          */
         if (time(NULL)<=awaketime) return 0;
         awaketime=time(NULL)+1;
         break;
      } while (0);

/*....................................... fill missing fields */

   event->x+=event->dx, event->y+=event->dy;
   statusB=event->buttons;

   i=open_console(O_RDONLY);
   /* modifiers */
   j = event->modifiers; /* save them */
   event->modifiers=6; /* code for the ioctl */
   if (ioctl(i,TIOCLINUX,&(event->modifiers))<0)
      gpm_report(GPM_PR_OOPS,GPM_MESS_GET_SHIFT_STATE);
   event->modifiers |= j; /* add mouse-specific bits */

   /* status */
   j = stat.v_active;
   if (ioctl(i,VT_GETSTATE,&stat)<0) gpm_report(GPM_PR_OOPS,GPM_MESS_GET_CONSOLE_STAT);

   /*
    * if we changed console, request the current console size,
    * as different consoles can be of different size
    */
   if (stat.v_active != j)
      get_console_size(event);
   close(i);

   event->vc = stat.v_active;

   if (oldB==event->buttons)
      event->type = (event->buttons ? GPM_DRAG : GPM_MOVE);
   else
      event->type = (event->buttons > oldB ? GPM_DOWN : GPM_UP);

   switch(event->type) {                /* now provide the cooked bits */
      case GPM_DOWN:
         GET_TIME(tv2);

         /* check first click */
         if (tv1.tv_sec && (DIF_TIME(tv1,tv2)<(which_mouse->opt_time))) {
            statusC++;
            statusC%=3; /* 0, 1 or 2 */
         }
         else
            statusC=0;
         event->type|=(GPM_SINGLE<<statusC);
         break;

      case GPM_UP:
         GET_TIME(tv1);
         event->buttons^=oldB; /* for button-up, tell which one */
         event->type|= (oldT&GPM_MFLAG);
         event->type|=(GPM_SINGLE<<statusC);
         break;

      case GPM_DRAG:
         event->type |= GPM_MFLAG;
         event->type|=(GPM_SINGLE<<statusC);
         break;

      case GPM_MOVE:
         statusC=0;
      default:
         break;
   }
   event->clicks=statusC;

/* UGLY - FIXME! */
/* The current policy is to force the following behaviour:
 * - At buttons up, must fit inside the screen, though flags are set.
 * - At button down, allow going outside by one single step
 */


   /* selection used 1-based coordinates, so do I */

   /*
    * 1.05: only one margin is current. Y takes priority over X.
    * The i variable is how much margin is allowed. "m" is which one is there.
    */

   m = 0;
   i = ((event->type&(GPM_DRAG|GPM_UP))!=0); /* i is boolean */

   if (event->y>win.ws_row)  {event->y=win.ws_row+1-!i; i=0; m = GPM_BOT;}
   else if (event->y<=0)     {event->y=1-i;             i=0; m = GPM_TOP;}

   if (event->x>win.ws_col)  {event->x=win.ws_col+1-!i; if (!m) m = GPM_RGT;}
   else if (event->x<=0)     {event->x=1-i;             if (!m) m = GPM_LFT;}

   event->margin=m;

   gpm_report(GPM_PR_DEBUG,"dx: %3i dy: %3i x: %3i y: %3i butt: %i vc: %i clicks: %i",
                                       event->dx,event->dy,
                                       event->x,event->y,
                                       event->buttons, event->vc,
                                       event->clicks);

   /* update the global state */
   statusX=event->x;
   statusY=event->y;

   if (opt_special && event->type & GPM_DOWN)
      return processSpecial(event);

   return 1;
}
