//===========================================================================//
// File:	Interface.cpp                                                       //
// Project: MUNGA           Brick: Interest Manager	                       //
// Contents: Interface specifications for interest manager						  //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  -----------------------------------------------------------//
// 08/25/97  JMA  Infrastructure changes.                                    //
// 08/25/97  ECH  Infrastructure changes.                                    //
//---------------------------------------------------------------------------//
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc.                //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "AdeptHeaders.hpp"

#include "Interface.hpp"
#include "Controls.hpp"
#include "EntityManager.hpp"
#include "CollisionGrid.hpp"
#include "Player.hpp"

extern "C"
{
typedef int                 BOOL;
typedef unsigned char       BYTE;
BOOL _stdcall IsDBCSLeadByte(BYTE TestChar);
char* _stdcall CharPrevA(const char* lpszStart, const char* lpszCurrent);
char* _stdcall CharNextA(const char* lpsz);
}
#define CharPrev CharPrevA
#define CharNext CharNextA
extern DWORD g_bDisableImeCompletely;
//#############################################################################
//###########################    Interface    #################################
//#############################################################################

Interface::ClassData*
	Interface::DefaultData = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Interface::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			InterfaceClassID,
			"Adept::Interface",
			BaseClass::DefaultData,
			0, NULL,
			(Entity::Factory)Make,
			(Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
			ExecutionStateEngine::DefaultData,
			(Entity::GameModel::Factory)GameModel::ConstructGameModel,
			NULL,
			(Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
			(Entity::GameModel::ModelWrite)GameModel::WriteToText,
			(Entity::GameModel::ModelSave)GameModel::SaveGameModel
		);
	Register_Object(DefaultData);
	g_ControlList = new CControlMappingList ();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Interface::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
	delete g_ControlList;
	g_ControlList = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Interface*
	Interface::Make(
		const CreateMessage *message,
		ReplicatorID *base_id
	)
{
	Check_Object(message);
	gos_PushCurrentHeap(g_LibraryHeap);
	Interface *new_entity = new Interface(DefaultData, message, base_id, NULL);
	Check_Object(new_entity);
	gos_PopCurrentHeap();
	return new_entity;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

Interface::~Interface()
{
	LeaveChatMode ();
	Check_Object(ControlsManager::Instance);
	ControlsManager::Instance->RemoveAllMappings();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

Interface::Interface(
	ClassData *class_data,
	const CreateMessage *message,
	ReplicatorID *base_id,
	ElementRenderer::Element *element
):
	Entity(class_data, message, base_id, element),
	reticuleEntity(NULL),
	targetQuery(NULL, NULL, CanBeShotFlag, NULL)
{
	Check_Pointer(this);

	//
	//------------------------------------------------------------
	// Interfaces should only map controls on their master machine
	//------------------------------------------------------------
	//
	m_ChatMode = false;
	m_InChatMessage = 0;
	m_InsertMode = true;
	m_CurrentChatMessage[m_InChatMessage] = 0;
	reticuleOffset = Point3D::Identity;
	Verify(GetReplicatorMode() == HermitMode);

	targetDistance = 2000.0f;

	//
	//---------------------------------------------------------------------
	// Create the mapping stream and figure out how many mappings to create
	//---------------------------------------------------------------------
	//
//	const GameModel *model = GetGameModel();
//	Check_Object(model);
	Check_Object(ControlsManager::Instance);

	//
	//------------------------------------------
	// Spin through the mappings and create them
	//------------------------------------------
	//
//	LoadControlStream (model->mappingStreamResourceID);
}

void Interface::ClearMappings (void)
{
	Check_Object (ControlsManager::Instance);
	ControlsManager::Instance->RemoveAllMappings ();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

static void MyInsertChar(char m_CurrentChatMessage[MaxChatSize], char c1, char c2, int& m_InChatMessage, int& m_ChatLen)
{
	gosASSERT(m_ChatLen >= m_InChatMessage);

	int num = c2 ? 2 : 1;
	memmove(m_CurrentChatMessage + m_InChatMessage + num, m_CurrentChatMessage + m_InChatMessage, m_ChatLen - m_InChatMessage);
	m_CurrentChatMessage[m_InChatMessage++] = c1;
	if (c2)
		m_CurrentChatMessage[m_InChatMessage++] = c2;
	m_ChatLen += num;
	m_CurrentChatMessage[m_ChatLen] = 0;
}

static void MyDeleteChar(char m_CurrentChatMessage[MaxChatSize], int& m_InChatMessage, int& m_ChatLen)
{
	gosASSERT(m_ChatLen >= m_InChatMessage);

	char* pszPrev = CharPrev(m_CurrentChatMessage, m_CurrentChatMessage + m_InChatMessage);
	int num = m_CurrentChatMessage + m_InChatMessage - pszPrev;
	memmove(pszPrev, m_CurrentChatMessage + m_InChatMessage, m_ChatLen - m_InChatMessage + 1);
	m_ChatLen -= num;
	m_InChatMessage -= num;
}

void Interface::pushChatKey (DWORD key)
{
	char chr = char (key & 0xff);
	unsigned int ctrl = unsigned int ((key >> 8) & 0xff);
	if ((chr == 13) || (m_InChatMessage == (MaxChatSize-1)))
	{
		LeaveChatMode ();
		m_InChatMessage = 0;
		m_CurrentChatMessage[m_InChatMessage] = 0;
		return;
	}

	if (!g_bDisableImeCompletely)
	{
		switch (ctrl) {
		case KEY_LEFT:
			// Left arrow must be processed to make GOS IME support work correctly.
			m_InChatMessage = CharPrev(m_CurrentChatMessage, m_CurrentChatMessage + m_InChatMessage) - m_CurrentChatMessage;
			break;

		case KEY_RIGHT:
			// This is necessary as we support left arrow key.
			m_InChatMessage = CharNext(m_CurrentChatMessage + m_InChatMessage) - m_CurrentChatMessage;
			break;

		// Fix for patch 2 #403 (feature request)
		case KEY_DELETE:
			{
			char* p = m_CurrentChatMessage + m_InChatMessage;
			if (*p) {
				char* pNext = CharNext(p);
				memmove(p, pNext, strlen(pNext) + 1);
			}
			}
			break;
		}
	}

	if (!chr)
		return;

	static char bLeadByte;
	if (bLeadByte)
	{
		if (m_ChatLen + 2 < MaxChatSize)
			MyInsertChar(m_CurrentChatMessage, bLeadByte, chr, m_InChatMessage, m_ChatLen);
		bLeadByte = 0;
	}
	else if (IsDBCSLeadByte(chr))
		bLeadByte = chr;
	else {
		switch (chr)
		{
			case '\\':						// invalid keys
			case '/':
				break;
			case KEY_ESCAPE:
				m_CurrentChatMessage[0] = 0;
				LeaveChatMode ();
				break;
			case '\b':
				if (m_InChatMessage)
					MyDeleteChar(m_CurrentChatMessage, m_InChatMessage, m_ChatLen);
				break;
			default:
				MyInsertChar(m_CurrentChatMessage, chr, 0, m_InChatMessage, m_ChatLen);
				break;
		}
	}
	if (m_InChatMessage == (MaxChatSize-1))
	{
		LeaveChatMode ();
		m_InChatMessage = 0;
		m_CurrentChatMessage[m_InChatMessage] = 0;
		return;
	}
}

static bool g_bImeOn = true;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Interface::EnterChatMode (bool team)
{
	m_ChatMode = true;
	m_TeamChat = team;
	m_InChatMessage = 0;
	m_ChatLen = 0;
	memset (m_CurrentChatMessage,0,MaxChatSize);
	m_CurrentChatMessage[m_InChatMessage] = 0;
	Check_Object(ControlsManager::Instance);
	ControlsManager::Instance->EnterChatMode(this);
	gos_EnableIME(true);
	gos_ToggleIME(g_bImeOn);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Interface::LeaveChatMode (void)
{
	m_ChatMode = false;
	if (ControlsManager::Instance)
	{
		Check_Object(ControlsManager::Instance);
		ControlsManager::Instance->LeaveChatMode();
	}
	g_bImeOn = gos_GetMachineInformation(gos_Info_GetIMEStatus) ? true : false;
	gos_EnableIME(false);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Interface::LoadControlStream (const ResourceID& control_stream)
{
//	ClearMappings ();
//	g_ControlList->LoadList (control_stream);
	g_ControlList->ApplyList (this);				// list is already loaded by the player
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Entity*
	Interface::CastCollisionLine()
{
	Check_Object(this);
	
	Stuff::Point3D local_point;
	Stuff::Point3D origin_point;
	LinearMatrix4D our_matrix;

	origin_point = GetLocalToWorld();
	our_matrix = GetLocalToWorld();

	UnitVector3D origin_direction;
	our_matrix.GetLocalForwardInWorld(&origin_direction);

	targetLine.SetOrigin(origin_point);
	targetLine.SetDirection(origin_direction);
	targetLine.m_length = targetDistance;
	
	targetQuery.m_line = &targetLine;
	targetQuery.m_normal = &reticuleNormal;
	targetQuery.m_collisionMask = CanBeShotFlag;
	Check_Object(Player::GetInstance());
	Entity *vehicle = Player::GetInstance()->vehicle;
	Check_Object(vehicle);
	targetQuery.m_raySource = vehicle;

	Adept::Entity *entity_hit;
	Check_Object(CollisionGrid::Instance);
	entity_hit = CollisionGrid::Instance->ProjectLine(&targetQuery);

	targetQuery.m_raySource = entity_hit;
	reticuleEntity.Remove();

	if (entity_hit != NULL)
	{
		Check_Object(entity_hit);
		reticuleEntity.Add(entity_hit);
		Point3D trans;
		targetLine.FindEnd(&trans);
		reticuleOffset.MultiplyByInverse(
			trans,
			entity_hit->GetLocalToWorld()
		);
	}

	return entity_hit;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Interface::TestInstance()
{
	Verify(IsDerivedFrom(DefaultData));
}
