/*
touch.c - touchscreen support prototype
Copyright (C) 2015-2018 mittorn

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 3 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.
*/
#include "common.h"
#include "input.h"
#include "client.h"
#include "math.h"
#include "vgui_draw.h"
#include "mobility_int.h"

typedef enum
{
	touch_command, // just tap a button
	touch_move,    // like a joystick stick
	touch_joy,     // like a joystick stick, centered
	touch_dpad,    // only two directions
	touch_look,    // like a touchpad
	touch_wheel    // scroll-like
} touchButtonType;

typedef enum
{
	state_none = 0,
	state_edit,
	state_edit_move
} touchState;

typedef enum
{
	round_none = 0,
	round_grid,
	round_aspect
} touchRound;

typedef struct touch_button_s
{
	// Touch button type: tap, stick or slider
	touchButtonType type;

	// Field of button
	float x1, y1, x2, y2;

	// Button texture
	int texture;
	rgba_t color;
	char texturefile[256];
	char command[256];
	char name[32];
	int finger;
	int flags;
	float fade;
	float fadespeed;
	float fadeend;
	float aspect;

	// Double-linked list
	struct touch_button_s *next;
	struct touch_button_s *prev;
} touch_button_t;

typedef struct touchdefaultbutton_s
{
	char name[32];
	char texturefile[256];
	char command[256];
	float x1, y1, x2, y2;
	rgba_t color;
	touchRound round;
	float aspect;
	int flags;
} touchdefaultbutton_t;

typedef struct touchbuttonlist_s
{
	touch_button_t *first;
	touch_button_t *last;
} touchbuttonlist_t;

static struct touch_s
{
	qboolean initialized;
	qboolean config_loaded;
	touchbuttonlist_t list_user, list_edit;
	poolhandle_t mempool;
	touchState state;

	int look_finger;
	int move_finger;
	int wheel_finger;

	touch_button_t *move_button;
	float move_start_x;
	float move_start_y;

	float wheel_amount;
	string wheel_up;
	string wheel_down;
	string wheel_end;
	int wheel_count;
	qboolean wheel_horizontal;

	float forward;
	float side;
	float yaw;
	float pitch;

	// editing
	touch_button_t *edit;
	touch_button_t *selection;
	touch_button_t *hidebutton;
	int resize_finger;
	qboolean showeditbuttons;

	// other features
	qboolean clientonly;
	rgba_t scolor;
	int swidth;
	qboolean precision;

	// textures
	int whitetexture;
	int joytexture; // touch indicator
	qboolean configchanged;
	float actual_aspect_ratio; // maximum aspect ratio from launch, or aspect ratio when entering editor
	float config_aspect_ratio; // aspect ratio set by command from config or after entering editor
} touch;

// private to the engine flags
#define TOUCH_FL_UNPRIVILEGED BIT( 10 )

touchdefaultbutton_t g_DefaultButtons[256];
int g_LastDefaultButton;

static CVAR_DEFINE_AUTO( touch_in_menu, "0", FCVAR_FILTERABLE, "draw touch in menu (for internal use only)" );
static CVAR_DEFINE_AUTO( touch_forwardzone, "0.06", FCVAR_FILTERABLE, "forward touch zone" );
static CVAR_DEFINE_AUTO( touch_sidezone, "0.06", FCVAR_FILTERABLE, "side touch zone" );
static CVAR_DEFINE_AUTO( touch_pitch, "90", FCVAR_FILTERABLE, "touch pitch sensitivity" );
static CVAR_DEFINE_AUTO( touch_yaw, "120", FCVAR_FILTERABLE, "touch yaw sensitivity" );
static CVAR_DEFINE_AUTO( touch_nonlinear_look, "0", FCVAR_FILTERABLE, "enable nonlinear touch look" );
static CVAR_DEFINE_AUTO( touch_pow_factor, "1.3", FCVAR_FILTERABLE, "set > 1 to enable" );
static CVAR_DEFINE_AUTO( touch_pow_mult, "400.0", FCVAR_FILTERABLE, "power multiplier, usually 200-1000" );
static CVAR_DEFINE_AUTO( touch_exp_mult, "0", FCVAR_FILTERABLE, "exponent multiplier, usually 20-200, 0 to disable" );
static CVAR_DEFINE_AUTO( touch_grid_count, "50", FCVAR_FILTERABLE, "touch grid count" );
static CVAR_DEFINE_AUTO( touch_grid_enable, "1", FCVAR_FILTERABLE, "enable touch grid" );
static CVAR_DEFINE_AUTO( touch_config_file, "touch.cfg", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "current touch profile file" );
static CVAR_DEFINE_AUTO( touch_precise_amount, "0.5", FCVAR_FILTERABLE, "sensitivity multiplier for precise-look" );
static CVAR_DEFINE_AUTO( touch_highlight_r, "1.0", 0, "highlight r color" );
static CVAR_DEFINE_AUTO( touch_highlight_g, "1.0", 0, "highlight g color" );
static CVAR_DEFINE_AUTO( touch_highlight_b, "1.0", 0, "highlight b color" );
static CVAR_DEFINE_AUTO( touch_highlight_a, "1.0", 0, "highlight alpha" );
static CVAR_DEFINE_AUTO( touch_dpad_radius, "1.0", FCVAR_FILTERABLE, "dpad radius multiplier" );
static CVAR_DEFINE_AUTO( touch_joy_radius, "1.0", FCVAR_FILTERABLE, "joy radius multiplier" );
static CVAR_DEFINE_AUTO( touch_move_indicator, "0.0", FCVAR_FILTERABLE, "indicate move events (0 to disable)" );
static CVAR_DEFINE_AUTO( touch_joy_texture, "touch_default/joy", FCVAR_FILTERABLE, "texture for move indicator");
CVAR_DEFINE_AUTO( touch_enable, DEFAULT_TOUCH_ENABLE, FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable touch controls" );
CVAR_DEFINE_AUTO( touch_emulate, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "emulate touch with mouse" );

// code looks smaller with it
#define B(x) (button->x)
#define SCR_W ((float)refState.width)
#define SCR_H ((float)refState.height)
#define TO_SCRN_Y(x) (refState.width * (x) * Touch_AspectRatio())
#define TO_SCRN_X(x) (refState.width * (x))

static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2  );
static void IN_TouchEditClear( void );
static void Touch_InitConfig( void );

void Touch_NotifyResize( void )
{
	if( refState.width && refState.height && ( !touch.configchanged || !touch.actual_aspect_ratio ))
	{
		float aspect_ratio = SCR_H/SCR_W;
		if( aspect_ratio < 0.99 && aspect_ratio > touch.actual_aspect_ratio )
			touch.actual_aspect_ratio = aspect_ratio;
	}
}

static inline float Touch_AspectRatio( void )
{
	if( touch.config_aspect_ratio )
		return touch.config_aspect_ratio;
	else if( touch.actual_aspect_ratio )
		return touch.actual_aspect_ratio;
	else if( refState.width && refState.height )
		return SCR_H/SCR_W;
	else
		return 9.0f / 16.0f;
}


static void Touch_ConfigAspectRatio_f( void )
{
	touch.config_aspect_ratio = Q_atof( Cmd_Argv( 1 ));
}


/*
==========================
Touch_ExportButtonToConfig

writes button data to config
returns 0 on success, non-zero on error
==========================
*/
static inline int Touch_ExportButtonToConfig( file_t *f, touch_button_t *button, qboolean keepAspect )
{
	string newCommand;
	int flags = button->flags;

	if( FBitSet( flags, TOUCH_FL_CLIENT ))
		return 1; // skip temporary buttons

	if( FBitSet( flags, TOUCH_FL_DEF_SHOW ))
		ClearBits( flags, TOUCH_FL_HIDE );

	if( FBitSet( flags, TOUCH_FL_DEF_HIDE ))
		SetBits( flags, TOUCH_FL_HIDE );

	Cmd_Escape( newCommand, B( command ), sizeof( newCommand ));

	FS_Printf( f, "touch_addbutton \"%s\" \"%s\" \"%s\" %f %f %f %f %d %d %d %d %d",
		B(name), B(texturefile), newCommand,
		B(x1), B(y1), B(x2), B(y2),
		B(color[0]), B(color[1]), B(color[2]), B(color[3]), flags );

	if( keepAspect )
	{
		float aspect = ( B(y2) - B(y1) ) / ( ( B(x2) - B(x1) ) /(Touch_AspectRatio()) );
		FS_Printf( f, " %f\n", aspect );
	}
	else FS_Printf( f, "\n" );

	return 0;
}

