#include "StdAfx.h"
#include "buddylist.h"
#include "resource.h"

BuddyList::BuddyList(void)
{
	NeedRefresh = 0;
	NeedEditRefresh = 0;
}

BuddyList::~BuddyList(void)
{
}

int BuddyList::Create(HWND parent)
{
	Load("buddywindow");
	titlebar.title = env.GetWideString("buddywindow","title",L"TelescopingList");
	titlebar.Load("buddywindow::titlebar");
	expand.Load("buddywindow::expand");
	edit.Load("buddywindow::edit");
	
	list.Load("buddywindow::list");
	list.scrollbar.Load("buddywindow::scrollbar");
	
	editinglist.Load("buddywindow::editinglist");
	editinglist.scrollbar.Load("buddywindow::scrollbar");
	editinglist.inplace.Load("buddywindow::inplace");

	// load the item appearances for the active buddy list
	Active.Load("buddywindow::styles::active");
	Idle.Load("buddywindow::styles::idle");
	Idle5min.Load("buddywindow::styles::idle5min");
	Idle15min.Load("buddywindow::styles::idle15min");
	Idle30min.Load("buddywindow::styles::idle30min");
	Away.Load("buddywindow::styles::away");
	LoggedOff.Load("buddywindow::styles::loggedoff");
	EmptyStyle.Load("buddywindow::styles::empty");
	// load the item appearnaces for the editing list
	eActive.Load("buddywindow::editstyles::active");
	eIdle.Load("buddywindow::editstyles::idle");
	eIdle5min.Load("buddywindow::editstyles::idle5min");
	eIdle15min.Load("buddywindow::editstyles::idle15min");
	eIdle30min.Load("buddywindow::editstyles::idle30min");
	eLoggedOff.Load("buddywindow::editstyles::loggedoff");
	eAway.Load("buddywindow::editstyles::away");
	eAddable.Load("buddywindow::editstyles::add");

	moveable = 0;
	ShowInTaskBar = 0;
	TelescopingList::Create(parent);
	list.ShowEmptyItem(EmptyStyle.description.data(),EmptyStyle);
	editinglist.ShowDataEntry(env.GetWideString("buddywindow::editstyles::add","description",L"add..."),eAddable);
	editinglist.inplace.SetMsgOnCarriageReturn(hWnd,ID_CARRIAGERETURN,(WPARAM)editinglist.inplace.hWnd,0);


	SetTimer(hWnd,0,5000,0);

	// finally request notifications for changes
	AddCustomNotification(bcUser);
	// bcEvent notifications are handled by the BuddyPopupList class

	LoadContextMenu(IDR_BUDDY_CONTEXTMENU);

	return 1;
}

void BuddyList::DeepRefresh(int memoize)
{
	if(memoize && editing){
		NeedRefresh = 1;
		return;
	} else
		NeedRefresh = 0;

	Vector<int> OnlineBuddyIds;

#ifdef SUPERUSER
	db.GetOnlineUsers(OnlineBuddyIds);
#else
	db.GetOnlineBuddyList(theApp.user.UserId,OnlineBuddyIds);
#endif

	int i;

	// first remove buddies that are no longer online
	Vector<int> current;
	list.GetKeys(current);
	for(i=0;i<current.length;i++){
		if(OnlineBuddyIds.find(current[i])==-1){ // not subscribable to anymore, remove it
			ListItem * item = list.GetItemByKey(current[i]);
			if(item){
				Buddy * buddy = (Buddy *)item->userdata;
				Delete(buddy);
			}
		}
	}

	// now take care of the buddies that are online
	for(i=0;i<OnlineBuddyIds.length;i++){
		ListItem * item = list.GetItemByKey(OnlineBuddyIds[i]);
		if(item){ // already in the list
			Buddy * buddy = (Buddy *)item->userdata;
			buddy->DeepRefresh(OnlineBuddyIds[i]);
		} else { // not in the list yet, so add it
			Buddy * buddy = new Buddy();
			buddy->DeepRefresh(OnlineBuddyIds[i]);
			Add(buddy);
		}
	}

	UpdateOnlineCount();

}

void BuddyList::UpdateOnlineCount()
{
	TCHAR buffer[100];
	_stprintf(buffer,L"%d/%d",list.items.size(),editinglist.items.size());
	expand.text = buffer;
	expand.Redraw();
}

void BuddyList::Add(Buddy * buddy)
{
	ListItem * item = list.CreateItem(buddy->record.UserLogin,buddy->record.UserId);
	buddy->item = item;
	buddy->editing = 0;
	buddy->ShallowRefresh();
	item->userdata = buddy;
	item->usertype = bcUser;
	list.AddItem(item);
	UpdateOnlineCount();
}

