#include "StdAfx.h"
#include "grouplist.h"
#include "resource.h"

GroupList::GroupList(void)
{
	NeedRefresh = 0;
	NeedEditRefresh = 0;
	nActiveGroups = 0;
}

GroupList::~GroupList(void)
{
}

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

	// load the item appearances for the active group list
	Active.Load("groupwindow::styles::active");
	Idle.Load("groupwindow::styles::idle");

	// load the item appearnaces for the editing list
	Checked.Load("groupwindow::editstyles::checked");
	Unchecked.Load("groupwindow::editstyles::unchecked");

	moveable = 0;
	ShowInTaskBar = 0;
	TelescopingList::Create(parent);

	EmptyStyle.Load("groupwindow::styles::empty");
	EditEmptyStyle.Load("groupwindow::editstyles::empty");
	list.ShowEmptyItem(EmptyStyle.description.data(),EmptyStyle);
	editinglist.ShowEmptyItem(EditEmptyStyle.description.data(),EditEmptyStyle);

	LoadContextMenu(IDR_GROUP_CONTEXTMENU);

	SetTimer(hWnd,0,5000,0);

	// finally request notifications for changes
	AddCustomNotification(bcGroup);

	return 1;
}

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

	Vector<int> groupIds;

#ifdef SUPERUSER
	db.GetAllGroups(groupIds);
#else
	db.GetSubscribedGroups(theApp.user.UserId,groupIds);
#endif

	{
		int i;
		// first remove items that are no longer subscribed to
		Vector<int> current;
		list.GetKeys(current);
		for(i=0;i<current.length;i++){
			if(groupIds.find(current[i])==-1){ // not subscribed to anymore, remove it
				ListItem * item = list.GetItemByKey(current[i]);
				if(item){
					Group * group = (Group *)item->userdata;
					Delete(group);
				}
			}
		}
		for(i=0;i<groupIds.length;i++){
			ListItem * item = list.GetItemByKey(groupIds[i]);
			if(item){ // already in the list, so just refresh it
				Group * group = (Group *)item->userdata;
				group->DeepRefresh(groupIds[i]);
			} else { // not in the list yet, so add it
				Group * group = new Group();
				group->DeepRefresh(groupIds[i]);
				Add(group);
			}
		}
	}

	UpdateActiveCount();

}

// this is a shallow update
void GroupList::UpdateActiveCount()
{
	nActiveGroups = 0;
	std::list<ListItem*>::iterator i;
	for(i=list.items.begin();i!=list.items.end();i++){
		ListItem * item = *i;
		Group * group = (Group*)item->userdata;
		if(!group->record.Idle)
			nActiveGroups++;
	}

	TCHAR buffer[100];
	_stprintf(buffer,L"%d/%d",nActiveGroups,list.items.size());
	expand.text = buffer;
	expand.Redraw();
}

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

	Vector<int> groupIds;
#ifdef SUPERUSER
	db.GetAllGroups(groupIds);
#else
	db.GetSubscribableGroups(theApp.user.UserId,groupIds);
#endif

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

	// now add or refresh the ones that are
	for(i=0;i<groupIds.length;i++){
		ListItem * item = editinglist.GetItemByKey(groupIds[i]);
		if(item){ // already in the list
			// TODO detect changes in the group record and update if necessary
		} else { // not in the list yet, so add it
			Group * group = new Group();
			group->editing = 1;
			// determine if this group is in the subscribed group list (w/o a db lookup)
			if(list.GetItemByKey(groupIds[i]))
				group->checked = 1;
			else
				group->checked = 0;
			group->DeepRefresh(groupIds[i]);
			EditAdd(group);
		}
	}

	UpdateActiveCount();
}

void GroupList::Add(Group * group)
{
	ListItem * item = list.CreateItem(group->record.GroupName,group->record.GroupId);
	group->item = item;
	group->editing = 0;
	group->ShallowRefresh();
	item->userdata = group;
	item->usertype = bcGroup;
	list.AddItem(item);
	UpdateActiveCount();
}

void GroupList::Delete(Group * group)
{
	list.DeleteItem(group->item);
	delete group;
	UpdateActiveCount();
}

void GroupList::EditAdd(Group * group)
{
	ListItem * item = editinglist.CreateItem(group->record.GroupName,group->record.GroupId);
	group->item = item;
	group->editing = 1;
	group->ShallowRefresh();
	item->userdata = group;
	item->usertype = bcGroup;
	editinglist.AddItem(item);
}

void GroupList::EditDelete(Group * group)
{
	editinglist.DeleteItem(group->item);
	delete group;
}