/*
=================
Touch_DumpConfig

Dump config to file
=================
*/
static qboolean Touch_DumpConfig( const char *name, const char *profilename )
{
	file_t *f;
	touch_button_t *button;

	f = FS_Open( name, "w", true );

	if( !f )
	{
		Con_Printf( S_ERROR "Couldn't write %s.\n", name );
		return false;
	}

	FS_Printf( f, "//=======================================================================\n");
	FS_Printf( f, "//\tCopyright FWGS & XashXT group %s (c)\n", Q_timestamp( TIME_YEAR_ONLY ));
	FS_Printf( f, "//\t\t\ttouchscreen config\n" );
	FS_Printf( f, "//=======================================================================\n" );
	FS_Printf( f, "\ntouch_config_file \"%s\"\n", profilename );
	FS_Printf( f, "\n// touch cvars\n" );
	FS_Printf( f, "\n// sensitivity settings\n" );
	FS_Printf( f, "touch_pitch \"%f\"\n", touch_pitch.value );
	FS_Printf( f, "touch_yaw \"%f\"\n", touch_yaw.value );
	FS_Printf( f, "touch_forwardzone \"%f\"\n", touch_forwardzone.value );
	FS_Printf( f, "touch_sidezone \"%f\"\n", touch_sidezone.value );
	FS_Printf( f, "touch_nonlinear_look \"%d\"\n", touch_nonlinear_look.value ? 1 : 0 );
	FS_Printf( f, "touch_pow_factor \"%f\"\n", touch_pow_factor.value );
	FS_Printf( f, "touch_pow_mult \"%f\"\n", touch_pow_mult.value );
	FS_Printf( f, "touch_exp_mult \"%f\"\n", touch_exp_mult.value );
	FS_Printf( f, "\n// grid settings\n" );
	FS_Printf( f, "touch_grid_count \"%d\"\n", (int)touch_grid_count.value );
	FS_Printf( f, "touch_grid_enable \"%d\"\n", touch_grid_enable.value ? 1 : 0 );
	FS_Printf( f, "\n// global overstroke (width, r, g, b, a)\n" );
	FS_Printf( f, "touch_set_stroke %d %d %d %d %d\n", touch.swidth, touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] );
	FS_Printf( f, "\n// highlight when pressed\n" );
	FS_Printf( f, "touch_highlight_r \"%f\"\n", touch_highlight_r.value );
	FS_Printf( f, "touch_highlight_g \"%f\"\n", touch_highlight_g.value );
	FS_Printf( f, "touch_highlight_b \"%f\"\n", touch_highlight_b.value );
	FS_Printf( f, "touch_highlight_a \"%f\"\n", touch_highlight_a.value );
	FS_Printf( f, "\n// _joy and _dpad options\n" );
	FS_Printf( f, "touch_dpad_radius \"%f\"\n", touch_dpad_radius.value );
	FS_Printf( f, "touch_joy_radius \"%f\"\n", touch_joy_radius.value );
	FS_Printf( f, "\n// how much slowdown when Precise Look button pressed\n" );
	FS_Printf( f, "touch_precise_amount \"%f\"\n", touch_precise_amount.value );
	FS_Printf( f, "\n// enable/disable move indicator\n" );
	FS_Printf( f, "touch_move_indicator \"%f\"\n", touch_move_indicator.value );

	FS_Printf( f, "\n// reset menu state when execing config\n" );
	FS_Printf( f, "touch_setclientonly 0\n" );
	FS_Printf( f, "\n// touch buttons\n" );
	FS_Printf( f, "touch_removeall\n" );
	FS_Printf( f, "touch_aspectratio %f\n", Touch_AspectRatio());


	for( button = touch.list_user.first; button; button = button->next )
	{
		Touch_ExportButtonToConfig( f, button, false );
	}

	FS_Close( f );

	return true;
}

/*
=================
Touch_WriteConfig

save current touch configuration
=================
*/
void Touch_WriteConfig( void )
{
	file_t	*f;
	string newconfigfile, oldconfigfile;

	if( !touch.list_user.first )
		return;

	if( Sys_CheckParm( "-nowriteconfig" ) || !touch.configchanged || !touch.config_loaded )
		return;

	Con_DPrintf( "Touch_WriteConfig(): %s\n", touch_config_file.string );

	Q_snprintf( newconfigfile, sizeof( newconfigfile ), "%s.new", touch_config_file.string );
	Q_snprintf( oldconfigfile, sizeof( oldconfigfile ), "%s.bak", touch_config_file.string );

	if( Touch_DumpConfig( newconfigfile, touch_config_file.string ))
	{
		FS_Delete( oldconfigfile );
		FS_Rename( touch_config_file.string, oldconfigfile );

		FS_Delete( touch_config_file.string );
		FS_Rename( newconfigfile, touch_config_file.string );
	}
}

/*
=================
Touch_ExportConfig_f

export current touch configuration into profile
=================
*/
static void Touch_ExportConfig_f( void )
{
	const char *name;
	string profilename, profilebase;

	if( Cmd_Argc() != 2 )
	{
		Con_Printf( S_USAGE "touch_exportconfig <name>\n" );
		return;
	}

	if( !touch.list_user.first ) return;

	name = Cmd_Argv( 1 );

	if( Q_strstr( name, "touch_presets/" ))
	{
		COM_FileBase( name, profilebase, sizeof( profilebase ));
		Q_snprintf( profilename, sizeof( profilebase ), "touch_profiles/%s (copy).cfg", profilebase );
	}
	else Q_strncpy( profilename, name, sizeof( profilename ));

	Con_Reportf( "Exporting config to \"%s\", profile name \"%s\"\n", name, profilename );
	Touch_DumpConfig( name, profilename );
}

/*
=================
Touch_GenerateCode_f

export current touch configuration into C code
=================
*/
static void Touch_GenerateCode_f( void )
{
	touch_button_t *button;
	rgba_t c = {0,0,0,0};

	if( !touch.list_user.first ) return;

	for( button = touch.list_user.first; button; button = button->next )
	{
		float aspect;
		int flags = button->flags;

		if( FBitSet( flags, TOUCH_FL_CLIENT ))
			continue; // skip temporary buttons

		if( FBitSet( flags, TOUCH_FL_DEF_SHOW ))
			ClearBits( flags, TOUCH_FL_HIDE );

		if( FBitSet( flags, TOUCH_FL_DEF_HIDE ))
			SetBits( flags, TOUCH_FL_HIDE );

		aspect = ( B(y2) - B(y1) ) / ( ( B(x2) - B(x1) ) /(Touch_AspectRatio()) );
		if( memcmp( &c, &B(color), sizeof( rgba_t ) ) )
		{
			Con_Printf( "unsigned char color[] = { %d, %d, %d, %d };\n", B(color[0]), B(color[1]), B(color[2]), B(color[3]) );
			memcpy( &c, &B(color), sizeof( rgba_t ) );
		}
		Con_Printf( "TOUCH_ADDDEFAULT( \"%s\", \"%s\", \"%s\", %f, %f, %f, %f, color, %d, %f, %d );\n",
			B(name), B(texturefile), B(command),
			B(x1), B(y1), B(x2), B(y2), (B(type) == touch_command)?(fabs( aspect  - 1.0f) < 0.0001)?2:1:0, aspect, flags );
	}
}

static void Touch_RoundAll_f( void )
{
	touch_button_t *button;

	if( !touch_grid_enable.value )
		return;

	for( button = touch.list_user.first; button; button = button->next )
		IN_TouchCheckCoords( &B(x1), &B(y1), &B(x2), &B(y2) );
}

static void Touch_ListButtons_f( void )
{
	touch_button_t *button;
	Touch_InitConfig();

	for( button = touch.list_user.first; button; button = button->next )
	{
		Con_Printf( "%s %s %s %f %f %f %f %d %d %d %d %d\n",
			B(name), B(texturefile), B(command),
			B(x1), B(y1), B(x2), B(y2),
			B(color[0]), B(color[1]), B(color[2]), B(color[3]), B(flags) );

		if( B(flags) & TOUCH_FL_CLIENT)
			continue;

		UI_AddTouchButtonToList( B(name), B(texturefile), B(command),B(color), B(flags) );
	}
	touch.configchanged = true;
}

static void Touch_Stroke_f( void )
{
	if( Cmd_Argc() != 6 )
	{
		Con_Printf( S_USAGE "touch_set_stroke <width> <r> <g> <b> <a>\n");
		return;
	}

	touch.swidth = Q_atoi( Cmd_Argv( 1 ) );
	MakeRGBA( touch.scolor, Q_atoi( Cmd_Argv( 2 ) ), Q_atoi( Cmd_Argv( 3 ) ), Q_atoi( Cmd_Argv( 4 ) ), Q_atoi( Cmd_Argv( 5 ) ) );
}

static touch_button_t *Touch_FindButton( touchbuttonlist_t *list, const char *name, qboolean privileged )
{
	touch_button_t *button;

	for( button = list->first; button; button = button->next )
	{
		if( !privileged && !FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED ))
			continue;

		if( Q_strncmp( button->name, name, sizeof( button->name )))
			continue;

		return button;
	}

	return NULL;
}

static touch_button_t *Touch_FindFirst( touchbuttonlist_t *list, const char *name, qboolean privileged )
{
	touch_button_t *button;

	for( button = list->first; button; button = button->next )
	{
		if( !privileged && !FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED ))
			continue;

		if(( Q_strchr( name, '*' ) && Q_stricmpext( name, button->name )) || !Q_strncmp( name, button->name, sizeof( button->name )))
		{
			return button;
		}
	}
	return NULL;
}

void Touch_SetClientOnly( byte state )
{
	// TODO: fix clash with vgui cursors
	touch.clientonly = state;

	touch.move_finger = touch.look_finger = -1;
	touch.forward = touch.side = 0;

	if( state )
	{
		IN_DeactivateMouse();
	}
	else
	{
		IN_ActivateMouse();
	}
}

static void Touch_SetClientOnly_f( void )
{
	if( Cmd_Argc() != 2 )
	{
		Con_Printf( S_USAGE "touch_setclientonly <state>\n");
		return;
	}

	Touch_SetClientOnly( Q_atoi( Cmd_Argv( 1 )));
}

static void Touch_RemoveButtonFromList( touchbuttonlist_t *list, const char *name, qboolean privileged )
{
	touch_button_t *button;

	IN_TouchEditClear();

	while(( button = Touch_FindFirst( &touch.list_user, name, privileged )))
	{
		if( button->prev )
			button->prev->next = button->next;
		else
			list->first = button->next;

		if( button->next )
			button->next->prev = button->prev;
		else
			list->last = button->prev;

		Mem_Free( button );
	}

}

void Touch_RemoveButton( const char *name, qboolean privileged )
{
	Touch_RemoveButtonFromList( &touch.list_user, name, privileged );
}

static void IN_TouchRemoveButton_f( void )
{
	if( Cmd_Argc() != 2 )
	{
		Con_Printf( S_USAGE "touch_removebutton <button>\n");
		return;
	}

	Touch_RemoveButton( Cmd_Argv( 1 ), Cmd_CurrentCommandIsPrivileged());
}

static void Touch_ClearList( touchbuttonlist_t *list )
{
	while( list->first )
	{
		touch_button_t *remove = list->first;
		list->first = list->first->next;
		Mem_Free( remove );
	}
	list->first = list->last = NULL;
}

static void Touch_RemoveAll_f( void )
{
	IN_TouchEditClear();
	Touch_ClearList( &touch.list_user );
	touch.config_aspect_ratio = 0.0f;
}

