/* $Id: relmouse.c,v 1.12 2005/09/15 15:03:17 cegger Exp $
******************************************************************************

   This LibGIC module is a very simple parser for PtrRelative Events.

   Copyright (C) 1999 Andreas Beck	[becka@ggi-project.org]

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

******************************************************************************
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <ggi/internal/gic.h>
#include <ggi/internal/gic_debug.h>

#include <ggi/internal/gg_replace.h>	/* for snprintf() */

struct relmouse
{ enum mode { MX, MY, MZ, MWHEEL, NUM_MODES} mode;
  int max;
};

static char modemap[NUM_MODES]="XYZW";


static int relmouse_check(gic_handle_t hand, gic_recognizer *ctrl,gii_event *event,
			gic_feature *feature,int recnum);

static int relmouse_get_name(gic_handle_t hand, gic_recognizer *ctrl,
			char *string, size_t maxlen);

static int relmouse_write_pvtdata(gic_handle_t hand, gic_recognizer *ctrl,
				char *string,int maxlen);

static int relmouse_read_pvtdata(gic_handle_t hand, gic_recognizer *ctrl,
				const char *string);

static void relmouse_free_pvtdata(gic_handle_t hand, gic_recognizer *ctrl);

static int relmouse_train(gic_handle_t hand, gic_recognizer **ctrl,gii_event *event);

static int relmouse_check_conflict(gic_handle_t hand, gic_recognizer *ctrl,
				gic_recognizer *ctrl2);

static int relmouse_get_opposite(gic_handle_t hand, gic_recognizer *recognizer,
				gic_recognizer **opposite);


static gic_recognizerdriver mycontrols= {
	"RelMouse",
	relmouse_check,
	relmouse_get_name,
	relmouse_write_pvtdata,
	relmouse_read_pvtdata,
	relmouse_free_pvtdata,
	relmouse_train,
	relmouse_check_conflict,
	relmouse_get_opposite
};


#define sign(x) ( x > 0 ? 1 : -1 )
#define abs(x)  ( x > 0 ? x : -x )

static int getrelmouse(gic_handle_t hand, struct relmouse *kp,gii_event *event,gic_feature *feature,int recnum)
{
	int myval;

	DPRINT_LIBS("Relmouse: relmouseEvent x=%08x,y=%08x,z=%08x,wheel=%08x [want %c,%08x].\n",
		event->pmove.x,event->pmove.y,event->pmove.z,event->pmove.wheel,
		modemap[kp->mode],kp->max);

	switch(kp->mode) {
		case MX:
			myval=event->pmove.x;break;
		case MY:
			myval=event->pmove.y;break;
		case MZ:
			myval=event->pmove.z;break;
		case MWHEEL:
			myval=event->pmove.wheel;break;
		default:
			return 0;
	}
	if (sign(myval)==sign(kp->max)) {
		if (abs(myval)>abs(kp->max)) myval=kp->max;
		gicFeatureActivate(hand, feature,
			(gic_state) ((double)GIC_STATE_MAX*myval/(double)kp->max)
			,GIC_FLAG_PULSE,recnum);
		return 1;
	}
	gicFeatureActivate(hand, feature,GIC_STATE_MIN,GIC_FLAG_PULSE,recnum);
	return 1;
}

static int relmouse_check(gic_handle_t hand, gic_recognizer *ctrl,gii_event *event,
			gic_feature *feature,int recnum)
{
	DPRINT_LIBS("Keys: Check with %p,%p.\n",ctrl,event);

	if (event->any.type==evPtrRelative) {
		return getrelmouse(hand, ctrl->privdata,event, feature, recnum);
	}

	return 0;
}

static struct trainingstate {
	int maxx,maxy,maxz,maxw;
	int minx,miny,minz,minw;
} trainingstate;

static int relmouse_register(gic_handle_t hand, gic_recognizer **ctrl)
{
	gic_recognizer *rl;
	struct relmouse *mkp;
	struct relmouse kp;
	
	kp.mode=MX;kp.max=trainingstate.maxx;
	if (trainingstate.maxy>kp.max) {
		kp.mode=MY;kp.max=trainingstate.maxy;
	}
	if (trainingstate.maxz>kp.max) {
		kp.mode=MZ;kp.max=trainingstate.maxz;
	}
	if (trainingstate.maxw>kp.max) {
		kp.mode=MWHEEL;kp.max=trainingstate.maxw;
	}

	if (-trainingstate.minx>abs(kp.max)) {
		kp.mode=MX;kp.max=trainingstate.minx;
	}
	if (-trainingstate.miny>abs(kp.max)) {
		kp.mode=MY;kp.max=trainingstate.miny;
	}
	if (-trainingstate.minz>abs(kp.max)) {
		kp.mode=MZ;kp.max=trainingstate.minz;
	}
	if (-trainingstate.minw>abs(kp.max)) {
		kp.mode=MWHEEL;kp.max=trainingstate.minw;
	}

	for(rl=*ctrl;rl!=NULL;rl=rl->next) {
		if (rl->driver==&mycontrols) {	/* We always attach one control max */
			/* Update, we _must_ be better. */
			memcpy(rl->privdata,&kp,sizeof(kp));
			return 1;
		}
	}

	rl = malloc(sizeof(gic_recognizer));
	if (rl == NULL) return GGI_ENOMEM;
	mkp = malloc(sizeof(struct relmouse));
	if (mkp == NULL) {
		free(rl);
		return GGI_ENOMEM;
	}	
	
	memcpy(mkp,&kp,sizeof(struct relmouse));
	rl->driver=&mycontrols;
	rl->confidence=GIC_STATE_MIDDLE;	/* FIXME ! */
	rl->privdata=mkp;
	gicRecognizerTrainAdd(hand, ctrl, rl);
	return 1;
}

