//	Zinc Application Framework - I_MOUSE.CPP
//	COPYRIGHT (C) 1990-1999.  All Rights Reserved.
//	Zinc Software Incorporated.  Pleasant Grove, Utah  USA

#include <z_mouse2.hpp> // Must be before z_i18n.hpp
#include <z_dsp.hpp>
#include <z_evtmgr.hpp>
#include <z_image.hpp>
#include <z_string.hpp> // For strcmp().
#include <z_sysevt.hpp>
#include <z_utils.hpp>

#if defined(USE_ZAF_MOUSE)
#define ZAF_MOUSE_INFO
#include "img_def.cpp"
#include "gbl_def.cpp"
#endif // USE_ZAF_MOUSE

#if defined(ZAF_MSDOS)
static bool _upToDate;
#	if !defined(USE_UGL_INPUT)
extern int _horizontalRatio;
extern int _verticalRatio;
#	endif
#endif // ZAF_MSDOS

// ----- ZafMouse ----------------------------------------------------------

#if defined(ZAF_GRAPHICS_MODE)
ZafMouseData **ZafMouse::imageTable = ZAF_NULLP(ZafMouseData *);
#endif // ZAF_GRAPHICS_MODE

#	if !defined(USE_UGL_INPUT)
int volatile _head, _tail;
#	endif

#if defined(USE_ZAF_MOUSE)
#	if defined(USE_UGL_INPUT) || (defined(ZAF_INDEPENDENT) && !defined(ZAF_MSDOS))
#include <uglInput.h>
UGL_INPUT_Q_ID InputQueueGet(void);
bool InputQueuePoll(ugl_input_event &event, ZafEventType type);
void InputQueueRelease();
#	endif
#	if defined(ZAF_GRAPHICS_MODE)
// Define mouse system resources.
static struct SYS_MOUSE
{
	ZafDeviceImage imageID;
	ZafMouseStruct *image;
} ZAF_FARDATA _mouseImage[] =
{
	{ DM_VIEW, &_view_ZafMouse },
	{ DM_SELECT, &_view_ZafMouse },
	{ DM_EDIT, &_edit_ZafMouse },
	{ DM_WAIT, &_wait_ZafMouse },
	{ DM_MOVE, &_move_ZafMouse },
	{ DM_LEFT_SIDE, &_horizontal_ZafMouse },
	{ DM_RIGHT_SIDE, &_horizontal_ZafMouse },
	{ DM_TOP_SIDE, &_vertical_ZafMouse },
	{ DM_BOTTOM_SIDE, &_vertical_ZafMouse },
	{ DM_TOP_LEFT_CORNER, &_left_ZafMouse },
	{ DM_BOTTOM_RIGHT_CORNER, &_left_ZafMouse },
	{ DM_TOP_RIGHT_CORNER, &_right_ZafMouse },
	{ DM_BOTTOM_LEFT_CORNER, &_right_ZafMouse },
	{ DM_CROSS_HAIRS, &_crossHairs_ZafMouse },
	{ DM_CANCEL, &_cancel_ZafMouse },
	{ DM_DRAG_MOVE, &_dragMove_ZafMouse },
	{ DM_DRAG_COPY, &_dragCopy_ZafMouse },
	{ DM_DRAG_MOVE_MULTIPLE, &_dragMoveMultiple_ZafMouse },
	{ DM_DRAG_COPY_MULTIPLE, &_dragCopyMultiple_ZafMouse },
	{ DM_VERTICAL_SPLIT, &_vertical_split_ZafMouse },
	{ DM_HORIZONTAL_SPLIT, &_horizontal_split_ZafMouse },
	{ 0, 0 }
};
#	endif // ZAF_GRAPHICS_MODE

ZafMouse::ZafMouse(ZafDeviceState _state, ZafDeviceImage _imageType) :
	ZafDevice(E_MOUSE, _state), imageType(_imageType)
{
	OpenMouse();
	if (!installed)
		return;

#	if !defined(USE_UGL_INPUT)
	_head = _tail = 0;
#	endif

	// Check the image information.
	ImageAllocate();
	SetStringID(className);
	SetNumberID((ZafNumberID)classID);

	// Initialize the mouse information.
	position.column = position.line = 0;
	offset.column = offset.line = 0;
#	if defined(ZAF_GRAPHICS_MODE)
	position.coordinateType = offset.coordinateType = ZAF_PIXEL;
#	elif defined(ZAF_TEXT_MODE)
	position.coordinateType = offset.coordinateType = ZAF_CELL;
#	endif // ZAF_GRAPHICS_MODE
}