static void Touch_SetColor( touchbuttonlist_t *list, const char *name, byte *color )
{
	touch_button_t *button;

	for( button = list->first; button; button = button->next )
	{
		if(( Q_strchr( name, '*' ) && Q_stricmpext( name, button->name )) || !Q_strncmp( name, button->name, sizeof( button->name )))
			MakeRGBA( button->color, color[0], color[1], color[2], color[3] );
	}
}

static void Touch_SetTexture( touchbuttonlist_t *list, const char *name, const char *texture, qboolean privileged )
{
	touch_button_t *button = Touch_FindButton( list, name, privileged );

	if( !button )
		return;

	button->texture = -1; // mark for texture load
	Q_strncpy( button->texturefile, texture, sizeof( button->texturefile ));
}

static void Touch_SetCommand( touch_button_t *button, const char *command )
{
	Q_strncpy( button->command, command, sizeof( button->command ));

	if( !Q_strcmp( command, "_look" ))
		button->type = touch_look;
	else if( !Q_strcmp( command, "_move" ))
		button->type = touch_move;
	else if( !Q_strcmp( command, "_joy" ))
		button->type = touch_joy;
	else if( !Q_strcmp( command, "_dpad" ))
		button->type = touch_dpad;
	else if( Q_stricmpext( "_wheel *", command ) || Q_stricmpext( "_hwheel *", command ))
		button->type = touch_wheel;
}

void Touch_HideButtons( const char *name, byte hide, qboolean privileged )
{
	touch_button_t *button;

	for( button = touch.list_user.first; button; button = button->next)
	{
		if( !privileged && !FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED ))
		    continue;

		if(( Q_strchr( name, '*' ) && Q_stricmpext( name, button->name )) || !Q_strncmp( name, button->name, sizeof( button->name )))
		{
			if( hide )
				SetBits( button->flags, TOUCH_FL_HIDE );
			else
				ClearBits( button->flags, TOUCH_FL_HIDE );
		}
	}
}

static void Touch_ToggleSelection_f( void )
{
	if( touch.selection )
		touch.selection->flags ^= TOUCH_FL_HIDE;
}

static void Touch_Hide_f( void )
{
	if( Cmd_Argc() != 2 )
	{
		Con_Printf( S_USAGE "touch_hide <button>\n");
		return;
	}

	Touch_HideButtons( Cmd_Argv( 1 ), true, Cmd_CurrentCommandIsPrivileged() );
}

static void Touch_Show_f( void )
{
	if( Cmd_Argc() != 2 )
	{
		Con_Printf( S_USAGE "touch_show <button>\n");
		return;
	}

	Touch_HideButtons( Cmd_Argv( 1 ), false, Cmd_CurrentCommandIsPrivileged() );
}

static void Touch_FadeButtons( touchbuttonlist_t *list, const char *name, float speed, float end, float start, qboolean privileged )
{
	touch_button_t *button;
	for( button = list->first; button; button = button->next)
	{
		if( !privileged && !FBitSet( button->flags, TOUCH_FL_UNPRIVILEGED ))
		    continue;

		if(( Q_strchr( name, '*' ) && Q_stricmpext( name, button->name )) || !Q_strncmp( name, button->name, sizeof( button->name )))
		{
			if( start >= 0 )
				button->fade = start;
			button->fadespeed = speed;
			button->fadeend = end;
		}
	}
}

static void Touch_Fade_f( void )
{
	float start = -1;

	if( Cmd_Argc() == 5 )
	{
		start = Q_atof( Cmd_Argv( 4 ) );
	}
	else if( Cmd_Argc() != 4 )
	{
		Con_Printf( S_USAGE "touch_fade <button> <speed> <end> [start]\n");
		return;
	}

	Touch_FadeButtons( &touch.list_user,Cmd_Argv( 1 ), Q_atof( Cmd_Argv( 2 )), Q_atof( Cmd_Argv( 3 )),
		start, Cmd_CurrentCommandIsPrivileged() );
}

static void Touch_SetColor_f( void )
{
	rgba_t color;
	if( Cmd_Argc() == 6 )
	{
	MakeRGBA( color,  Q_atoi( Cmd_Argv(2) ), Q_atoi( Cmd_Argv(3) ), Q_atoi( Cmd_Argv(4) ), Q_atoi( Cmd_Argv(5) ) );
		Touch_SetColor( &touch.list_user, Cmd_Argv(1), color );
		return;
	}
	Con_Printf( S_USAGE "touch_setcolor <pattern> <r> <g> <b> <a>\n" );
}

static void Touch_SetTexture_f( void )
{
	if( Cmd_Argc() == 3 )
	{
		Touch_SetTexture( &touch.list_user, Cmd_Argv( 1 ), Cmd_Argv( 2 ), Cmd_CurrentCommandIsPrivileged() );
		return;
	}
	Con_Printf( S_USAGE "touch_settexture <name> <file>\n" );
}

static void Touch_SetFlags_f( void )
{
	if( Cmd_Argc() == 3 )
	{
		qboolean privileged = Cmd_CurrentCommandIsPrivileged();

		touch_button_t *button = Touch_FindButton( &touch.list_user, Cmd_Argv( 1 ), privileged );
		if( button )
		{
			button->flags = ( privileged ? 0 : TOUCH_FL_UNPRIVILEGED | TOUCH_FL_CLIENT ) | Q_atoi( Cmd_Argv( 2 ));
		}
		return;
	}
	Con_Printf( S_USAGE "touch_setflags <name> <file>\n" );
}

static void Touch_SetCommand_f( void )
{
	if( Cmd_Argc() == 3 )
	{
		touch_button_t *button = Touch_FindButton( &touch.list_user, Cmd_Argv(1), Cmd_CurrentCommandIsPrivileged() );

		if( !button )
			Con_Printf( S_ERROR "no such button" );
		else
			Touch_SetCommand( button, Cmd_Argv( 2 ) );

		return;
	}
	Con_Printf( S_USAGE "touch_setcommand <name> <command>\n" );
}

static void Touch_LoadDefaults_f( void );

static void Touch_ReloadConfig_f( void )
{
	touch.state = state_none;
	if( touch.edit )
		touch.edit->finger = -1;
	if( touch.selection )
		touch.selection->finger = -1;
	touch.edit = touch.selection = NULL;
	touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1;
	if( FS_FileExists( touch_config_file.string, true ) )
	{
		Cbuf_AddTextf( "exec \"%s\"\n", touch_config_file.string );
	}
	else
	{
		Touch_LoadDefaults_f();
		touch.configchanged = true;
	}
}

static touch_button_t *Touch_AddButton( touchbuttonlist_t *list,
	const char *name, const char *texture, const char *command,
	float x1, float y1, float x2, float y2, byte *color,
	qboolean privileged )
{
	touch_button_t *button = Mem_Calloc( touch.mempool, sizeof( touch_button_t ) );

	button->texture = -1;
	Q_strncpy( button->texturefile, texture, sizeof( B( texturefile )));
	Q_strncpy( button->name, name, sizeof( B( name )));
	Touch_RemoveButtonFromList( list, name, privileged ); //replace if exist
	button->x1 = x1;
	button->y1 = y1;
	button->x2 = x2;
	button->y2 = y2;
	button->flags = privileged ? 0 : TOUCH_FL_UNPRIVILEGED | TOUCH_FL_CLIENT;
	MakeRGBA( button->color, color[0], color[1], color[2], color[3] );
	button->command[0] = 0;
	button->fade = 1;

	Touch_SetCommand( button, command );

	button->finger = -1;
	button->next = NULL;
	button->prev = list->last;
	if( list->last )
		list->last->next = button;
	list->last = button;

	if( !list->first )
		list->first = button;

	return button;
}

void Touch_AddClientButton( const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags )
{
	touch_button_t *button;

	if( !touch.initialized )
		return;
	if( round )
		IN_TouchCheckCoords( &x1, &y1, &x2, &y2 );
	if( round == round_aspect )
	{
		y2 = y1 + ( x2 - x1 ) / (Touch_AspectRatio()) * aspect;
	}
	button = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, true );
	button->flags |= flags | TOUCH_FL_CLIENT | TOUCH_FL_NOEDIT;
	button->aspect = aspect;
}

static void Touch_LoadDefaults_f( void )
{
	int i;
	for( i = 0; i < g_LastDefaultButton; i++ )
	{
		touch_button_t *button;
		float x1 = g_DefaultButtons[i].x1,
			  y1 = g_DefaultButtons[i].y1,
			  x2 = g_DefaultButtons[i].x2,
			  y2 = g_DefaultButtons[i].y2;

		IN_TouchCheckCoords( &x1, &y1, &x2, &y2 );

		if( g_DefaultButtons[i].aspect && g_DefaultButtons[i].round == round_aspect )
		{
			if( g_DefaultButtons[i].texturefile[0] == '#' )
				y2 = y1 + ( (float)clgame.scrInfo.iCharHeight / (float)clgame.scrInfo.iHeight ) * g_DefaultButtons[i].aspect + touch.swidth*2/SCR_H;
			else
				y2 = y1 + (( x2 - x1 ) / Touch_AspectRatio()) * g_DefaultButtons[i].aspect;
		}

		IN_TouchCheckCoords( &x1, &y1, &x2, &y2 );
		button = Touch_AddButton( &touch.list_user, g_DefaultButtons[i].name, g_DefaultButtons[i].texturefile, g_DefaultButtons[i].command, x1, y1, x2, y2, g_DefaultButtons[i].color, true );
		button->flags |= g_DefaultButtons[i].flags;
		button->aspect = g_DefaultButtons[i].aspect;
	}
	touch.configchanged = true;
}

// Add default button from client
void Touch_AddDefaultButton( const char *name, const char *texturefile, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags )
{
	touchdefaultbutton_t *button;

	if( g_LastDefaultButton >= 255 )
		return;

	button = g_DefaultButtons + g_LastDefaultButton;

	Q_strncpy( B( name ), name, sizeof( B( name )));
	Q_strncpy( B( texturefile ), texturefile, sizeof( B( texturefile )));
	Q_strncpy( B( command ), command, sizeof( B( command )));
	B( x1 ) = x1;
	B( y1 ) = y1;
	B( x2 ) = x2;
	B( y2 ) = y2;
	MakeRGBA( B( color ), color[0], color[1], color[2], color[3] );
	B( round )  = round;
	B( aspect ) = aspect;
	B( flags )  = flags;
	g_LastDefaultButton++;
}