static int relmouse_train(gic_handle_t hand, gic_recognizer **ctrl,gii_event *event)
{
	int rc;
	DPRINT_LIBS("Keys: Training with %p,%p.\n",ctrl,event);

	rc=0;
	if (event) {
		DPRINT_LIBS("Keys: Analyzing event ...\n");
		if (event->any.type==evPtrRelative) {	/* O.K. match. Remember the stats */
			if (trainingstate.maxx<event->pmove.x)
				trainingstate.maxx =event->pmove.x;
			if (trainingstate.maxy<event->pmove.y)
				trainingstate.maxy =event->pmove.y;
			if (trainingstate.maxz<event->pmove.z)
				trainingstate.maxz =event->pmove.z;
			if (trainingstate.maxw<event->pmove.wheel)
				trainingstate.maxw =event->pmove.wheel;
			if (trainingstate.minx>event->pmove.x)
				trainingstate.minx =event->pmove.x;
			if (trainingstate.miny>event->pmove.y)
				trainingstate.miny =event->pmove.y;
			if (trainingstate.minz>event->pmove.z)
				trainingstate.minz =event->pmove.z;
			if (trainingstate.minw>event->pmove.wheel)
				trainingstate.minw =event->pmove.wheel;
			DPRINT_LIBS("Keys: Remembering last pressed relmouse ...\n");
			rc+=relmouse_register(hand, ctrl);
			return rc;
		}
	} else {
		trainingstate.maxx=
		trainingstate.maxy=
		trainingstate.maxz=
		trainingstate.maxw=
		trainingstate.minx=
		trainingstate.miny=
		trainingstate.minz=
		trainingstate.minw=0;
		DPRINT_LIBS("Keys: Initialized training state.\n");
	}
	return rc;
}

/*
 * Privatedata format is :
 * mode (0-2)  max (hex, 4 digits)
 * modes are : KeyLabel (0), Keycode (1), KeySymbol (2)
 * max is the appropriate max of the corresponding field.
 */
 
static int relmouse_write_pvtdata(gic_handle_t hand, gic_recognizer *ctrl,
				char *string,int maxlen)
{
	struct relmouse *relmousep=ctrl->privdata;

	if (maxlen < 7) {
		*string='\0';
		return GGI_ENOSPACE;
	}
	sprintf(string,"%c %08x",modemap[relmousep->mode],relmousep->max);

	return 0;
}

static int relmouse_read_pvtdata(gic_handle_t hand, gic_recognizer *ctrl,
				const char *string)
{
	struct relmouse *relmousep;
	char hlp;
	int x;

	relmousep=ctrl->privdata=malloc(sizeof(struct relmouse));
	sscanf(string, "%c %x", &hlp, (uint32_t *)&relmousep->max);
	for(x=0;x<NUM_MODES;x++)
	{
		if (hlp==modemap[x]) {
			relmousep->mode=x;
			break;
		}
	}

	return 0;
}

static void relmouse_free_pvtdata(gic_handle_t hand, gic_recognizer *ctrl)
{
	struct relmouse *relmousep=ctrl->privdata;

	free(relmousep);ctrl->privdata=NULL;
	
	return;
}

static int relmouse_get_name(gic_handle_t hand, gic_recognizer *ctrl,
			char *string, size_t maxlen)
{
	struct relmouse *relmousep=ctrl->privdata;
	char hlpstr[30];

	snprintf(hlpstr, 30, "Mouse.%c", (sign(relmousep->max)<0) ? '-' : '+');

	switch(relmousep->mode) {
	case MX:
		snprintf(hlpstr+7, 30, "X");
		break;
	case MY:
		snprintf(hlpstr+7, 30, "Y");
		break;
	case MZ:
		snprintf(hlpstr+7, 30, "Z");
		break;
	case MWHEEL:
		snprintf(hlpstr+7, 30, "W");
		break;
	default:
		break;
	}
	strncpy(string,hlpstr,maxlen);
	string[maxlen-1] = '\0';
	return 0;
}

static int relmouse_check_conflict(gic_handle_t hand, gic_recognizer *ctrl,
				gic_recognizer *ctrl2)
{
	struct relmouse *relmousep =ctrl ->privdata;
	struct relmouse *relmousep2=ctrl2->privdata;

	if (ctrl==ctrl2) return GIC_C_ISSAME;
	if (ctrl->driver!=ctrl2->driver) return GIC_C_NOCONFLICT;

	if ( relmousep->mode==relmousep2->mode)
	{
		if (sign(relmousep->max)==sign(relmousep2->max))
			return GIC_C_ISSAME;
		else
			return GIC_C_NOTATSAMETIME;
	}
	return GIC_C_NOCONFLICT;
}

static int relmouse_get_opposite(gic_handle_t hand, gic_recognizer *recognizer,
				gic_recognizer **opposite)
{
	return GGI_ENOMATCH;	/* FIXME */
}

EXPORTFUNC gic_recognizerdriver *GICdl_relmouse(void);

gic_recognizerdriver *GICdl_relmouse(void)
{
	return &mycontrols;
}
