#include "StdAfx.h"
#include "MapWindow.h"
#include "utilities.h"

MapWindow::MapWindow(void)
{
	map = NULL;
	original = NULL;
	NeedToLoadLocations = 1;
	focusml = NULL;
}

MapWindow::~MapWindow(void)
{
}

int MapWindow::Create(HWND parent)
{
	Load("mapwindow");
	titlebar.Load("mapwindow::titlebar");
	titlebar.title = L"";

	NearbyUsers.Load("mapwindow::NearbyUsers");
	NearbyUsers.SetLocation(NearbyUsers.location.X,size.Height-NearbyUsers.frombottom);
	FocusLocation.Load("mapwindow::FocusLocation");
	FocusLocation.location = Point(size.Width-FocusLocation.fromright,FocusLocation.location.Y);

	// load the map user styles
	MapUser::LoadStyles("mapwindow::users");

	if(SkinnableFrame::Create(parent)){
		SetWindowText(hWnd,env.GetWideString("mapwindow","title",L"MapWindow"));
		mapview.Load("mapwindow::mapview");
		// save the distance from edge for resize
		mv_edgex = size.Width-mapview.size.Width-mapview.location.X;
		mv_edgey = size.Height-mapview.size.Height-mapview.location.Y;
		mapview.Create(hWnd);
		mapview.glideHz = env.GetDouble("mapwindow::mapview","glideHz",10);
		mapview.dt = mapview.glideHz*env.GetDouble("mapwindow::mapview","glidetime",2)/1000.0;

		original = new Bitmap(env.GetWideString("mapwindow","mapfile",_T("testmap.bmp")));
		map = original->Clone(0,0,original->GetWidth(),original->GetHeight(),PixelFormatDontCare);

		mapview.SetImage(map);

		NearbyUsers.Create(hWnd,ES_MULTILINE|ES_READONLY|ES_LEFT);
		titlebar.visuals.push_back(&FocusLocation);

		SetMinMaxSize(-1,original->GetWidth()+mapview.location.X+mv_edgex,-1,
			original->GetHeight()+mapview.location.Y+mv_edgey);

		AddCustomNotification(bcLocation);
		AddCustomNotification(bcUser);
		NeedToLoadLocations = 1;
		return 1;
	}

	return 0;
}

// call this only when we have a connection to the SQL Server
void MapWindow::LoadLocations()
{
	if(!NeedToLoadLocations) return;

	// load the locations
	Vector<int> locationIds;
	db.GetAllLocations(locationIds,BUILD_NUMBER);
	if(locationIds.length>0){
		locations.allocate(locationIds.length);
		int i;
		for(i=0;i<locationIds.length;i++){
			char buffer[255];
			sprintf(buffer,"mapwindow::locations::%d",locationIds[i]);
			locations[i].Load(buffer);
			db.GetCachedLocation(locationIds[i],locations[i].record);
			print(L"MapWindow: loaded %s (x=%d, y=%d)\n",
				(TCHAR*)locations[i].record.LocationName,
				locations[i].location.X,locations[i].location.Y);
			locations[i].Hide();
			if(locations[i].record.ParentId==NullLocationId){
				locations[i].Create(mapview.canvas.hWnd);
				locations[i].origin = Point(0,0);
			} else {
				MapLocation * ml = FindLocation(locations[i].record.ParentId);
				if(ml){
					locations[i].Create(ml->hWnd);
					locations[i].origin = ml->visual.location;
					// assign this location's parent
					locations[i].parent = ml;
					// update the parent's list of children
					ml->children.push_back(&locations[i]);
				}
			}
		}
		NeedToLoadLocations = 0;
	} else {
		print(L"MapWindow: Unable to load locations from the database.\n");
	}
}

void MapWindow::DeepRefresh(){

	// first update the users already registered with the map
	if(users.size()>0){
		std::list<MapUser *>::iterator i;
		for(i=users.begin();i!=users.end();i++){
			MapUser * mu = *i;
			UpdateUser(mu->record.UserId,0);
		}
	}

	// now update all the users that are online but not on the map yet
	Vector<int> userIds;
	
	db.GetOnlineUsers(userIds);

	if(userIds.length>0){
		int i;
		for(i=0;i<userIds.length;i++){
			if(FindUser(userIds[i])==NULL)
				UpdateUser(userIds[i],0);
		}
	} 

	// finally commit all the changes to the map
	UpdateMap();
}

void MapWindow::UpdateUser(int UserId, int redraw)
{
	MapUser * mu = FindUser(UserId);
	if(mu==NULL) { // not on the map yet
		mu = new MapUser;
		mu->Create(mapview.canvas.hWnd);
		db.GetCachedUser(UserId,mu->record);
		print(L"MapWindow: Adding user %s.\n",(TCHAR*)mu->record.UserLogin);
		users.push_back(mu);
	}

	UpdateUser(mu,redraw);
}