ZafMouse::~ZafMouse(void)
{
	// Restore the old mouse information.
	if (installed)
		CloseMouse();

	// Check the image information.
	ImageFree();

	installed = false;
}

ZafEventType ZafMouse::Event(const ZafEventStruct &event)
{
	// Make sure the mouse is installed.
	if (!installed)
		return (D_OFF);

	ZafDeviceState oldState = deviceState;
	ZafDeviceImage oldImage = imageType;
	ZafPositionStruct oldPosition = position;
	ZafEventType ccode = event.type;

	// Switch on the raw code.
	switch (ccode)
	{
	case D_INITIALIZE:
#	if defined(ZAF_MSDOS)
#	if !defined(USE_UGL_INPUT)
#		if defined(ZAF_GRAPHICS_MODE)
		_horizontalRatio = 1;
		_verticalRatio = 1;
#		elif defined(ZAF_TEXT_MODE)
		_horizontalRatio = display->columns == 40 ? 16 : 8;
		_verticalRatio = 8;
#		endif
#	endif // !USE_UGL_INPUT
#	endif // ZAF_MSDOS
		position.column = display->columns / 2;
		position.line = display->lines / 2;
		oldImage = 0;
		oldState = D_OFF;

		// Initialize the original image.
		Event(imageType);
		break;

	case S_DEINITIALIZE:
		{
			ZafRegionStruct clip;

			// Start with screen region and pare it down.
			clip.left = clip.top = 0;
			clip.right = display->columns - 1;
			clip.bottom = display->lines - 1;
#	if defined(ZAF_GRAPHICS_MODE)
			clip.coordinateType = ZAF_PIXEL;
#	elif defined(ZAF_TEXT_MODE)
			clip.coordinateType = ZAF_CELL;
#	endif // ZAF_GRAPHICS_MODE

			((ZafScreenDisplay *)display)->DeviceMove(ZAF_IMT_MOUSE_IMAGE, display->columns, display->lines, clip);
			deviceState = D_HIDE;
		}
		break;

	case D_POSITION:
		position = event.position;
		break;

	case DM_VIEW:
	case DM_SELECT:
	case DM_EDIT:
	case DM_WAIT:
	case DM_MOVE:
	case DM_LEFT_SIDE:
	case DM_RIGHT_SIDE:
	case DM_TOP_SIDE:
	case DM_BOTTOM_SIDE:
	case DM_TOP_LEFT_CORNER:
	case DM_TOP_RIGHT_CORNER:
	case DM_BOTTOM_LEFT_CORNER:
	case DM_BOTTOM_RIGHT_CORNER:
	case DM_CROSS_HAIRS:
	case DM_CANCEL:
	case DM_DRAG_MOVE:
	case DM_DRAG_COPY:
	case DM_DRAG_MOVE_MULTIPLE:
	case DM_DRAG_COPY_MULTIPLE:
	case DM_VERTICAL_SPLIT:
	case DM_HORIZONTAL_SPLIT:
		if (deviceState != D_OFF)
			deviceState = D_ON;
		imageType = ccode;
		break;

	case D_ON:
		{
			ZafRegionStruct clip;

			// Start with screen region and pare it down.
			clip.left = clip.top = 0;
			clip.right = display->columns - 1;
			clip.bottom = display->lines - 1;
#	if defined(ZAF_GRAPHICS_MODE)
			clip.coordinateType = ZAF_PIXEL;
#	elif defined(ZAF_TEXT_MODE)
			clip.coordinateType = ZAF_CELL;
#	endif // ZAF_GRAPHICS_MODE

			((ZafScreenDisplay *)display)->DeviceMove(ZAF_IMT_MOUSE_IMAGE, position.column - offset.column,
				position.line - offset.line, clip);

			if (deviceState == D_OFF)
			{
#	if !defined(USE_UGL_INPUT)
				_tail = _head;
#	endif
			}
			deviceState = D_ON;
		}
		break;

	case D_OFF:
		deviceState = D_OFF;
		break;

	case D_STATE:
		return ((deviceState == D_OFF) ? D_OFF : imageType);

	case D_HIDE:
		{
			ZafRegionStruct clip;

			// Start with screen region and pare it down.
			clip.left = clip.top = 0;
			clip.right = display->columns - 1;
			clip.bottom = display->lines - 1;
#	if defined(ZAF_GRAPHICS_MODE)
			clip.coordinateType = ZAF_PIXEL;
#	elif defined(ZAF_TEXT_MODE)
			clip.coordinateType = ZAF_CELL;
#	endif // ZAF_GRAPHICS_MODE

			((ZafScreenDisplay *)display)->DeviceMove(ZAF_IMT_MOUSE_IMAGE, display->columns, display->lines, clip);
			deviceState = event.type;
		}
		break;

	default:
		return (ccode);
	}

	// Reset the mouse information.
	if (oldImage == imageType && oldState == deviceState && oldPosition == position)
		return (ccode);
	if (oldPosition != position && deviceState != D_HIDE)
	{
		ZafRegionStruct clip;

		// Start with screen region and pare it down.
		clip.left = clip.top = 0;
		clip.right = display->columns - 1;
		clip.bottom = display->lines - 1;
#	if defined(ZAF_GRAPHICS_MODE)
		clip.coordinateType = ZAF_PIXEL;
#	elif defined(ZAF_TEXT_MODE)
		clip.coordinateType = ZAF_CELL;
#	endif // ZAF_GRAPHICS_MODE

#	if defined(ZAF_MSDOS)
		_upToDate =
#	endif // ZAF_MSDOS
		((ZafScreenDisplay *)display)->DeviceMove(ZAF_IMT_MOUSE_IMAGE,
			position.column - offset.column, position.line - offset.line, clip);
	}
	if (oldImage != imageType || oldState != deviceState && deviceState != D_HIDE)
	{
#	if defined(ZAF_GRAPHICS_MODE)
		int tableOffset = 0;
		for (tableOffset = 0; imageTable[tableOffset]; tableOffset++)
			if (imageTable[tableOffset]->NumberID() == imageType)
				break;

		offset.column = imageTable[tableOffset]->hotSpotX;
		offset.line = imageTable[tableOffset]->hotSpotY;

		ZafRegionStruct clip;
		clip.left = clip.top = 0;
		clip.right = display->columns - 1;
		clip.bottom = display->lines - 1;
		clip.coordinateType = ZAF_PIXEL;

#		if defined(ZAF_MSDOS)
		_upToDate =
#		endif // ZAF_MSDOS
		((ZafScreenDisplay *)display)->DeviceSet(ZAF_IMT_MOUSE_IMAGE,
			position.column - offset.column,
			position.line - offset.line, imageTable[tableOffset]->Width(),
			imageTable[tableOffset]->Height(), imageTable[tableOffset]->array, clip);
#	elif defined(ZAF_TEXT_MODE)
		ZafRegionStruct clip;
		clip.left = clip.top = 0;
		clip.right = display->columns - 1;
		clip.bottom = display->lines - 1;
		clip.coordinateType = ZAF_CELL;

#		if defined(ZAF_MSDOS)
		_upToDate =
#		endif // ZAF_MSDOS
		((ZafScreenDisplay *)display)->DeviceSet(ZAF_IMT_MOUSE_IMAGE, position.column,
			position.line, 1, 1, NULL, clip);
#	endif // ZAF_GRAPHICS_MODE
	}

	// Return the control code.
	return (ccode);
}