// Client may remove all default buttons from engine
void Touch_ResetDefaultButtons( void )
{
	g_LastDefaultButton = 0;
}

static void Touch_AddButton_f( void )
{
	rgba_t color;
	int argc = Cmd_Argc( );
	touch_button_t *button = NULL;
	const char *name, *texture, *command;
	float x1, y1, x2, y2;
	qboolean privileged = Cmd_CurrentCommandIsPrivileged();

	if( argc < 4 )
	{
		Con_Printf( S_USAGE "touch_addbutton <name> <texture> <command> [<x1> <y1> <x2> <y2> [ r g b a ] ]\n" );
		return;
	}

	name = Cmd_Argv( 1 );
	texture = Cmd_Argv( 2 );
	command = Cmd_Argv( 3 );

	if( argc < 8 )
	{
		x1 = y1 = 0.4f;
		x2 = y2 = 0.6f;
	}
	else
	{
		x1 = Q_atof( Cmd_Argv( 4 ));
		y1 = Q_atof( Cmd_Argv( 5 ));
		x2 = Q_atof( Cmd_Argv( 6 ));
		y2 = Q_atof( Cmd_Argv( 7 ));
	}

	if( argc < 12 )
	{
		MakeRGBA( color, 255, 255, 255, 255 );
	}
	else
	{
		MakeRGBA( color,
			Q_atoi( Cmd_Argv( 8 )),
			Q_atoi( Cmd_Argv( 9 )),
			Q_atoi( Cmd_Argv( 10 )),
			Q_atoi( Cmd_Argv( 11 )));
	}

	button = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, privileged );

	if( argc >= 13 )
	{
		SetBits( button->flags, Q_atoi( Cmd_Argv( 12 )));
	}

	if( argc >= 14 )
	{
		// Recalculate button coordinates aspect ratio
		// This is feature for distributed configs
		float aspect = Q_atof( Cmd_Argv( 13 ));
		if( aspect )
		{
			if( B( texturefile )[0] != '#' )
				B( y2 ) = B( y1 ) + (( B( x2 ) - B( x1 )) / Touch_AspectRatio() ) * aspect;
			B( aspect ) = aspect;
		}
	}
}

static void Touch_EnableEdit_f( void )
{
	touch_button_t *button;
	float current_ratio = SCR_H/SCR_W;
	if( touch.state == state_none )
		touch.state = state_edit;
	touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1;
	touch.move_button = NULL;
	touch.configchanged = true;
	/* try determine the best ratio
	 * User enters editor. Window now have correct size. Need to fix aspect ratio in some cases */
	// Case A: no config was loaded, touch was generated with lower height, but window was resized higher, reset it to actual size
	if( touch.actual_aspect_ratio > current_ratio )
		touch.actual_aspect_ratio = current_ratio;
	if( !touch.config_aspect_ratio )
		touch.config_aspect_ratio = touch.actual_aspect_ratio;
	// Case B: config was loaded, but window may be resized later, so keep y coordinate as is
	touch.actual_aspect_ratio = current_ratio;
	// convert coordinates to actual aspect ratio after it was updated
	if( touch.config_aspect_ratio != touch.actual_aspect_ratio )
	{
		for( button = touch.list_user.first; button; button = button->next )
		{
			B(y1) /= touch.actual_aspect_ratio / touch.config_aspect_ratio;
			B(y2) /= touch.actual_aspect_ratio / touch.config_aspect_ratio;

			// clamp positions to make buttons visible by user
			if( B(y2) > 1.0f )
			{
				B(y1) -= B(y2) - 1.0f;
				B(y2) -= B(y2) - 1.0f;
			}
		}
		touch.config_aspect_ratio = touch.actual_aspect_ratio;
	}
}

static void Touch_DisableEdit_f( void )
{
	touch.state = state_none;
	if( touch.edit )
		touch.edit->finger = -1;
	if( touch.selection )
		touch.selection->finger = -1;
	touch.edit = touch.selection = NULL;
	touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1;

	if( touch_in_menu.value )
	{
		Cvar_Set( "touch_in_menu", "0" );
	}
	else if( cls.key_dest ==  key_game )
		Touch_WriteConfig();
}

static void Touch_DeleteProfile_f( void )
{
	if( Cmd_Argc() != 2 )
	{
		Con_Printf( S_USAGE "touch_deleteprofile <name>\n" );
		return;
	}

	// delete profile
	FS_Delete( va( "touch_profiles/%s.cfg", Cmd_Argv( 1 )));
}

static void Touch_InitEditor( void )
{
	float x = 0.1f * (Touch_AspectRatio());
	float y = 0.05f;
	touch_button_t *temp;
	rgba_t color;

	MakeRGBA( color, 255, 255, 255, 255 );

	Touch_ClearList( &touch.list_edit );

	temp = Touch_AddButton( &touch.list_edit, "close", "touch_default/edit_close", "touch_disableedit", 0, y, x, y + 0.1f, color, true );
	SetBits( temp->flags, TOUCH_FL_NOEDIT );

	temp = Touch_AddButton( &touch.list_edit, "close", "#Close and save", "", x, y, x + 0.2f, y + 0.1f, color, true );
	SetBits( temp->flags, TOUCH_FL_NOEDIT );

	y += 0.2f;

	temp = Touch_AddButton( &touch.list_edit, "cancel", "touch_default/edit_reset", "touch_reloadconfig", 0, y, x, y + 0.1f, color, true );
	SetBits( temp->flags, TOUCH_FL_NOEDIT );

	temp = Touch_AddButton( &touch.list_edit, "close", "#Cancel and reset", "", x, y, x + 0.2f, y + 0.1f, color, true );
	SetBits( temp->flags, TOUCH_FL_NOEDIT );

	y += 0.2f;

	touch.hidebutton = Touch_AddButton( &touch.list_edit, "showhide", "touch_default/edit_hide", "touch_toggleselection", 0, y, x, y + 0.1f, color, true );
	SetBits( touch.hidebutton->flags, TOUCH_FL_HIDE | TOUCH_FL_NOEDIT );
}