void MapWindow::UpdateUser(MapUser * mu, int redraw){

	print(L"MapWindow: Updating user %s.\n",(TCHAR*)mu->record.UserLogin);
	// update their record
	int OldLocationId = mu->record.LocationId;
	db.GetCachedUserState(mu->record.UserId,mu->record);
	int NewLocationId = mu->record.LocationId;

	// update the location items in the location list 
	// because they depend on the map state
	LocationRecord oldrecord, newrecord;
	db.GetCachedLocation(OldLocationId,oldrecord);
	db.GetCachedLocation(NewLocationId,newrecord);
	theApp.locationlist.ShallowRefreshItem(oldrecord.ParentId);
	theApp.locationlist.ShallowRefreshItem(OldLocationId);
	theApp.locationlist.ShallowRefreshItem(NewLocationId);
	theApp.locationlist.ShallowRefreshItem(newrecord.ParentId);

	// now redraw this user to reflect his state change
	if(mu->Update())
		if(redraw)
			UpdateMap();
}

void MapWindow::OnSize(int cx, int cy)
{
	SkinnableFrame::OnSize(cx,cy);
	mapview.SetSize(size.Width-mapview.location.X-mv_edgex,size.Height-mapview.location.Y-mv_edgey);
	NearbyUsers.SetLocation(NearbyUsers.location.X,size.Height-NearbyUsers.frombottom);
	FocusLocation.location = Point(size.Width-FocusLocation.fromright,FocusLocation.location.Y);

}

int MapWindow::SaveView(TCHAR * filename, TCHAR * format)
{
	CLSID Clsid;
	GetEncoderClsid(format, &Clsid);
	return (map->Save(filename,&Clsid)==Ok);
}

MapLocation * MapWindow::FindLocation(int locationId)
{
	int i;
	for(i=0;i<locations.length;i++){
		if(locations[i].record.LocationId==locationId)
			return &(locations[i]);
	}	
	return NULL;
}

MapLocation * MapWindow::FindLocation(HWND h)
{
	int i;
	for(i=0;i<locations.length;i++){
		if(locations[i].hWnd==h)
			return &(locations[i]);
	}	
	return NULL;
}

void MapWindow::SelectUser(int userId)
{
	MapUser * mu = FindUser(userId);
	if(mu) {
		if(selectedUser){
			selectedUser->SetState(AppearanceState::normal);
		}
		selectedUser = mu;
		mu->SetState(AppearanceState::selected);
	}
}

void MapWindow::ClearSelection()
{
	if(selectedUser){
		selectedUser->SetState(AppearanceState::normal);
		selectedUser = NULL;
	}
}

void MapWindow::ShowLocation(int locationId)
{
	focusml = FindLocation(locationId);
	if(focusml){
		FocusLocation.text = theApp.GetDisplayName(focusml->record);
		titlebar.Redraw();

		for(int i=0;i<locations.length;i++) locations[i].Hide();
		
		while(focusml->parent) focusml = focusml->parent; // travel up to get the ancestor
		focusml->Show();

		UpdateMap();

	}
}

MapUser * MapWindow::FindUser(int UserId)
{
	std::list<MapUser *>::iterator i;
	for(i=users.begin();i!=users.end();i++){
		MapUser * mu = *i;
		if(mu->record.UserId==UserId)
			return mu;
	}	
	return NULL;
}

MapUser * MapWindow::FindUser(HWND h)
{
	std::list<MapUser *>::iterator i;
	for(i=users.begin();i!=users.end();i++){
		MapUser * mu = *i;
		if(mu->hWnd==h)
			return mu;
	}	
	return NULL;
}

void MapWindow::UpdateMap()
{
	try {
		if(original==NULL) return;

		SetLastError(0);

		Graphics graphics(map);
		graphics.DrawImage(original,0,0,0,0,original->GetWidth(),original->GetHeight(),UnitPixel);
		if(graphics.GetLastStatus()!=Ok){
			print(L"MapWindow.Create: Failed to draw on image, check image pixelformat (status = %d)\n",graphics.GetLastStatus());
			PrintLastError("MapWindow.Create");
		}

		// draw the locations
		{
			int i;
			for(i=0;i<locations.length;i++){
				if(!locations[i].hidden)
					locations[i].DrawToMap(graphics);
			}
		}		

		// draw the users
		{
			std::list<MapUser *>::iterator i;
			for(i=users.begin();i!=users.end();i++){
				MapUser * mu = *i;
				if(!mu->hidden){
					mu->DrawToMap(graphics);
				}
			}	
		}

		mapview.SetImage(map);	

		if(focusml){
			// fill the NearbyUsers textbox with the usernames of users in the ancestor location
			NearbyUsers.SetText(focusml->UserList().data());
		}

	} catch(...) {
		print(L"MapWindow::UpdateMap exception.\n");
	}
}

static void DrawLocation(MapLocation * ml, Graphics & graphics)
{
	// the main location
	if(!ml->hidden)
		ml->DrawToMap(graphics);

	// all of its children locations
	{
		std::list<MapLocation *>::iterator i;
		for(i=ml->children.begin();i!=ml->children.end();i++){
			MapLocation * ml = *i;
			if(!ml->hidden)
				ml->DrawToMap(graphics);
		}
	}

	// all of its occupants
	{
		std::list<MapUser *>::iterator i;
		for(i=ml->users.begin();i!=ml->users.end();i++){
			MapUser * mu = *i;
			if(!mu->hidden)
				mu->DrawToMap(graphics);
		}
	}
}

