//	Darkstar Engine  (c) UnderWorld Interactive Studio (all coding by Svarog    2007)
//
//      Данный файл является частью Darkstar Engine. Любое использование кода файла
//      без согласия компании, является нарушением закона об авторском праве (в слу-
//      чае если не оговорено обратное).
//
//      Имя файла: mouse.cpp
//      Краткое описание: работа с мышью
//

#include <Darkstar/Input/mouse.hpp>
#include <Darkstar/Input/inputDriver.hpp>
#include <Darkstar/Kernel/Log/log.hpp>

using ker::Log;
using input::InputDriver;
using input::Mouse;

Mouse::Mouse()
{
	is_immediate = true;
	mouse = NULL;
	buffer_data_size = 0;
	cur_valid_buf_size = 0;
	ZeroMemory(&immediate_data, sizeof(DIMOUSESTATE2));
};

Mouse::~Mouse()
{
	if(data_buffered)
	{
		data_buffered.reset(NULL);
	};
};

void Mouse::setBufferedMode(const unsigned int buffer_data_size)
{
	this->buffer_data_size = buffer_data_size;
	data_buffered.reset(new DIDEVICEOBJECTDATA[buffer_data_size]);
	is_immediate = false;
};

void Mouse::setImmediateMode()
{
	is_immediate = true;
	buffer_data_size = 0;
};

bool Mouse::getIsImmediateMode() const
{
	return is_immediate;
};

unsigned int Mouse::getBufferedDataSize() const
{
	return buffer_data_size;
};

bool Mouse::init(HWND hwnd)
{
	mouse = InputDriver::ptr()->createDevice(GUID_SysMouse);
	if(!mouse)
	{
		lwar << "Cannot create mouse device";
		return false;
	};
	if(FAILED(mouse->SetDataFormat(&c_dfDIMouse2)))
	{
		lwar << "Cannot set data format for mouse device";
		return false;
	};
	if(FAILED(mouse->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND)))
	{
		lwar << "Cannot set cooperative level for mouse device";
		return false;
	};
	DIPROPHEADER diph;
	diph.dwSize = sizeof(DIPROPDWORD);
	diph.dwHeaderSize = sizeof(DIPROPHEADER);
	diph.dwHow = DIPH_DEVICE;
	diph.dwObj = 0;
	if(FAILED(mouse->SetProperty(DIPROP_BUFFERSIZE, &diph)))
	{
		lwar << "Cannot set property in mouse device";
		return false;
	};
	if(FAILED(mouse->Acquire()))
	{
		lwar << "Cannot acquire mouse";
		return false;
	};
	return true;
};

bool Mouse::_tryToReacquire()
{
	HRESULT h = mouse->Acquire();
	while(h == DIERR_INPUTLOST)
		h = mouse->Acquire();
	if(FAILED(h))
	{
		lwar << "Cannot get device state for mouse";
		return false;
	};
	return true;
};

bool Mouse::updateBuffer()
{
	if(is_immediate)
	{
		lwar << "Try to updata buffer in immediate mode";
		return false;
	};
	ZeroMemory(data_buffered.get(), sizeof(DIDEVICEOBJECTDATA)*buffer_data_size);
	cur_valid_buf_size = buffer_data_size;
	if(FAILED(mouse->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), data_buffered.get(), &cur_valid_buf_size, 0)))
	{
		_tryToReacquire();
		return false;
	};
	return true;
};

bool Mouse::updateImmediateData()
{
	if(!is_immediate)
	{
		lwar << "Try to update immediate data in buffered mode";
		return false;
	};
	ZeroMemory(&immediate_data, sizeof(DIMOUSESTATE2));
	if(FAILED(mouse->GetDeviceState(sizeof(DIMOUSESTATE2), &immediate_data)))
	{
		_tryToReacquire();
		return false;
	};
	return true;
};

bool Mouse::getIsButtonDownImmediate(const unsigned char code) const
{
	if(!is_immediate)
	{
		lwar << "Try to get button state in immediate mode when set buffered";
		return false;
	};
	return (immediate_data.rgbButtons[code] & 0x80) != 0;
};

bool Mouse::getIsButtonReleaseImmediate(const unsigned char code) const
{
	if(!is_immediate)
	{
		lwar << "Try to get button state in immediate mode when set buffered";
		return false;
	};
	return (immediate_data.rgbButtons[code] & 0x80) == 0;
};

int Mouse::getRelMouseMoveImmediate(const unsigned char axe) const
{
	if(!is_immediate)
	{
		lwar << "Try to get mouse move data in immediate mode whent set buffered";
		return 0;
	};
	switch(axe)
	{
	case DMI_X:
		return immediate_data.lX;
	case DMI_Y:
		return -immediate_data.lY; // отрицательные значения соответствуют движению мыши от
		// пользователя. Мы же будем считать, что движение мыши от пользователя соответствует
		// положительному смещению
	case DMI_Z:
		return immediate_data.lZ;
	default:
		return 0;
	};
	return 0;
};

bool Mouse::getIsButtonDownBuffered(const unsigned char code) const
{
	if(is_immediate)
	{
		lwar << "Try to get button state in buffered mode when set immediate";
		return false;
	};
	DWORD cur_search = 0;
	switch(code)
	{
	case DMI_LB:
		{
			cur_search = DIMOFS_BUTTON0;
		};break;
	case DMI_RB:
		{
			cur_search = DIMOFS_BUTTON1;
		};break;
	case DMI_MB:
		{
			cur_search = DIMOFS_BUTTON2;
		};break;
	default:
		return false;
	};
	for(unsigned int i = 0; i < cur_valid_buf_size; ++i)
	{
		if(data_buffered[i].dwOfs == cur_search)
			return (data_buffered[i].dwData & 0x80) != 0;
	};
	return false;
};

bool Mouse::getIsButtonReleaseBufferred(const unsigned char code) const
{
	if(is_immediate)
	{
		lwar << "Try to get button state in buffered mode when set immediate";
		return false;
	};
	DWORD cur_search = 0;
	switch(code)
	{
	case DMI_LB:
		{
			cur_search = DIMOFS_BUTTON0;
		};break;
	case DMI_RB:
		{
			cur_search = DIMOFS_BUTTON1;
		};break;
	case DMI_MB:
		{
			cur_search = DIMOFS_BUTTON2;
		};break;
	default:
		return false;
	};
	for(unsigned int i = 0; i < cur_valid_buf_size; ++i)
	{
		if(data_buffered[i].dwOfs == cur_search)
			return (data_buffered[i].dwData & 0x80) == 0;
	};
	return false;
};

int Mouse::getRelMouseMoveBuffered(const unsigned char axis) const
{
	if(is_immediate)
	{
		lwar << "Try to get button state in buffered mode when set immediate";
		return false;
	};
	DWORD cur_search = 0;
	switch(axis)
	{
	case DMI_X:
		{
			cur_search = DIMOFS_X;
		};break;
	case DMI_Y:
		{
			cur_search = DIMOFS_Y;
		};break;
	case DMI_Z:
		{
			cur_search = DIMOFS_Z;
		};break;
	default:
		return false;
	};
	for(unsigned int i = 0; i < cur_valid_buf_size; ++i)
	{
		if(data_buffered[i].dwOfs == cur_search)
			return data_buffered[i].dwData;
	};
	return 0;
};