void Touch_Init( void )
{
	rgba_t color;
	if( touch.initialized )
		return;
	touch.mempool = Mem_AllocPool( "Touch" );
	//touch.first = touch.last = NULL;
	Con_Printf( "IN_TouchInit()\n");
	touch.move_finger = touch.resize_finger = touch.look_finger = touch.wheel_finger = -1;
	touch.state = state_none;
	touch.showeditbuttons = true;
	touch.clientonly = false;
	touch.precision = false;
	MakeRGBA( touch.scolor, 255, 255, 255, 255 );
	touch.swidth = 1;
	g_LastDefaultButton = 0;

	touch.list_edit.first = touch.list_edit.last = NULL;
	touch.list_user.first = touch.list_user.last = NULL;

	// fill default buttons list
	MakeRGBA( color, 255, 255, 255, 255 );
	Touch_AddDefaultButton( "look", "", "_look", 0.500000, 0.000000, 1.000000, 1, color, 0, 0, 0 );
	Touch_AddDefaultButton( "move", "", "_move", 0.000000, 0.000000, 0.500000, 1, color, 0, 0, 0 );
	Touch_AddDefaultButton( "invnext", "touch_default/next_weap", "invnext", 0.000000, 0.530200, 0.120000, 0.757428, color, 2, 1, 0 );
	Touch_AddDefaultButton( "invprev", "touch_default/prev_weap", "invprev", 0.000000, 0.075743, 0.120000, 0.302971, color, 2, 1, 0 );
	Touch_AddDefaultButton( "use", "touch_default/use", "+use", 0.880000, 0.454457, 1.000000, 0.681685, color, 2, 1, 0 );
	Touch_AddDefaultButton( "jump", "touch_default/jump", "+jump", 0.880000, 0.227228, 1.000000, 0.454457, color, 2, 1, 0 );
	Touch_AddDefaultButton( "attack", "touch_default/shoot", "+attack", 0.760000, 0.530200, 0.880000, 0.757428, color, 2, 1, 0 );
	Touch_AddDefaultButton( "attack2", "touch_default/shoot_alt", "+attack2", 0.760000, 0.302971, 0.880000, 0.530200, color, 2, 1, 0 );
	Touch_AddDefaultButton( "loadquick", "touch_default/load", "loadquick", 0.680000, 0.000000, 0.760000, 0.151486, color, 2, 1, 16 );
	Touch_AddDefaultButton( "savequick", "touch_default/save", "savequick", 0.760000, 0.000000, 0.840000, 0.151486, color, 2, 1, 16 );
	Touch_AddDefaultButton( "messagemode", "touch_default/keyboard", "messagemode", 0.760000, 0.000000, 0.840000, 0.151486, color, 2, 1, 8 );
	Touch_AddDefaultButton( "reload", "touch_default/reload", "+reload", 0.000000, 0.302971, 0.120000, 0.530200, color, 2, 1, 0 );
	Touch_AddDefaultButton( "flashlight", "touch_default/flash_light_filled", "impulse 100", 0.920000, 0.000000, 1.000000, 0.151486, color, 2, 1, 0 );
	Touch_AddDefaultButton( "scores", "touch_default/map", "+showscores", 0.680000, 0.000000, 0.760000, 0.151486, color, 2, 1, 8 );
	Touch_AddDefaultButton( "show_numbers", "touch_default/show_weapons", "exec touch_default/numbers.cfg", 0.440000, 0.833171, 0.520000, 0.984656, color, 2, 1, 0 );
	Touch_AddDefaultButton( "duck", "touch_default/crouch", "+duck", 0.880000, 0.757428, 1.000000, 0.984656, color, 2, 1, 0 );
	Touch_AddDefaultButton( "tduck", "touch_default/tduck", ";+duck", 0.560000, 0.833171, 0.620000, 0.946785, color, 2, 1, 0 );
	Touch_AddDefaultButton( "edit", "touch_default/settings", "touch_enableedit", 0.420000, 0.000000, 0.500000, 0.151486, color, 2, 1, 32 );
	Touch_AddDefaultButton( "menu", "touch_default/menu", "escape", 0.000000, 0.833171, 0.080000, 0.984656, color, 2, 1, 0 );
	Touch_AddDefaultButton( "spray", "touch_default/spray", "impulse 201", 0.840000, 0.000000, 0.920000, 0.151486, color, 2, 1, 0 );

	Cmd_AddCommand( "touch_addbutton", Touch_AddButton_f, "add native touch button" );
	Cmd_AddCommand( "touch_removebutton", IN_TouchRemoveButton_f, "remove native touch button" );
	Cmd_AddRestrictedCommand( "touch_enableedit", Touch_EnableEdit_f, "enable button editing mode" );
	Cmd_AddRestrictedCommand( "touch_disableedit", Touch_DisableEdit_f, "disable button editing mode" );
	Cmd_AddCommand( "touch_settexture", Touch_SetTexture_f, "change button texture" );
	Cmd_AddCommand( "touch_setcolor", Touch_SetColor_f, "change button color" );
	Cmd_AddCommand( "touch_setcommand", Touch_SetCommand_f, "change button command" );
	Cmd_AddCommand( "touch_setflags", Touch_SetFlags_f, "change button flags (be careful)" );
	Cmd_AddCommand( "touch_show", Touch_Show_f, "show button" );
	Cmd_AddCommand( "touch_hide", Touch_Hide_f, "hide button" );
	Cmd_AddRestrictedCommand( "touch_list", Touch_ListButtons_f, "list buttons" );
	Cmd_AddRestrictedCommand( "touch_removeall", Touch_RemoveAll_f, "remove all buttons" );
	Cmd_AddRestrictedCommand( "touch_loaddefaults", Touch_LoadDefaults_f, "generate config from defaults" );
	Cmd_AddRestrictedCommand( "touch_roundall", Touch_RoundAll_f, "round all buttons coordinates to grid" );
	Cmd_AddRestrictedCommand( "touch_exportconfig", Touch_ExportConfig_f, "export config keeping aspect ratio" );
	Cmd_AddCommand( "touch_set_stroke", Touch_Stroke_f, "set global stroke width and color" );
	Cmd_AddCommand( "touch_setclientonly", Touch_SetClientOnly_f, "when 1, only client buttons are shown" );
	Cmd_AddRestrictedCommand( "touch_reloadconfig", Touch_ReloadConfig_f, "load config, not saving changes" );
	Cmd_AddRestrictedCommand( "touch_writeconfig", Touch_WriteConfig, "save current config" );
	Cmd_AddRestrictedCommand( "touch_deleteprofile", Touch_DeleteProfile_f, "delete profile by name" );
	Cmd_AddRestrictedCommand( "touch_generate_code", Touch_GenerateCode_f, "create code sample for mobility API" );
	Cmd_AddCommand( "touch_fade", Touch_Fade_f, "start fade animation for selected buttons" );
	Cmd_AddRestrictedCommand( "touch_toggleselection", Touch_ToggleSelection_f, "toggle vidibility on selected button in editor" );
	Cmd_AddRestrictedCommand( "touch_aspectratio", Touch_ConfigAspectRatio_f, "set current aspect ratio" );

	// not saved, just runtime state for scripting
	Cvar_RegisterVariable( &touch_in_menu );

	// sensitivity configuration
	Cvar_RegisterVariable( &touch_forwardzone );
	Cvar_RegisterVariable( &touch_sidezone );
	Cvar_RegisterVariable( &touch_pitch );
	Cvar_RegisterVariable( &touch_yaw );
	Cvar_RegisterVariable( &touch_nonlinear_look );
	Cvar_RegisterVariable( &touch_pow_factor );
	Cvar_RegisterVariable( &touch_pow_mult );
	Cvar_RegisterVariable( &touch_exp_mult );

	// touch.cfg
	Cvar_RegisterVariable( &touch_grid_count );
	Cvar_RegisterVariable( &touch_grid_enable );
	Cvar_RegisterVariable( &touch_config_file );
	Cvar_RegisterVariable( &touch_precise_amount );
	Cvar_RegisterVariable( &touch_highlight_r );
	Cvar_RegisterVariable( &touch_highlight_g );
	Cvar_RegisterVariable( &touch_highlight_b );
	Cvar_RegisterVariable( &touch_highlight_a );
	Cvar_RegisterVariable( &touch_dpad_radius );
	Cvar_RegisterVariable( &touch_joy_radius );
	Cvar_RegisterVariable( &touch_move_indicator );
	Cvar_RegisterVariable( &touch_joy_texture );

	// input devices cvar
	Cvar_RegisterVariable( &touch_enable );
	Cvar_RegisterVariable( &touch_emulate );

	// TODO: touch platform
#if SDL_VERSION_ATLEAST( 2, 0, 10 )
	SDL_SetHint( SDL_HINT_MOUSE_TOUCH_EVENTS, "0" );
	SDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, "0" );
#elif defined(SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH)
	SDL_SetHint( SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH, "1" );
#endif

	touch.initialized = true;
}

//int pfnGetScreenInfo( SCREENINFO *pscrinfo );
static void Touch_InitConfig( void )
{
	if( !touch.initialized )
		return;

	if( !host.config_executed )
		return;

	if( touch.config_loaded )
		return;

	/// TODO: hud font
	//pfnGetScreenInfo( NULL ); //HACK: update hud screen parameters like iHeight
	if( FS_FileExists( touch_config_file.string, true ) )
	{
		Cbuf_AddTextf( "exec \"%s\"\n", touch_config_file.string );
		Cbuf_Execute();
	}
	else
	{
		Touch_LoadDefaults_f();
	}

	Touch_InitEditor();
	touch.joytexture = ref.dllFuncs.GL_LoadTexture( touch_joy_texture.string, NULL, 0, TF_NOMIPMAP );
	touch.whitetexture = R_GetBuiltinTexture( REF_WHITE_TEXTURE );
	touch.configchanged = false;
	touch.config_loaded = true;
}

/*
============================================================================

                     TOUCH CONTROLS RENDERING

============================================================================
*/

static qboolean Touch_IsVisible( touch_button_t *button )
{
	if( !FBitSet( button->flags, TOUCH_FL_CLIENT ) && touch.clientonly )
		return false; // skip nonclient buttons in clientonly mode

	if( touch.state >= state_edit )
		return true; //!!! Draw when editor is open

	if( FBitSet( button->flags, TOUCH_FL_HIDE ))
		return false; // skip hidden

	if( FBitSet( button->flags, TOUCH_FL_SP ) && CL_GetMaxClients() != 1 )
		return false; // skip singleplayer(load, save) buttons in multiplayer

	if( FBitSet( button->flags, TOUCH_FL_MP ) && CL_GetMaxClients() == 1 )
		return false; // skip multiplayer buttons in singleplayer

	return true;
}

static void Touch_DrawTexture ( float x1, float y1, float x2, float y2, int texture, byte r, byte g, byte b, byte a )
{
	if( x1 >= x2 )
		return;

	if( y1 >= y2 )
		return;

	ref.dllFuncs.Color4ub( r, g, b, a );
	ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(x1),
		TO_SCRN_Y(y1),
		TO_SCRN_X(x2 - x1),
		TO_SCRN_Y(y2 - y1),
		0, 0, 1, 1, texture );
}

#define GRID_COUNT_X ((int)touch_grid_count.value)
#define GRID_COUNT_Y (((int)touch_grid_count.value) * Touch_AspectRatio())
#define GRID_X (1.0f/GRID_COUNT_X)
#define GRID_Y (1.0f/Touch_AspectRatio()/GRID_COUNT_X)
#define GRID_ROUND_X(x) ((float)round( x * GRID_COUNT_X ) / GRID_COUNT_X)
#define GRID_ROUND_Y(x) ((float)round( x * GRID_COUNT_Y ) / GRID_COUNT_Y)

static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2  )
{
	/// TODO: grid check here
	if( *x2 - *x1 < GRID_X * 2 )
		*x2 = *x1 + GRID_X * 2;
	if( *y2 - *y1 < GRID_Y * 2)
		*y2 = *y1 + GRID_Y * 2;
	if( *x1 < 0 )
		*x2 -= *x1, *x1 = 0;
	if( *y1 < 0 )
		*y2 -= *y1, *y1 = 0;
	if( *y2 > 1 )
		*y1 -= *y2 - 1, *y2 = 1;
	if( *x2 > 1 )
		*x1 -= *x2 - 1, *x2 = 1;
	if( touch_grid_enable.value )
	{
		*x1 = GRID_ROUND_X( *x1 );
		*x2 = GRID_ROUND_X( *x2 );
		*y1 = GRID_ROUND_Y( *y1 );
		*y2 = GRID_ROUND_Y( *y2 );
	}
}

static float Touch_DrawCharacter( float x, float y, int number, float size )
{
	float	s1, s2, t1, t2, width, height;
	int	w, h;
	wrect_t *prc;
	if( !cls.creditsFont.valid )
		return 0;

	number &= 255;
	number = Con_UtfProcessChar( number );

	R_GetTextureParms( &w, &h, cls.creditsFont.hFontTexture );
	prc = &cls.creditsFont.fontRc[number];

	s1 = ((float)prc->left) / (float)w;
	t1 = ((float)prc->top) / (float)h;
	s2 = ((float)prc->right) / (float)w;
	t2 = ((float)prc->bottom) / (float)h;

	width = ((float)( prc->right - prc->left )) / 1024.0f * size;
	height = ((float)( prc->bottom - prc->top )) / 1024.0f * size;

	ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(x), TO_SCRN_Y(y), TO_SCRN_X(width), TO_SCRN_X(height), s1, t1, s2, t2, cls.creditsFont.hFontTexture );
	return width;
}