void MapWindow::UpdateMapAround(MapUser * mu)
{
	try {
		if(original==NULL) return;

		SetLastError(0);

		Graphics graphics(map);
		if(graphics.GetLastStatus()!=Ok){
			print(L"MapWindow.Create: Failed to draw on image, check image pixelformat (status = %d)\n",graphics.GetLastStatus());
			PrintLastError("MapWindow.Create");
		}

		Rect r;
		if(mu->ml && !mu->ml->hidden){
			r = Rect(mu->ml->location,mu->ml->size);
		} else {
			r = Rect(mu->origin+mu->location,mu->size);
		}
		graphics.DrawImage(original,r.X-10,r.Y-10,r.X-10,r.Y-10,r.Width+10,r.Height+10,UnitPixel);

		DrawLocation(mu->ml,graphics);

		mapview.SetImage(map);	
	} catch(...) {
		print(L"MapWindow::UpdateMap exception.\n");
	}
}

void MapWindow::UpdateMapAround(MapLocation * ml)
{
	try {
		if(original==NULL) return;

		SetLastError(0);

		Graphics graphics(map);
		if(graphics.GetLastStatus()!=Ok){
			print(L"MapWindow.Create: Failed to draw on image, check image pixelformat (status = %d)\n",graphics.GetLastStatus());
			PrintLastError("MapWindow.Create");
		}

		Rect r;
		r = Rect(ml->location,ml->size);
		graphics.DrawImage(original,r.X-10,r.Y-10,r.X-10,r.Y-10,r.Width+10,r.Height+10,UnitPixel);

		DrawLocation(ml,graphics);

		mapview.SetImage(map);	
	} catch(...) {
		print(L"MapWindow::UpdateMap exception.\n");
	}
}


void MapWindow::GlideToLocation(int locationId)
{
	MapLocation * ml = FindLocation(locationId);
	if(ml){
		mapview.GlideTo(ml->origin.X+ml->location.X+ml->size.Width/2,
			ml->origin.Y+ml->location.Y+ml->size.Height/2);
	}
}

void MapWindow::GlideToUser(int userId)
{
	MapUser * mu = FindUser(userId);
	if(mu){
		if(mu->record.LocationId!=NullLocationId){
			ShowLocation(mu->record.LocationId);
			mapview.GlideTo(mu->location.X+mu->size.Width/2,mu->location.Y+mu->size.Height/2);
			SelectUser(mu->record.UserId);

			TCHAR buffer[255];
			_stprintf(buffer,L"%s %s",(TCHAR*)mu->record.FirstName,(TCHAR*)mu->record.LastName,mu->record.LocationId);
			titlebar.title = buffer;
			titlebar.Redraw();
		} else {
			SelectUser(mu->record.UserId);
			TCHAR buffer[255];
			_stprintf(buffer,L"%s %s (location is unknown)",(TCHAR*)mu->record.FirstName,(TCHAR*)mu->record.LastName);
			titlebar.title = buffer;
			titlebar.Redraw();
		}


	}

}


void MapWindow::OnCustomNotify(int id, Notification& n)
{
	switch(id){
	case bcLocation:
		break;
	case bcUser:
		UpdateUser(n.param[0]);
		break;
	}
}

void MapWindow::OnButton(HWND h){
	MapUser * mu = FindUser(h);
	if(mu){
		SelectUser(mu->record.UserId);
		
		TCHAR buffer[255];
		_stprintf(buffer,L"%s %s",(TCHAR*)mu->record.FirstName,(TCHAR*)mu->record.LastName);
		titlebar.title = buffer;
		titlebar.Redraw();

		theApp.ViewProfile(mu->record.UserId);
	} else {
		MapLocation * ml = FindLocation(h);
		if(ml){
			FocusLocation.text = theApp.GetDisplayName(ml->record);
			titlebar.Redraw();

			if(env.mode==L"LocationEditor"){
				theApp.AddLocationSignature(ml->record.LocationId);
			}
		}
	}
}

void MapWindow::ShowAllLocations()
{
	int i;
	for(i=0;i<locations.length;i++){
		locations[i].Show();
	}
	UpdateMap();
}

#define BOGUS_USER_ID 6969

void MapWindow::CreateBogusUser()
{
	MapUser * mu = new MapUser;
	mu->Create(mapview.canvas.hWnd);
	mu->record.UserId = BOGUS_USER_ID;
	mu->record.FirstName = L"Mr.";
	mu->record.LastName = L"Decoy";
	mu->record.UserLogin = L"decoy";
	mu->record.LoggedIn = 1;
	mu->record.Idle = 0;
	mu->Update();
	users.push_back(mu);
	UpdateMap();
}

void MapWindow::UpdateBogusUser(int locationId)
{
	MapUser * mu = FindUser(BOGUS_USER_ID);
	if(mu){
		mu->record.LocationId = locationId;
		if(mu->Update())
			UpdateMap();
	}
}