void ZafMouse::ImageAllocate(void)
{
#	if defined(ZAF_GRAPHICS_MODE)
	// Check for an initialized mouse system.
#	define initialized zafMouse_initialized
	extern bool initialized;
	if (initialized)
		return;

	// Initialize the mouse images.
	ZafI18nData::AddStaticModule(ImageFree);
	imageTable = new ZafMouseData *[sizeof(_mouseImage) / sizeof(SYS_MOUSE)];
	int i;
	for (i = 0; _mouseImage[i].imageID; i++)
	{
		imageTable[i] = new ZafMouseData(*_mouseImage[i].image);
		imageTable[i]->hotSpotX = _mouseImage[i].image->hotSpotX;
		imageTable[i]->hotSpotY = _mouseImage[i].image->hotSpotY;
		imageTable[i]->SetNumberID((ZafNumberID)_mouseImage[i].imageID);
	}
	imageTable[i] = ZAF_NULLP(ZafMouseData);

	initialized = true;
#	endif // ZAF_GRAPHICS_MODE
}

void ZafMouse::ImageFree(bool globalRequest)
{
# if defined(ZAF_GRAPHICS_MODE)
	// Destroy the static images.
	if (globalRequest && imageTable)
	{
		for (int i = 0; imageTable[i]; i++)
		{
			imageTable[i]->staticHandle = false;
			delete imageTable[i];
		}
		delete []imageTable;
		imageTable = ZAF_NULLP(ZafMouseData *);
	}
#	endif // ZAF_GRAPHICS_MODE
}

bool ZafMouse::IsA(ZafClassID compareID) const
{
	return ((compareID == classID) ? true : ZafDevice::IsA(compareID));
}

bool ZafMouse::IsA(ZafClassName compareName) const
{
	return (!strcmp(compareName, className) ? true : ZafDevice::IsA(compareName));
}