static float Touch_DrawText( float x1, float y1, float x2, float y2, const char *s, byte *color, float size )
{
	float x = x1;
	float maxy = y2;
	float maxx;
	if( x2 )
		maxx = x2 - cls.creditsFont.charWidths['M'] / 1024.0f * size;
	else
		maxx = 1;

	if( !cls.creditsFont.valid )
		return GRID_X * 2;
	Con_UtfProcessChar( 0 );

	ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd );

	// text is additive and alpha does not work
	ref.dllFuncs.Color4ub( color[0] * ( (float)color[3] /255.0f ), color[1] * ( (float)color[3] /255.0f ),
			color[2] * ( (float)color[3] /255.0f ), 255 );

	while( *s )
	{
		while( *s && ( *s != '\n' ) && ( *s != ';' ) && ( x1 < maxx ) )
			x1 += Touch_DrawCharacter( x1, y1, *s++, size );
		y1 += cls.creditsFont.charHeight / 1024.f * size / Touch_AspectRatio();

		if( y1 >= maxy )
			break;

		if( *s == '\n' || *s == ';' )
			s++;
		x1 = x;
	}
	ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );
	return x1;
}

static void Touch_DrawButtons( touchbuttonlist_t *list )
{
	touch_button_t *button;

	for( button = list->first; button; button = button->next )
	{
		if( Touch_IsVisible( button ) )
		{
			rgba_t color;
			MakeRGBA( color, B( color[0] ), B( color[1] ), B( color[2] ), B( color[3] ) );

			if( B( fadespeed ) )
			{
				button->fade += B( fadespeed ) * host.frametime;
				button->fade = bound( 0, B(fade), 1 );
				if( ( B( fade ) == 0 ) || ( B(fade) == 1 ) )
					B( fadespeed ) = 0;
				if( ( ( B( fade ) >= B( fadeend ) ) && ( B( fadespeed ) > 0 ) ) ||
					( ( B( fade ) <= B( fadeend ) ) && ( B( fadespeed ) < 0 ) ) )
					B( fadespeed ) = 0, B( fade ) = B( fadeend ) ;
			}

			if( ( B( finger ) != -1 ) && !FBitSet( B( flags ), TOUCH_FL_CLIENT ) )
			{
				color[0] = bound( 0,(float) color[0] * touch_highlight_r.value, 255 );
				color[1] = bound( 0,(float) color[1] * touch_highlight_g.value, 255 );
				color[2] = bound( 0,(float) color[2] * touch_highlight_b.value, 255 );
				color[3] = bound( 0,(float) color[3] * touch_highlight_a.value, 255 );
			}

			color[3] *= B( fade );
			if( button->texturefile[0] == '#' )
				Touch_DrawText( touch.swidth/SCR_W + B(x1), touch.swidth/SCR_H + B(y1), B(x2), B(y2), button->texturefile + 1, color, B( aspect )?B(aspect):1 );
			else if( button->texturefile[0] )
			{
				if( button->texture == -1 )
				{
					button->texture = ref.dllFuncs.GL_LoadTexture( button->texturefile, NULL, 0, TF_NOMIPMAP );
				}

				if( FBitSet( B(flags), TOUCH_FL_DRAW_ADDITIVE ))
					ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd );

				Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), B(texture), color[0], color[1], color[2], color[3] );

				ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );
			}
			if( FBitSet( B(flags), TOUCH_FL_STROKE ))
			{
				ref.dllFuncs.Color4ub( touch.scolor[0], touch.scolor[1], touch.scolor[2], touch.scolor[3] * B( fade ) );
				ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(B(x1)),
					TO_SCRN_Y(B(y1)),
					touch.swidth,
					TO_SCRN_Y(B(y2)-B(y1)) - touch.swidth,
					0, 0, 1, 1, touch.whitetexture );
				ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(B(x1)) + touch.swidth,
					TO_SCRN_Y(B(y1)),
					TO_SCRN_X(B(x2)-B(x1)) - touch.swidth,
					touch.swidth,
					0, 0, 1, 1, touch.whitetexture );
				ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(B(x2))-touch.swidth,
					TO_SCRN_Y(B(y1)) + touch.swidth,
					touch.swidth,
					TO_SCRN_Y(B(y2)-B(y1)) - touch.swidth,
					0, 0, 1, 1, touch.whitetexture );
				ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(B(x1)),
					TO_SCRN_Y(B(y2))-touch.swidth,
					TO_SCRN_X(B(x2)-B(x1)) - touch.swidth,
					touch.swidth,
					0, 0, 1, 1, touch.whitetexture );
				ref.dllFuncs.Color4ub( 255, 255, 255, 255 );
			}
		}
		if( touch.state >= state_edit && !( button->flags & TOUCH_FL_NOEDIT )  )
		{
			rgba_t color;
			if( !FBitSet( button->flags, TOUCH_FL_HIDE ) )
				Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), touch.whitetexture, 255, 255, 0, 32 );
			else
				Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), touch.whitetexture, 128, 128, 128, 128 );
			MakeRGBA( color, 255, 255,127, 255 );
			Con_DrawString( TO_SCRN_X( B(x1) ), TO_SCRN_Y( B(y1) ), B(name), color );
		}
	}

}

void Touch_Draw( void )
{
	touch_button_t *button;

	if( !touch.initialized || ( !touch_enable.value && !touch.clientonly ))
		return;

	if( cls.key_dest != key_game && !touch_in_menu.value )
		return;

	Touch_InitConfig();


	ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );

	if( touch.state >= state_edit && touch_grid_enable.value )
	{
		float x;
		if( touch_in_menu.value )
			Touch_DrawTexture( 0, 0, 1, 1, touch.whitetexture, 32, 32, 32, 255 );
		else
			Touch_DrawTexture( 0, 0, 1, 1, touch.whitetexture, 0, 0, 0, 112 );
		ref.dllFuncs.Color4ub( 0, 224, 224, 112 );
		for( x = 0; x < 1 ; x += GRID_X )
			ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X(x),
				0,
				1,
				TO_SCRN_Y(1),
				0, 0, 1, 1, touch.whitetexture );
		for( x = 0; x < 1 ; x += GRID_Y )
			ref.dllFuncs.R_DrawStretchPic( 0,
				TO_SCRN_Y(x),
				TO_SCRN_X(1),
				1,
				0, 0, 1, 1, touch.whitetexture );
	}

	Touch_DrawButtons( &touch.list_user );

	if( touch.state >= state_edit )
	{
		rgba_t color;

		MakeRGBA( color, 255, 255, 255, 255 );

		if( touch.edit )
		{
			float	x1 = touch.edit->x1,
					y1 = touch.edit->y1,
					x2 = touch.edit->x2,
					y2 = touch.edit->y2;
			IN_TouchCheckCoords( &x1, &y1, &x2, &y2 );
			Touch_DrawTexture( x1, y1, x2, y2, touch.whitetexture, 0, 255, 0, 32 );
		}

		Touch_DrawTexture( 0, 0, GRID_X, GRID_Y, touch.whitetexture, 255, 255, 255, 64 );


		if( touch.showeditbuttons )
			Touch_DrawButtons( &touch.list_edit );

		/// TODO: move to mainui
		if( touch.selection )
		{
			button = touch.selection;
			Touch_DrawTexture( B(x1), B(y1), B(x2), B(y2), touch.whitetexture, 255, 0, 0, 64 );

			Con_DrawString( 0, TO_SCRN_Y(GRID_Y * 11), "Selection:", color );
			Con_DrawString( Con_DrawString( 0, TO_SCRN_Y(GRID_Y*12), "Name: ", color ),
											   TO_SCRN_Y(GRID_Y*12), B(name), color );
			Con_DrawString( Con_DrawString( 0, TO_SCRN_Y(GRID_Y*13), "Texture: ", color ),
											   TO_SCRN_Y(GRID_Y*13), B(texturefile), color );
			Con_DrawString( Con_DrawString( 0, TO_SCRN_Y(GRID_Y*14), "Command: ", color ),
											   TO_SCRN_Y(GRID_Y*14), B(command), color );
		}
	}

	ref.dllFuncs.Color4ub( 255, 255, 255, 255 );

	if( ( touch.move_finger != -1 ) && touch.move_button && touch_move_indicator.value )
	{
		float width;
		float height;
		if( FBitSet( touch_joy_texture.flags, FCVAR_CHANGED ) )
		{
			ClearBits( touch_joy_texture.flags, FCVAR_CHANGED );
			touch.joytexture = ref.dllFuncs.GL_LoadTexture( touch_joy_texture.string, NULL, 0, TF_NOMIPMAP );
		}
		if( touch.move_button->type == touch_move )
		{
			width =  touch_sidezone.value;
			height = touch_forwardzone.value;
		}
		else
		{
			width = (touch.move_button->x2 - touch.move_button->x1)/2;
			height = (touch.move_button->y2 - touch.move_button->y1)/2;
		}
		ref.dllFuncs.Color4ub( 255, 255, 255, 128 );
		ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X( touch.move_start_x - GRID_X * touch_move_indicator.value ),
						  TO_SCRN_Y( touch.move_start_y - GRID_Y * touch_move_indicator.value ),
						  TO_SCRN_X( GRID_X * 2 * touch_move_indicator.value ), TO_SCRN_Y( GRID_Y * 2 * touch_move_indicator.value ), 0, 0, 1, 1, touch.joytexture );
		ref.dllFuncs.Color4ub( 255, 255, 255, 255 );
		ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X( touch.move_start_x + touch.side * width - GRID_X * touch_move_indicator.value ),
						  TO_SCRN_Y( touch.move_start_y - touch.forward * height - GRID_Y * touch_move_indicator.value ),
						  TO_SCRN_X( GRID_X * 2 * touch_move_indicator.value ), TO_SCRN_Y( GRID_Y * 2 * touch_move_indicator.value ), 0, 0, 1, 1, touch.joytexture );

	}

}

// clear move and selection state
static void IN_TouchEditClear( void )
{
	// allow keep move/look fingers when doing touch_removeall
	//touch.move_finger = touch.look_finger = -1;

	if( touch.state < state_edit )
		return;

	touch.state = state_edit;

	if( touch.edit )
		touch.edit->finger = -1;

	touch.resize_finger = -1;
	touch.edit = NULL;
	touch.selection = NULL;
}