void GroupList::OnListSelect(HWND h, HWND hItem)
{
	ListItem * item = (ListItem*)GetWindowLongPtr(hItem,GWL_USERDATA);
	if(item){
		if(list.hWnd==h){
			// edit/view the group
			Group * group = (Group*)item->userdata;
#ifdef SUPERUSER
			theApp.EditGroup(group->record.GroupId);
#else
			if(group->record.OwnerId==theApp.user.UserId)
				theApp.EditGroup(group->record.GroupId);
			else 
				theApp.ViewGroup(group->record.GroupId);
#endif
		} else if(editinglist.hWnd==h){
#ifdef SUPERUSER

#else
			Group * group = (Group *)item->userdata;
			if(group){
				rpc.BeginTransaction();
				if(group->checked){
					db.UnsubscribeFromGroup(theApp.user.UserId,group->record.GroupId);
					rpc.ServerNotify(bcGroup,group->record.GroupId,bcUnsubscribe,theApp.user.UserId);
					group->checked = 0;
				} else {
					if(group->record.PublicEdit || group->record.OwnerId==theApp.user.UserId){
						db.SubscribeToGroup(theApp.user.UserId,group->record.GroupId);
						rpc.ServerNotify(bcGroup,group->record.GroupId,bcSubscribe,theApp.user.UserId);
						group->checked = 1;
					} else {
						theApp.ViewGroup(group->record.GroupId);
					}
				}
				group->ShallowRefresh();
				DeepRefresh();
				rpc.EndTransaction();
			}
#endif
		}
	}
}

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

		}
	}
}

LRESULT GroupList::DisplayContextMenu(Group * group)
{
	// alter the context menu before displaying
	std::wstring text;
	if(group->record.Idle){
		text = L"Begin Discussion";
	} else {
		text = L"Join Discussion";
	}

	if(!ModifyMenu(hContextMenu,0,MF_STRING|MF_BYPOSITION,ID_GROUP_CHAT,text.data()))
		print(L"Unable to change menu item in Group list's context menu.\n");
	return SkinnableWindow::DisplayContextMenu();
}

void GroupList::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
		Group * group = (Group*)item->userdata;
		switch(DisplayContextMenu(group)){
			case ID_GROUP_CHAT:
				if(group->record.Idle)
					theApp.BeginDiscussion(group->record.GroupId,group->record.GroupName);
				else
					theApp.JoinDiscussion(group->record.GroupId,group->record.GroupName);
				UpdateActiveCount();
				break;
			case ID_GROUP_VIEW:
				theApp.ViewGroup(group->record.GroupId);
				break;
			default:
				print(L"Unknown menu option.\n");
				break;
		}
	} else if(editinglist.hWnd==h){

	}
}

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

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

void GroupList::OnTimer()
{
	lipos i;
	for(i=list.items.begin();i!=list.items.end();i++){
		ListItem * item =  *i;
		if(item){
			Group * group = (Group*)item->userdata;
			group->ShallowRefresh(); // only refresh time-sensitive components
		}		
	}
}

void GroupList::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 GroupList::ClearList()
{
	lipos i;
	for(i=list.items.begin();i!=list.items.end();i++){
		ListItem * item = *i;
		Group * group = (Group*)item->userdata;
		delete group;
	}
	list.DeleteAllItems();
	nActiveGroups = 0;
}

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

void GroupList::OnCarriageReturn(HWND h)
{
	if(h==editinglist.inplace.hWnd){
		// try to add the group specified in the edit box
		std::wstring group;
		editinglist.inplace.GetText(group);
		int groupId;
		db.GetGroupId(group.data(),groupId);
		if(groupId==-1){// invalid group, so pop up the group creation window
			theApp.CreateGroup(group.data());
			editinglist.inplace.Hide();
			editinglist.inplace.SetText(L"");
		} else { // it already exists
			rpc.BeginTransaction();
			db.SubscribeToGroup(theApp.user.UserId,groupId);
			rpc.ServerNotify(bcGroup,groupId,bcModified,theApp.user.UserId);
			DeepRefreshEdit();
			DeepRefresh();
			editinglist.inplace.Hide();
			editinglist.inplace.SetText(L"");
			rpc.EndTransaction();
		}

	}
}

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

	if(hList==editinglist.hWnd && item){
		Group * group = (Group *)item->userdata;
		if(group){
			rpc.BeginTransaction();
			db.UnsubscribeFromGroup(theApp.user.UserId,group->record.GroupId);
			rpc.ServerNotify(bcGroup,group->record.GroupId,bcModified,theApp.user.UserId);
			EditDelete(group);
			DeepRefresh();
			rpc.EndTransaction();
		}
	}
}

void GroupList::OnCustomNotify(int id, Notification& n)
{
	ListItem * item;
	Group * group;

	switch(id){
	case bcGroup:

		switch(n.reason){
		case bcAdd:
			DeepRefresh();
			DeepRefreshEdit();
			break;
		default:
			// for the list
			item = list.GetItemByKey(n.param[0]);
			if(item){
				group = (Group*)item->userdata;
				if(group){
					// this notification applies to a group in the list
					switch(n.reason){
					case bcRemove:
					case bcUnsubscribe:
						if(n.param[1]==theApp.user.UserId)
							Delete(group);
						break;
					case bcSubscribe:
						if(n.param[1]==theApp.user.UserId)
							DeepRefresh();
						break;						
					default: // the group will handle the rest
						group->OnCustomNotify(id,n);
						UpdateActiveCount();
					}
				}
			} else {
				DeepRefresh();
			}
			// for the edit list
			item = editinglist.GetItemByKey(n.param[0]);
			if(item){
				group = (Group*)item->userdata;
				if(group){
					// this notification applies to a group in the list
					switch(n.reason){
					case bcRemove:
						EditDelete(group);
						break;
					default: // the group will handle the rest
						group->OnCustomNotify(id,n);
					}
				}
			} else {
				DeepRefreshEdit();
			}
			break;
		}

		break;
	}
}