#if defined(USE_UGL_INPUT) || (defined(ZAF_INDEPENDENT) && !defined(ZAF_MSDOS))
static void ZafEventSet(ZafEventStruct &event, const ugl_input_event
	&uglEvent, ZafPositionStruct &position, ZafMouse *mouse);
#endif

void ZafMouse::Poll(void)
{
	if (!installed)
		return;

#if defined(USE_UGL_INPUT) || (defined(ZAF_INDEPENDENT) && !defined(ZAF_MSDOS))
	ugl_input_event uglEvent;
	ugl_input_event uglEvent1;
	ZafPositionStruct oldPos = position;
	if (InputQueuePoll(uglEvent, E_MOUSE) && deviceState != D_OFF)
	{
		uglEvent1 = uglEvent;
		ZafEventStruct mouseEvent(E_MOUSE);
		if (uglEvent.id == UGL_INPUT_EVENT_TYPE_INCREMENTAL_POINTER)
		{
			position.column += uglEvent.event.pointer.dx;
			position.line += uglEvent.event.pointer.dy;
			// Compress the mouse queue.
			while (InputQueuePoll(uglEvent1, E_MOUSE) &&
				uglEvent1.event.pointer.buttonChange ==
				uglEvent.event.pointer.buttonChange)
			{
				position.column += uglEvent1.event.pointer.dx;
				position.line += uglEvent1.event.pointer.dy;
			}
#	if defined(ZAF_MSDOS)
			_upToDate = (oldPos.line == position.line &&
				oldPos.column == position.column);
#	endif
		}
		else
		{
			position.column = uglEvent.event.pointer.x;
			position.line = uglEvent.event.pointer.y;
#	if defined(ZAF_MSDOS)
			_upToDate = false;
#	endif
		}

		ZafEventSet(mouseEvent, uglEvent, position, this);
		if (mouseEvent.rawCode == (M_LEFT | M_LEFT_CHANGE))
		{
			int line = int(position.line - lastClickPosition.line);
			int column = int(position.column - lastClickPosition.column);
			if (line < 0)
				line = -line;
			if (column < 0)
				column = -column;
			if (DoubleClicked(line, column))
				mouseEvent.rawCode |= S_DOUBLE_CLICK;
			else
				lastClickPosition = position;
		}
		eventManager->Put(mouseEvent);

		if (uglEvent1.event.pointer.buttonChange !=
			uglEvent.event.pointer.buttonChange)
		{
			position.column += uglEvent1.event.pointer.dx;
			position.line += uglEvent1.event.pointer.dy;
#	if defined(ZAF_MSDOS)
			_upToDate = (oldPos.line == position.line &&
				oldPos.column == position.column);
#	endif
			ZafEventSet(mouseEvent, uglEvent1, position, this);

			if (mouseEvent.rawCode == (M_LEFT | M_LEFT_CHANGE))
			{
				int line = int(position.line - lastClickPosition.line);
				int column = int(position.column - lastClickPosition.column);
				if (line < 0)
					line = -line;
				if (column < 0)
					column = -column;
				if (DoubleClicked(line, column))
					mouseEvent.rawCode |= S_DOUBLE_CLICK;
				else
					lastClickPosition = position;
			}
			eventManager->Put(mouseEvent);
		}
	}
#	if defined(ZAF_MSDOS)
	// Mouse could not be updated during a previous paint operation.
	if (!_upToDate)
	{
		ZafDeviceImage newImage = imageType;
		imageType = 0;
		Event(ZafEventStruct(newImage));
		_upToDate = true;
	}
#	else
	if (oldPos.line != position.line || oldPos.column != position.column)
	{
		// Move the cursor.
		ZafRegionStruct clip;
		clip.left = clip.top = 0;
		clip.right = display->columns - 1;
		clip.bottom = display->lines - 1;
#		if defined(ZAF_GRAPHICS_MODE)
		clip.coordinateType = ZAF_PIXEL;
#		elif defined(ZAF_TEXT_MODE)
		clip.coordinateType = ZAF_CELL;
#		endif // ZAF_GRAPHICS_MODE

		((ZafScreenDisplay *)display)->DeviceMove(ZAF_IMT_MOUSE_IMAGE, position.column - offset.column,
			position.line - offset.line, clip);
	}
#	endif // ZAF_MSDOS
#else
#	if defined(DOS32PP) || (defined(_MSC_VER) && defined(DOS386))
	MouseRead();
#	endif // DOS32PP, etc.

	// Make sure the mouse is valid.
	if (deviceState != D_OFF && _head != _tail)
	{
		while (_head != _tail)
		{
			ZafEventStruct mouseEvent(E_MOUSE);
			TranslateMouseEvent(mouseEvent);

			static bool button1Down = false;
			static bool button2Down = false;
			bool oldButton1Down = button1Down;
			bool oldButton2Down = button2Down;
			button1Down = (mouseEvent.rawCode & M_LEFT) ? true : false;
			button2Down = (mouseEvent.rawCode & M_RIGHT) ? true : false;

			// TranslateMouseEvent() updates position.
			if (position.column < 0)
				position.column = 0;
			else if (position.column >= display->columns)
				position.column = display->columns - 1;
			if (position.line < 0)
				position.line = 0;
			else if (position.line >= display->lines)
				position.line = display->lines - 1;

			// Only update position and put an event on the queue if done
			// processing the ring buffer or something actually happened
			// (e.g., a button was clicked).
			if (deviceState != D_HIDE &&
				(_head == _tail || button1Down != oldButton1Down || button2Down != oldButton2Down))
			{
				// Move the cursor.
				ZafRegionStruct clip;
				clip.left = clip.top = 0;
				clip.right = display->columns - 1;
				clip.bottom = display->lines - 1;
#	if defined(ZAF_GRAPHICS_MODE)
				clip.coordinateType = ZAF_PIXEL;
#	elif defined(ZAF_TEXT_MODE)
				clip.coordinateType = ZAF_CELL;
#	endif // ZAF_GRAPHICS_MODE

#	if defined(ZAF_MSDOS)
				_upToDate =
#	endif // ZAF_MSDOS
				((ZafScreenDisplay *)display)->DeviceMove(ZAF_IMT_MOUSE_IMAGE, position.column - offset.column,
					position.line - offset.line, clip);

				// Build the event.
				mouseEvent.position = position;
				if (button1Down != oldButton1Down)
					mouseEvent.rawCode |= M_LEFT_CHANGE;
				if (button2Down != oldButton2Down)
					mouseEvent.rawCode |= M_RIGHT_CHANGE;

				long line = position.line - lastClickPosition.line;
				long column = position.column - lastClickPosition.column;
				if (line < 0)
					line = -line;
				if (column < 0)
					column = -column;

				if (mouseEvent.rawCode == (M_LEFT | M_LEFT_CHANGE))
				{
					int tLine = (int)line;
					int tColumn = (int)column;
					if (DoubleClicked(tLine, tColumn))
						mouseEvent.rawCode |= S_DOUBLE_CLICK;
					else
						lastClickPosition = position;
				}

				mouseEvent.osEvent.type = E_MOUSE;
				mouseEvent.osEvent.position.line = position.line;
				mouseEvent.osEvent.position.column = position.column;
				eventManager->Put(mouseEvent);
			}
		}
	}
#	if defined(ZAF_MSDOS)
	// Mouse could not be updated during a previous paint operation.
	else if (!_upToDate)
	{
		ZafDeviceImage newImage = imageType;
		imageType = 0;
		Event(ZafEventStruct(newImage));
		_upToDate = true;
	}
#	endif // ZAF_MSDOS
#endif // USE_UGL_INPUT
}