static void Touch_EditMove( touchEventType type, int fingerID, float x, float y, float dx, float dy )
{
	if( touch.edit->finger == fingerID )
	{
		if( type == event_up ) // shutdown button move
		{
			touch_button_t *button = touch.edit;
			IN_TouchCheckCoords( &B(x1), &B(y1), &B(x2), &B(y2) );
			IN_TouchEditClear();
			if( button->type == touch_command )
			{
				touch.selection = button;

				// update "hide" editor button
				touch.hidebutton->texture = -1;
				touch.hidebutton->flags &= ~TOUCH_FL_HIDE;

				if( FBitSet( button->flags, TOUCH_FL_HIDE ))
					Q_strncpy( touch.hidebutton->texturefile, "touch_default/edit_show", sizeof( touch.hidebutton->texturefile ));
				else
					Q_strncpy( touch.hidebutton->texturefile, "touch_default/edit_hide", sizeof( touch.hidebutton->texturefile ));
			}
		}
		if( type == event_motion ) // shutdown button move
		{
			touch.edit->y1 += dy;
			touch.edit->y2 += dy;
			touch.edit->x1 += dx;
			touch.edit->x2 += dx;
		}
	}
	else
	{
		if( type == event_down ) // enable resizing
		{
			if( touch.resize_finger == -1 )
			{
				touch.resize_finger = fingerID;
			}
		}
		if( type == event_up ) // disable resizing
		{
			if( touch.resize_finger == fingerID )
			{
				touch.resize_finger = -1;
			}
		}
		if( type == event_motion ) // perform resizing
		{
			if( touch.resize_finger == fingerID )
			{
				touch.edit->y2 += dy;
				touch.edit->x2 += dx;
			}
		}
	}
}

static void Touch_Motion( touchEventType type, int fingerID, float x, float y, float dx, float dy )
{
	// process wheel
	if( fingerID == touch.wheel_finger )
	{
		touch.wheel_amount += touch.wheel_horizontal ? dx : dy;

		if( touch.wheel_amount > 0.1f )
		{
			Cbuf_AddText( touch.wheel_down );
			touch.wheel_count++;
			touch.wheel_amount = 0;
		}
		if( touch.wheel_amount < -0.1f )
		{
			Cbuf_AddText( touch.wheel_up );
			touch.wheel_count++;
			touch.wheel_amount = 0;
		}
		return;
	}

	// walk
	if( fingerID == touch.move_finger )
	{
		// check bounds
		if( touch_forwardzone.value <= 0 )
			Cvar_SetValue( "touch_forwardzone", 0.5 );
		if( touch_sidezone.value <= 0 )
			Cvar_SetValue( "touch_sidezone", 0.3 );

		if( !touch.move_button || touch.move_button->type == touch_move )
		{
			// move relative to touch start
			touch.forward = ( touch.move_start_y - y ) / touch_forwardzone.value;
			touch.side = ( x - touch.move_start_x ) / touch_sidezone.value;
		}
		else if( touch.move_button->type == touch_joy )
		{
			// move relative to joy center
			touch.forward = ( ( touch.move_button->y2 + touch.move_button->y1 ) - y * 2 ) / ( touch.move_button->y2 - touch.move_button->y1 ) * touch_joy_radius.value;
			touch.side = ( x * 2 - ( touch.move_button->x2 + touch.move_button->x1 ) ) / ( touch.move_button->x2 - touch.move_button->x1 ) * touch_joy_radius.value;
		}
		else if( touch.move_button->type == touch_dpad )
		{
			// like joy, but without acceleration. useful for bhop
			touch.forward = round( ( (touch.move_button->y2 + touch.move_button->y1) - y * 2 ) / ( touch.move_button->y2 - touch.move_button->y1 ) * touch_dpad_radius.value );
			touch.side = round( ( x * 2 - (touch.move_button->x2 + touch.move_button->x1) ) / ( touch.move_button->x2 - touch.move_button->x1 ) * touch_dpad_radius.value );
		}

		touch.forward = bound( -1, touch.forward, 1 );
		touch.side = bound( -1, touch.side, 1 );
	}

	// process look
	if( fingerID == touch.look_finger )
	{
		if( touch.precision )
			dx *= touch_precise_amount.value, dy *= touch_precise_amount.value;

		if( touch_nonlinear_look.value )
		{
			float dabs, dcos, dsin;

			// save angle, modify only velocity
			dabs = sqrt( dx * dx + dy * dy );

			if( dabs < 0.000001f )
				return; // no motion, avoid division by zero

			dcos = dx / dabs;
			dsin = dy / dabs;

			if( touch_exp_mult.value > 1 )
				dabs = ( exp( dabs * touch_exp_mult.value ) - 1 ) / touch_exp_mult.value;

			if( touch_pow_mult.value > 1 && touch_pow_factor.value > 1 )
				dabs = pow( dabs * touch_pow_mult.value, touch_pow_factor.value ) / touch_pow_mult.value;

			dx = dabs * dcos;
			dy = dabs * dsin;
		}

		// prevent breaking engine/client with bad values
		if( IS_NAN( dx ) || IS_NAN( dy ) )
			return;

		// accumulate
		touch.yaw -= dx * touch_yaw.value, touch.pitch += dy * touch_pitch.value;
	}
}

static qboolean Touch_ButtonPress( touchbuttonlist_t *list, touchEventType type, int fingerID, float x, float y, float dx, float dy )
{
	touch_button_t *button;
	qboolean result = false;

	// run from end(front) to start(back)
	for( button = list->last; button; button = button->prev )
	{
		// skip invisible buttons
		if( !Touch_IsVisible( button ) )
			continue;

		if( type == event_down )
		{
			// button bounds check
			if( ( x > button->x1 &&
				 x < button->x2 ) &&
				( y < button->y2 &&
				  y > button->y1 ) )
			{
				button->finger = fingerID;

				if( button->type == touch_command )
				{
					char command[256];

					// command down: just execute command
					Q_snprintf( command, sizeof( command ), "%s\n", button->command );
					if( FBitSet( B( flags ), TOUCH_FL_UNPRIVILEGED ))
						Cbuf_AddFilteredText( command );
					else Cbuf_AddText( command );

					// increase precision
					if( FBitSet( B(flags), TOUCH_FL_PRECISION ))
						touch.precision = true;

					result = true;
				}

				if( button->type == touch_wheel )
				{
					string command;
					touch.wheel_finger = fingerID;
					touch.wheel_amount = touch.wheel_count = 0;

					Cmd_TokenizeString( button->command );

					touch.wheel_horizontal = !Q_strcmp( Cmd_Argv( 0 ), "_hwheel" );

					Q_snprintf( touch.wheel_up, sizeof( touch.wheel_up ), "%s\n", Cmd_Argv( 1 ) );
					Q_snprintf( touch.wheel_down, sizeof( touch.wheel_down ), "%s\n", Cmd_Argv( 2 ) );
					Q_snprintf( touch.wheel_end, sizeof( touch.wheel_end ), "%s\n", Cmd_Argv( 3 ) );
					if( Q_snprintf( command, sizeof( command ), "%s\n", Cmd_Argv( 4 ) ) > 1)
					{
						if( FBitSet( B( flags ), TOUCH_FL_UNPRIVILEGED ))
							Cbuf_AddFilteredText( command );
						else Cbuf_AddText( command );
						touch.wheel_count++;
					}

					// increase precision
					if( FBitSet( B(flags), TOUCH_FL_PRECISION ))
						touch.precision = true;

					result = true;
				}

				// initialize motion when player touched motion zone
				if( button->type == touch_move || button->type == touch_joy || button->type == touch_dpad  )
				{
					if( touch.move_finger !=-1 )
					{
						// prevent initializing move while already moving
						// revert finger switch, leave first finger
						button->finger = touch.move_finger;
						continue;
					}

					result = true;

					if( touch.look_finger == fingerID )
					{
						touch_button_t *newbutton;

						// this is an error, try recover
						touch.move_finger = touch.look_finger = -1;

						// player touched touch_move with enabled look mode
						// and same finger id. release all move triggers
						for( newbutton = list->first; newbutton; newbutton = newbutton->next )
							if( ( newbutton->type == touch_move ) || ( newbutton->type == touch_look ) ) newbutton->finger = -1;

						Con_DPrintf( S_ERROR "Touch: touch_move on look finger %d!\n", fingerID );
						continue;
					}

					// initialize move mode
					touch.move_finger = fingerID;
					touch.move_button = button;

					if( touch.move_button->type == touch_move )
					{
						// initial position is first touch
						touch.move_start_x = x;
						touch.move_start_y = y;
					}
					else if( touch.move_button->type == touch_joy )
					{
						// initial position is button center
						touch.move_start_y = (touch.move_button->y2 + touch.move_button->y1) / 2;
						touch.move_start_x = (touch.move_button->x2 + touch.move_button->x1) / 2;

						// start move instanly
						touch.forward = ((touch.move_button->y2 + touch.move_button->y1) - y * 2) / (touch.move_button->y2 - touch.move_button->y1);
						touch.side = (x * 2 - (touch.move_button->x2 + touch.move_button->x1)) / (touch.move_button->x2 - touch.move_button->x1);
					}
					else if( touch.move_button->type == touch_dpad )
					{
						// dame as joy, but round
						touch.move_start_y = (touch.move_button->y2 + touch.move_button->y1) / 2;
						touch.move_start_x = (touch.move_button->x2 + touch.move_button->x1) / 2;

						// start move instanly
						touch.forward = round(((touch.move_button->y2 + touch.move_button->y1) - y * 2) / (touch.move_button->y2 - touch.move_button->y1));
						touch.side = round((x * 2 - (touch.move_button->x2 + touch.move_button->x1)) / (touch.move_button->x2 - touch.move_button->x1));
					}
				}

				// initialize look
				if( button->type == touch_look )
				{
					if( touch.look_finger !=-1 )
					{
						// prevent initializing look while already looking
						// revert finger switch, leave first finger
						button->finger = touch.look_finger;
						continue;
					}

					result = true;

					if( touch.move_finger == fingerID )
					{
						touch_button_t *newbutton;

						// this is an error, try recover
						touch.move_finger = touch.look_finger = -1;

						// player touched touch_move with enabled look mode
						// and same finger id. release all move triggers
						for( newbutton = list->first; newbutton; newbutton = newbutton->next )
							if( ( newbutton->type == touch_move ) || ( newbutton->type == touch_look ) ) newbutton->finger = -1;

						Con_Printf( S_ERROR "touch: touch_look on move finger %d!\n", fingerID );
						continue;
					}

					touch.look_finger = fingerID;
				}
			}
		}

		if( type == event_up )
		{
			// no bounds check here.
			// button released when finger released
			if( fingerID == button->finger )
			{
				button->finger = -1;

				// handle +command, replace by -command
				if( button->type == touch_command )
				{
					if( button->command[0] == '+' )
					{
						char command[256];

						Q_snprintf( command, sizeof( command ), "%s\n", button->command );

						command[0] = '-';

						if( FBitSet( B( flags ), TOUCH_FL_UNPRIVILEGED ))
							Cbuf_AddFilteredText( command );
						else Cbuf_AddText( command );
					}

					// disable precision mode
					if( FBitSet( B(flags), TOUCH_FL_PRECISION ))
						touch.precision = false;

					result = true;
				}

				// handle wheel end
				if( button->type == touch_wheel )
				{
					if( touch.wheel_count )
					{
						if( FBitSet( B( flags ), TOUCH_FL_UNPRIVILEGED ))
							Cbuf_AddFilteredText( touch.wheel_end );
						else Cbuf_AddText( touch.wheel_end );
					}

					// disable precision mode
					if( B(flags) & TOUCH_FL_PRECISION )
						touch.precision = false;

					touch.wheel_finger = -1;

					result = true;
				}

				// release motion buttons
				if( button->type == touch_move || button->type == touch_joy || button->type == touch_dpad )
				{
					touch.move_finger = -1;
					touch.forward = touch.side = 0;
					touch.move_button = NULL;
				}

				// release look buttons
				if( button->type == touch_look )
				{
					touch.look_finger = -1;
				}
			}
		}
	}

	return result;
}