void BuddyList::Delete(Buddy * buddy)
{
	list.DeleteItem(buddy->item);
	delete buddy;
	UpdateOnlineCount();
}

void BuddyList::EditAdd(Buddy * buddy)
{
	ListItem * item = editinglist.CreateItem(buddy->record.UserLogin,buddy->record.UserId);
	buddy->item = item;
	buddy->editing = 1;
	buddy->ShallowRefresh();
	item->userdata = buddy;
	item->usertype = bcUser;
	editinglist.AddItem(item);
	UpdateOnlineCount();
}

void BuddyList::EditDelete(Buddy * buddy)
{
	editinglist.DeleteItem(buddy->item);
	delete buddy;
	UpdateOnlineCount();
}


void BuddyList::OnListSelect(HWND h, HWND hItem)
{
	ListItem * item = (ListItem*)GetWindowLongPtr(hItem,GWL_USERDATA);
	if(list.hWnd==h){
		// open the map and indicate the buddy on it
		Buddy * buddy = (Buddy*)item->userdata;
		theApp.ShowUserOnMap(buddy->record.UserId);
	} else if(editinglist.hWnd==h){

	}
}

void BuddyList::OnListRightSelect(HWND h, HWND hItem)
{
	ListItem * item = (ListItem*)GetWindowLongPtr(hItem,GWL_USERDATA);
	if(list.hWnd==h){
		// bring up the context menu and perform the requested operation
		Buddy * buddy = (Buddy*)item->userdata;
		switch(DisplayContextMenu()){
			case ID_BUDDY_CHAT:
				theApp.ChatWithUser(buddy->record.UserId, buddy->record.UserLogin);
				break;
			case ID_BUDDY_SHOWONMAP:
				theApp.ShowUserOnMap(buddy->record.UserId);
				break;
			case ID_BUDDY_VIEWPROFILE:
				theApp.ViewProfile(buddy->record.UserId);
				break;
		}
	} else if(editinglist.hWnd==h){

	}
}

void BuddyList::OnListDblSelect(HWND h, HWND hItem)
{
	ListItem * item = (ListItem*)GetWindowLongPtr(hItem,GWL_USERDATA);
	if(list.hWnd==h){
		
	} else if(editinglist.hWnd==h){

	}
}

void BuddyList::OnPopupStart(WPARAM wParam, LPARAM lParam)
{
	HWND hSourceList = (HWND)lParam;
	if(hSourceList==list.hWnd){
		ListItem * item = (ListItem *)wParam;
		Buddy * buddy = (Buddy *)item->userdata;
		if(buddy){
			buddy->ShallowRefresh();
			RECT rect;
			GetWindowRect(item->hWnd,&rect);
			buddy->popup.SetLocation(theApp.sidebar.location.X+location.X+list.location.X-buddy->popup.size.Width,theApp.sidebar.location.Y+rect.top);
			buddy->popup.SetActivator(item->hWnd);
			buddy->popup.Show();
		}
	}
}

void BuddyList::OnPopupEnd(WPARAM wParam, LPARAM lParam)
{
	HWND hSourceList = (HWND)lParam;
	if(hSourceList==list.hWnd){
		ListItem * item = (ListItem *)wParam;
		Buddy * buddy = (Buddy *)item->userdata;
		if(buddy){
			if(buddy->popup.titlebar.MouseOutside)
				buddy->popup.Hide();
			else
				buddy->popup.StartTimer();
		}
	} 
}

void BuddyList::OnTimer()
{
	if(editing){
		lipos i;
		for(i=editinglist.items.begin();i!=editinglist.items.end();i++){
			ListItem * item =  *i;
			if(item){
				Buddy * buddy = (Buddy*)item->userdata;
				buddy->ShallowRefresh(); // only refresh time-sensitive components
			}		
		}
	} else {
		lipos i;
		for(i=list.items.begin();i!=list.items.end();i++){
			ListItem * item =  *i;
			if(item){
				Buddy * buddy = (Buddy*)item->userdata;
				buddy->ShallowRefresh(); // only refresh time-sensitive components
			}		
		}
	}
}

void BuddyList::OnButton(HWND h)
{
	if(h==edit.hWnd && editing==0){ // about to show the editing list
		if(NeedEditRefresh)
			DeepRefreshEdit(0);
	}
	if(h==edit.hWnd && editing==1){
		if(NeedRefresh)
			DeepRefresh(0);
	}

	TelescopingList::OnButton(h);
}

void BuddyList::ClearList()
{
	lipos i;
	for(i=list.items.begin();i!=list.items.end();i++){
		ListItem * item = *i;
		Buddy * buddy = (Buddy*)item->userdata;
		delete buddy;
	}
	list.DeleteAllItems();
}