// ----- Attributes ---------------------------------------------------------

ZafDeviceImage ZafMouse::SetImageType(ZafDeviceImage tImageType)
{
	imageType = tImageType;
	return (imageType);
}

int ZafMouse::SetDoubleClickRate(int tDoubleClickRate)
{
	doubleClickRate = tDoubleClickRate;
	return (doubleClickRate);
}

#if defined(USE_UGL_INPUT) || (defined(ZAF_INDEPENDENT) && !defined(ZAF_MSDOS))
static void ZafEventSet(ZafEventStruct &event, const ugl_input_event
	&uglEvent, ZafPositionStruct &position, ZafMouse *mouse)
{
	event.rawCode = uglEvent.event.pointer.buttonState << 8;
	if (uglEvent.event.pointer.buttonChange & 0x1) //???
		event.rawCode |= M_LEFT_CHANGE;
	if (uglEvent.event.pointer.buttonChange & 0x2) //???
		event.rawCode |= M_RIGHT_CHANGE;

	if (position.line > mouse->display->lines)
		position.line = mouse->display->lines;
	if (position.column > mouse->display->columns)
		position.column = mouse->display->columns;
	if (position.line < 0)
		position.line = 0;
	if (position.column < 0)
		position.column = 0;

	event.position = position;
	event.osEvent.type = E_MOUSE;
	event.osEvent.position.line = position.line;
	event.osEvent.position.column = position.column;
}
#endif

#endif // USE_ZAF_MOUSE