static qboolean Touch_ButtonEdit( touchEventType type, int fingerID, float x, float y, float dx, float dy )
{
	touch_button_t *button;

	// edit buttons are on y1
	if( type == event_down )
	{
		if( (x < GRID_X) && (y < GRID_Y) )
		{
			touch.showeditbuttons ^= true;
			return true;
		}

		if( touch.showeditbuttons && Touch_ButtonPress( &touch.list_edit, type, fingerID, x, y, dx, dy ) )
			return true;
	}

	// run from end(front) to start(back)
	for( button = touch.list_user.last; button; button = button->prev )
	{
		if( type == event_down )
		{
			if( ( x > button->x1 &&
				 x < button->x2 ) &&
				( y < button->y2 &&
				  y > button->y1 ) )
			{
				button->finger = fingerID;

				// do not edit NOEDIT buttons
				if( FBitSet( button->flags, TOUCH_FL_NOEDIT ))
						continue;

				touch.edit = button;
				touch.selection = NULL;

				// make button last to bring it up
				if( ( button->next ) && ( button->type == touch_command ) )
				{
					if( button->prev )
						button->prev->next = button->next;
					else
						touch.list_user.first = button->next;

					button->next->prev = button->prev;
					touch.list_user.last->next = button;
					button->prev = touch.list_user.last;
					button->next = NULL;
					touch.list_user.last = button;
				}
				touch.state = state_edit_move;
				return true;
			}
		}
		if( type == event_up )
			if( fingerID == button->finger )
				button->finger = -1;
	}

	if( type == event_down )
	{
		touch.selection = NULL;
		touch.hidebutton->flags |= TOUCH_FL_HIDE;
	}

	return false;
}

static int Touch_ControlsEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy )
{
	if( touch.state == state_edit_move )
	{
		Touch_EditMove( type, fingerID, x, y, dx, dy );
		return 1;
	}

	if( touch.state == state_edit && Touch_ButtonEdit( type, fingerID, x, y, dx, dy ) )
		return true;
	if( Touch_ButtonPress( &touch.list_user, type, fingerID, x, y, dx, dy ) )
		return true;
	if( type == event_motion )
		Touch_Motion( type, fingerID, x, y, dx, dy );
	return true;
}

int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy )
{
	y *= SCR_H/SCR_W/Touch_AspectRatio();
//	Con_Printf("%f %f\n", TO_SCRN_X(x), TO_SCRN_Y(y));
	// simulate menu mouse click
	if( cls.key_dest != key_game && !touch_in_menu.value )
	{
		touch.move_finger = touch.resize_finger = touch.look_finger = -1;
		// Hack for keyboard, hope it help
		// a1ba: this is absolutely horrible
		if( cls.key_dest == key_console || cls.key_dest == key_message )
		{
			static float x1 = 0.0f;
			x1 += dx;

			if( type == event_up ) // don't show keyboard on every tap
			{
				Key_EnableTextInput( true, true );
				x1 = 0.0f;
			}

			if( cls.key_dest == key_console )
			{
				static float y1 = 0;
				y1 += dy;
				if( dy > 0.4f )
					Con_Bottom();
				if( y1 > 0.01f )
				{
					Con_PageUp( 1 );
					y1 = 0;
				}
				if( y1 < -0.01f )
				{
					Con_PageDown( 1 );
					y1 = 0;
				}
			}

			// exit of console area
			if( type == event_down && x < 0.1f && y > 0.9f )
				Cbuf_AddText( "escape\n" );

			// swipe from edge to exit console/chat
			if(( x > 0.8f && x1 < -0.1f ) || ( x < 0.2f && x1 > 0.1f ))
			{
				Cbuf_AddText( "escape\n" );
				x1 = 0.0f;
			}
		}
		UI_MouseMove( TO_SCRN_X(x), TO_SCRN_Y(y) );
		//MsgDev( D_NOTE, "touch %d %d\n", TO_SCRN_X(x), TO_SCRN_Y(y) );
		if( type == event_down )
			Key_Event( K_MOUSE1, true );
		if( type == event_up )
			Key_Event( K_MOUSE1, false );
		return 0;
	}


	if( VGui_IsActive() )
	{
		VGui_MouseMove( TO_SCRN_X(x), TO_SCRN_Y(y) );

		switch( type )
		{
		case event_down:
			VGui_MouseEvent( K_MOUSE1, 1 );
			break;
		case event_up:
			VGui_MouseEvent( K_MOUSE1, 0 );
			break;
		default: break;
		}
	}

	if( !touch.initialized || ( !touch_enable.value && !touch.clientonly ))
		return 0;

	if( clgame.dllFuncs.pfnTouchEvent && clgame.dllFuncs.pfnTouchEvent( type, fingerID, x, y, dx, dy ) )
		return true;

	return Touch_ControlsEvent( type, fingerID, x, y, dx, dy );
}

void Touch_GetMove( float *forward, float *side, float *yaw, float *pitch )
{
	*forward += touch.forward;
	*side += touch.side;
	*yaw += touch.yaw;
	*pitch += touch.pitch;
	touch.yaw = touch.pitch = 0;
}

void Touch_KeyEvent( int key, int down )
{
	static float lx, ly;
	static int kidNamedFinger = -1;
	touchEventType event;
	float x, y;
	int finger, xi, yi;

	if( !touch_emulate.value )
	{
		if( touch_enable.value )
			return;

		if( !touch.clientonly )
			return;
	}

	if( !key )
	{
		if( kidNamedFinger < 0 )
			return;

		finger = kidNamedFinger;
		event  = event_motion;
	}
	else
	{
		finger = key == K_MOUSE1 ? 0 : 1;
		if( down )
		{
			event = event_down;
			kidNamedFinger = finger;
		}
		else
		{
			event = event_up;
			kidNamedFinger = -1;
		}
	}

	// don't deactivate mouse in game
	// checking a case when mouse and touchscreen
	// can be used simultaneously
	Platform_SetCursorType( dc_arrow );
	Platform_GetMousePos( &xi, &yi );

	x = xi / SCR_W;
	y = yi / SCR_H;

	Con_DPrintf( "event %d %.2f %.2f %.2f %.2f\n",
		event, x, y, x - lx, y - ly );

	IN_TouchEvent( event, finger, x, y, x - lx, y - ly );

	lx = x;
	ly = y;
}

qboolean Touch_WantVisibleCursor( void )
{
	return ( touch_enable.value && touch_emulate.value ) || touch.clientonly;
}

void Touch_Shutdown( void )
{
	if( !touch.initialized )
		return;
	Touch_RemoveAll_f();
	Cmd_RemoveCommand( "touch_addbutton" );
	Cmd_RemoveCommand( "touch_removebutton" );
	Cmd_RemoveCommand( "touch_enableedit" );
	Cmd_RemoveCommand( "touch_disableedit" );
	Cmd_RemoveCommand( "touch_settexture" );
	Cmd_RemoveCommand( "touch_setcolor" );
	Cmd_RemoveCommand( "touch_setcommand" );
	Cmd_RemoveCommand( "touch_setflags" );
	Cmd_RemoveCommand( "touch_show" );
	Cmd_RemoveCommand( "touch_hide" );
	Cmd_RemoveCommand( "touch_list" );
	Cmd_RemoveCommand( "touch_removeall" );
	Cmd_RemoveCommand( "touch_loaddefaults" );
	Cmd_RemoveCommand( "touch_roundall" );
	Cmd_RemoveCommand( "touch_exportconfig" );
	Cmd_RemoveCommand( "touch_set_stroke" );
	Cmd_RemoveCommand( "touch_setclientonly" );
	Cmd_RemoveCommand( "touch_reloadconfig" );
	Cmd_RemoveCommand( "touch_writeconfig" );
	Cmd_RemoveCommand( "touch_generate_code" );

	touch.initialized = false;
	Mem_FreePool( &touch.mempool );
}