void BuddyList::DeepRefreshEdit(int memoize)
{
	if(memoize && !editing){
		NeedEditRefresh = 1;
		return;
	} else
		NeedEditRefresh = 0;

	Vector<int> BuddyIds;

#ifdef SUPERUSER
	db.GetAllUsers(BuddyIds);
#else
	db.GetBuddyList(theApp.user.UserId,BuddyIds);
#endif

	int i;

	// first remove buddies that are no longer available
	Vector<int> current;
	editinglist.GetKeys(current);
	for(i=0;i<current.length;i++){
		if(BuddyIds.find(current[i])==-1){ // not subscribable to anymore, remove it
			ListItem * item = editinglist.GetItemByKey(current[i]);
			if(item){
				Buddy * buddy = (Buddy *)item->userdata;
				EditDelete(buddy);
			}
		}
	}

	for(i=0;i<BuddyIds.length;i++){
		ListItem * item = editinglist.GetItemByKey(BuddyIds[i]);
		if(item){ // already in the list
			Buddy * buddy = (Buddy *)item->userdata;
			buddy->DeepRefresh(BuddyIds[i]);
		} else { // not in the list yet, so add it
			Buddy * buddy = new Buddy();
			buddy->DeepRefresh(BuddyIds[i]);
			EditAdd(buddy);
		}
	}

}

void BuddyList::ClearEditList()
{
	lipos i;
	for(i=editinglist.items.begin();i!=editinglist.items.end();i++){
		ListItem * item = *i;
		Buddy * buddy = (Buddy*)item->userdata;
		delete buddy;
	}
	editinglist.DeleteAllItems();
}

void BuddyList::OnCarriageReturn(HWND h)
{
	if(h==editinglist.inplace.hWnd){
		// try to add the buddy specified in the edit box
		std::wstring buddy;
		editinglist.inplace.GetText(buddy);
		int buddyId;
		db.GetUserId(buddy.data(),buddyId);
		if(buddyId==NullUserId){// invalid buddy
			theApp.Alert(L"There is no such username!",location.X,location.Y);
		} else {
			db.AddToBuddyList(theApp.user.UserId,buddyId);
			DeepRefreshEdit();
			DeepRefresh();
			editinglist.inplace.Hide();
			editinglist.inplace.SetText(L"");
			// finally update the map
			theApp.mapwindow.UpdateUser(buddyId);
		}

	}
}

void BuddyList::OnListDeleteItem(WPARAM wParam, LPARAM lParam)
{
	HWND hList = (HWND)wParam;
	ListItem * item = (ListItem *)lParam;

	if(hList==editinglist.hWnd && item){
		Buddy * buddy = (Buddy *)item->userdata;
		if(buddy){
			int buddyId = buddy->record.UserId;
			db.RemoveFromBuddyList(theApp.user.UserId,buddyId);
			ListItem * item2 = list.GetItemByKey(buddyId);
			if(item2){
				Delete((Buddy*)item2->userdata);
			}
			EditDelete(buddy);
			// finally update the map
			theApp.mapwindow.UpdateUser(buddyId);
		}
	}
}

Buddy * BuddyList::GetBuddy(int buddyId)
{
	ListItem * item = list.GetItemByKey(buddyId);
	if(item){
		return (Buddy*)item->userdata;
	} else {
		return NULL;
	}
}

Buddy * BuddyList::GetEditBuddy(int buddyId)
{
	ListItem * item = editinglist.GetItemByKey(buddyId);
	if(item){
		return (Buddy*)item->userdata;
	} else {
		return NULL;
	}
}

void BuddyList::OnCustomNotify(int id, Notification& n)
{
	Buddy * buddy;

	switch(id){
	case bcUser:
		if(EditExists(n.param[0])){
			switch(n.reason){
			case bcLogOn:
				DeepRefresh();
				break;
			}

			// now notify the Buddy objects in both lists

			buddy = GetBuddy(n.param[0]);
			if(buddy){
				// this notification applies to a buddy in the list
				switch(n.reason){
				case bcLogOff:
					Delete(buddy);
					break;
				case bcRemove:
					Delete(buddy);
					break;
				default: // the buddy will handle the rest
					buddy->OnCustomNotify(id,n);
				}
			}

			buddy = GetEditBuddy(n.param[0]);
			if(buddy){
				// this notification applies to a buddy in the edit list
				switch(n.reason){
				case bcRemove:
					EditDelete(buddy);
					break;
				default: // the buddy will handle the rest
					buddy->OnCustomNotify(id,n);
				}
			}
		}
		break;
	}

}

int BuddyList::Exists(int id)
{
	Vector<int> current;
	list.GetKeys(current);
	return (current.find(id)!=-1);
}

int BuddyList::EditExists(int id)
{
	Vector<int> current;
	editinglist.GetKeys(current);
	return (current.find(id)!=-1);
}
