/*
	AppServer.cpp
		File for the main app_server thread. This particular thread monitors for
		application start and quit messages. It also starts the housekeeping threads
		and initializes most of the server's globals.
*/
#include <Message.h>
#include <AppDefs.h>
#include "scheduler.h"
#include <iostream.h>
#include <stdio.h>
#include <OS.h>
#include <Debug.h>
#include "AppServer.h"
#include "FontServer.h"
#include "Layer.h"
#include "ServerApp.h"
#include "ServerWindow.h"
#include "Desktop.h"
#include "ServerProtocol.h"
#include "PortLink.h"
#include "DisplayDriver.h"
#include "DebugTools.h"
#include "BeDecorator.h"

//#define DEBUG_APPSERVER_THREAD
//#define DEBUG_POLLER_THREAD

// This bad boy holds only the top layer for each workspace
//BList *layerlist;

// Used for mediating access to the layer lists
//BLocker *layerlock;

// Used for access via instantiate_decorator
AppServer *app_server;

AppServer::AppServer(void) : BApplication("application/x-vnd.obe-OBAppServer")
{
#ifdef DEBUG_APPSERVER_THREAD
	printf("AppServer()\n");
#endif

	mouseport=create_port(100,SERVER_INPUT_PORT);
	messageport=create_port(100,SERVER_PORT_NAME);
#ifdef DEBUG_APPSERVER_THREAD
printf("Server message port: %ld\n",messageport);
printf("Server input port: %ld\n",mouseport);
#endif
	applist=new BList(0);
	quitting_server=false;
	exit_poller=false;

	fontserver=new FontServer;
	fontserver->Lock();
	fontserver->ScanDirectory("/boot/beos/etc/fonts/ttfonts/");
//	fontserver->ScanDirectory("/boot/beos/etc/fonts/PS-Type1/");
//	fontserver->ScanDirectory("/boot/home/config/fonts/ttfonts/");
//	fontserver->ScanDirectory("/boot/home/config/fonts/psfonts/");
	fontserver->SaveList();

	if(!fontserver->SetSystemPlain("Swis721 BT","Roman",12))
		printf("Couldn't set plain to Swis721 BT, Roman 12 pt\n");
	if(!fontserver->SetSystemBold("Dutch801 Rm BT","Bold",12))
		printf("Couldn't set bold to Dutch801 Rm BT, Bold 12 pt\n");
	if(!fontserver->SetSystemFixed("Courier10 BT","Roman",12))
		printf("Couldn't set fixed to Courier10 BT, Roman 12 pt\n");
	fontserver->Unlock();

	// Set up the Desktop
	init_desktop(3);

	// This is necessary to mediate access between the Poller and app_server threads
	active_lock=new BLocker;

	// This locker is for app_server and Picasso to vy for control of the ServerApp list
	applist_lock=new BLocker;

	// This locker is to mediate access to the make_decorator pointer
	decor_lock=new BLocker;
	
	// Get the driver first - Poller thread utilizes the thing
	driver=get_gfxdriver();

	// Spawn our input-polling thread
	poller_id = spawn_thread(PollerThread, "Poller", B_NORMAL_PRIORITY, this);
	if (poller_id >= 0)
		resume_thread(poller_id);

	// Spawn our thread-monitoring thread
	picasso_id = spawn_thread(PicassoThread,"Picasso", B_NORMAL_PRIORITY, this);
	if (picasso_id >= 0)
		resume_thread(picasso_id);

	active_app=-1;
	p_active_app=NULL;

	make_decorator=NULL;	
//	layerlock=new BLocker;
//	layerlist=new BList(1);
	LoadGUIColors();
}

AppServer::~AppServer(void)
{
	shutdown_desktop();

	ServerApp *tempapp;
//	Layer *templayer;
	int32 i;
	applist_lock->Lock();
	for(i=0;i<applist->CountItems();i++)
	{
		tempapp=(ServerApp *)applist->ItemAt(i);
		if(tempapp!=NULL)
			delete tempapp;
	}
	delete applist;
	applist_lock->Unlock();

/*	layerlock->Lock();	
	for(i=0;i<layerlist->CountItems();i++)
	{
		templayer=(Layer *)layerlist->ItemAt(i);
		
		if(templayer!=NULL)
		{
			templayer->PruneTree();
			delete templayer;
		}
	}
	delete layerlist;
	layerlock->Unlock();

	delete layerlock;
*/	delete active_lock;
	delete applist_lock;

	// If these threads are still running, kill them - after this, if exit_poller
	// is deleted, who knows what will happen... These things will just return an
	// error and fail if the threads have already exited.
	kill_thread(poller_id);
	kill_thread(picasso_id);

	delete decor_lock;
	delete fontserver;
	
	make_decorator=NULL;
}

thread_id AppServer::Run(void)
{
#ifdef DEBUG_APPSERVER_THREAD
printf("AppServer::Run()\n");
#endif	

	MainLoop();
	return 0;
}

void AppServer::MainLoop(void)
{
	// Main loop (duh). Monitors the message queue and dispatches as appropriate.
	// Input messages are handled by the poller, however.

#ifdef DEBUG_APPSERVER_THREAD
printf("AppServer::MainLoop()\n");
#endif

	int32 msgcode;
	int8 *msgbuffer=NULL;
	ssize_t buffersize,bytesread;
	
	for(;;)
	{
		buffersize=port_buffer_size(messageport);
		if(buffersize>0)
			msgbuffer=new int8[buffersize];
		bytesread=read_port(messageport,&msgcode,msgbuffer,buffersize);

		if (bytesread != B_BAD_PORT_ID && bytesread != B_TIMED_OUT && bytesread != B_WOULD_BLOCK)
		{
			switch(msgcode)
			{
				case CREATE_APP:
				case DELETE_APP:
				case GET_SCREEN_MODE:
				case B_QUIT_REQUESTED:
				case UPDATED_CLIENT_FONTLIST:
				case QUERY_FONTS_CHANGED:
					DispatchMessage(msgcode,msgbuffer);
					break;
				default:
				{
#ifdef DEBUG_APPSERVER_THREAD
printf("Server received unexpected code %ld\n",msgcode);
#endif
					break;
				}
			}

		}

		if(buffersize>0)
			delete msgbuffer;
		if(msgcode==DELETE_APP || msgcode==B_QUIT_REQUESTED)
		{
			if(quitting_server==true && applist->CountItems()==0)
				break;
		}
	}
	// Make sure our polling thread has exited
	if(find_thread("Poller")!=B_NAME_NOT_FOUND)
		kill_thread(poller_id);

}


void AppServer::DispatchMessage(int32 code, int8 *buffer)
{
	int8 *index=buffer;
	switch(code)
	{
		case CREATE_APP:
		{
			// Create the ServerApp to node monitor a new BApplication
			
#ifdef DEBUG_APPSERVER_THREAD
printf("AppServer: Create App\n");
#endif
			
			// Attached data:
			// 1) port_id - port to reply to
			// 2) port_id - receiver port of a regular app
			// 3) char * - signature of the regular app

			// Find the necessary data
			port_id reply_port=*((port_id*)index); index+=sizeof(port_id);
#ifdef DEBUG_APPSERVER_THREAD
printf("\tCreate App: reply port is %lu\n",reply_port);
#endif
			port_id app_port=*((port_id*)index); index+=sizeof(port_id);
#ifdef DEBUG_APPSERVER_THREAD
printf("\tCreate App: ServerApp port is %lu\n", app_port);
#endif

			char *app_signature=(char *)index;
#ifdef DEBUG_APPSERVER_THREAD
printf("\tCreate App: Signature received is %s\n", app_signature);
#endif
			
			// Create the ServerApp subthread for this app
			applist_lock->Lock();
			ServerApp *newapp=new ServerApp(app_port,app_signature);
			applist->AddItem(newapp);
			applist_lock->Unlock();

			active_lock->Lock();
			p_active_app=newapp;
			active_app=applist->CountItems()-1;

			PortLink *replylink=new PortLink(reply_port);
			replylink->SetOpCode(SET_SERVER_PORT);
			replylink->Attach((int32)newapp->receiver);
			replylink->Flush();

			delete replylink;

			active_lock->Unlock();
			
			newapp->Run();

			break;
		}
		case DELETE_APP:
		{
			// Delete a ServerApp. Received only from the respective ServerApp when a
			// BApplication asks it to quit.
			
#ifdef DEBUG_APPSERVER_THREAD
printf("AppServer: Delete App\n");
#endif
			
			// Attached Data:
			// 1) thread_id - thread ID of the ServerApp to be deleted
			
			int32 i, appnum=applist->CountItems();
			ServerApp *srvapp;
			thread_id srvapp_id=*((thread_id*)buffer);
			
			// Run through the list of apps and nuke the proper one
			for(i=0;i<appnum;i++)
			{
				srvapp=(ServerApp *)applist->ItemAt(i);

				if(srvapp!=NULL && srvapp->monitor_thread==srvapp_id)
				{
					applist_lock->Lock();
					srvapp=(ServerApp *)applist->RemoveItem(i);
					if(srvapp)
					{
						delete srvapp;
						srvapp=NULL;
					}
					applist_lock->Unlock();
					active_lock->Lock();

					if(applist->CountItems()==0)
					{
						// active==-1 signifies that no other apps are running - NOT good
						active_app=-1;
					}
					else
					{
						// we actually still have apps running, so make a new one active
						if(active_app>0)
							active_app--;
						else
							active_app=0;
					}
					p_active_app=(active_app>-1)?(ServerApp*)applist->ItemAt(active_app):NULL;
					active_lock->Unlock();
					break;	// jump out of our for() loop
				}

			}
			break;
		}
		case UPDATED_CLIENT_FONTLIST:
		{
			// received when the client-side global font list has been
			// refreshed
			fontserver->Lock();
			fontserver->FontsUpdated();
			fontserver->Unlock();
			break;
		}
		case QUERY_FONTS_CHANGED:
		{
			// Client application is asking if the font list has changed since
			// the last client-side refresh

			// Attached data:
			// 1) port_id reply port
			
			fontserver->Lock();
			bool needs_update=fontserver->FontsNeedUpdated();
			fontserver->Unlock();
			
			PortLink *pl=new PortLink(*((port_id*)index));
			pl->SetOpCode( (needs_update)?SERVER_TRUE:SERVER_FALSE );
			pl->Flush();
			delete pl;
			
			break;
		}
		case GET_SCREEN_MODE:
		{
			// Synchronous message call to get the stats on the current screen mode
			// in the app_server. Simply a hack in place for the Input Server until
			// BScreens are done.
			
#ifdef DEBUG_APPSERVER_THREAD
printf("AppServer: Get Screen Mode\n");
#endif
			// Attached Data:
			// 1) port_id - port to reply to
			
			// Returned Data:
			// 1) int32 width
			// 2) int32 height
			// 3) int depth
			
			PortLink *replylink=new PortLink(*((port_id*)index));
			replylink->SetOpCode(GET_SCREEN_MODE);
			replylink->Attach((int16)driver->GetWidth());
			replylink->Attach((int16)driver->GetHeight());
			replylink->Attach((int16)driver->GetDepth());
			replylink->Flush();
			delete replylink;
			break;
		}
		case B_QUIT_REQUESTED:
		{
#ifdef DEBUG_APPSERVER_THREAD
printf("Shutdown sequence initiated.\n");
#endif

			// Attached Data:
			// none
			
			// We've been asked to quit, so (for now) broadcast to all
			// test apps to quit. This situation will occur only when the server
			// is compiled as a regular Be application.
			PortLink *applink=new PortLink(0);
			ServerApp *srvapp;

			applist_lock->Lock();
			int32 i,appnum=applist->CountItems();
			
			applink->SetOpCode(QUIT_APP);
			for(i=0;i<appnum;i++)
			{
				srvapp=(ServerApp *)applist->ItemAt(i);
				if(srvapp!=NULL)
				{
					applink->SetPort(srvapp->receiver);
					applink->Flush();
				}
			}
			applist_lock->Unlock();

			delete applink;
			// So when we delete the last ServerApp, we can exit the server
			quitting_server=true;
			exit_poller=true;
			break;
		}
		default:
			//printf("Unexpected message %ld (%lx) in AppServer::DispatchMessage()\n",code,code);
			break;
	}
}

bool AppServer::LoadDecorator(BString path)
{
	// Loads a window decorator based on the supplied path and forces a decorator update.
	// If it cannot load the specified decorator, it will retain the current one and
	// return false.
	
	create_decorator *pcreatefunc=NULL;
	get_version *pversionfunc=NULL;
	status_t stat;
	image_id addon;
	
	addon=load_add_on(path.String());

	stat=get_image_symbol(addon, "get_decorator_version", B_SYMBOL_TYPE_TEXT, (void**)&pversionfunc);
	if(stat!=B_OK)
	{
		printf("ERROR: Could not get version for Decorator %s\n", path.String());
		unload_add_on(addon);
		return false;
	}

	// As of now, we do nothing with decorator versions, but the possibility exists
	// that the API will change even though I cannot forsee any reason to do so. If
	// we *did* do anything with decorator versions, the assignment to a global would
	// go here.
		
	stat=get_image_symbol(addon, "instantiate_decorator", B_SYMBOL_TYPE_TEXT, (void**)&pversionfunc);
	if(stat!=B_OK)
	{
		printf("ERROR: Could not get allocator for Decorator %s\n", path.String());
		unload_add_on(addon);
		return false;
	}
	decor_lock->Lock();
	make_decorator=pcreatefunc;
	decorator_id=addon;
	decor_lock->Unlock();
	return true;
}

void AppServer::LoadDefaultDecorator(void)
{
	// This function is called on server startup to provide a fallback decorator in
	// case the decorator specified in the config file cannot be loaded. It just
	// wouldn't do to have windows with no borders. ;) It also unloads any loaded
	// decorator addons. We can specify that we want the default. It is no less
	// functional - just internal.
	
}

Decorator *instantiate_decorator(BRect rect, const char *title, int32 wlook, int32 wfeel, int32 wflags,
	DisplayDriver *ddriver)
{
	Decorator *decor=NULL;

	app_server->LockDecorator();
	if(app_server->make_decorator!=NULL)
		decor=app_server->make_decorator(rect,wlook,wfeel,wflags);
	else
		decor=new BeDecorator(rect,title,wlook,wfeel,wflags,ddriver);

	decor->SetDriver(get_gfxdriver());
	app_server->UnlockDecorator();
	return decor;
}

void AppServer::Poller(void)
{
	// This thread handles nothing but input messages for mouse and keyboard
	int32 msgcode;
	int8 *msgbuffer=NULL,*index;
	ssize_t buffersize,bytesread;
	
	for(;;)
	{
		buffersize=port_buffer_size(mouseport);
		if(buffersize>0)
			msgbuffer=new int8[buffersize];
		bytesread=read_port(mouseport,&msgcode,msgbuffer,buffersize);

		if (bytesread != B_BAD_PORT_ID && bytesread != B_TIMED_OUT && bytesread != B_WOULD_BLOCK)
		{
			switch(msgcode)
			{
				// We don't need to do anything with these two, so just pass them
				// onto the active application. Eventually, we will pass them onto
				// the window which is currently under the cursor.
				case B_MOUSE_DOWN:
				{
					if(!msgbuffer)
					{
#ifdef DEBUG_POLLER_THREAD
printf("Poller: MouseDown() - Empty buffer\n");
#endif
						ServerWindow::HandleMouseEvent(msgcode,msgbuffer);
						break;
					}

#ifdef DEBUG_POLLER_THREAD
printf("Poller: MouseDown\n");
#endif
					ServerWindow::HandleMouseEvent(msgcode,msgbuffer);
					break;
				}
				case B_MOUSE_UP:
				{
					if(!msgbuffer)
					{
#ifdef DEBUG_POLLER_THREAD
printf("Poller: MouseUp() - Empty buffer\n");
#endif
						break;
					}
/*
					active_lock->Lock();
					if(active_app>-1 && p_active_app!=NULL)
						write_port(p_active_app->receiver, msgcode,msgbuffer,buffersize);
					active_lock->Unlock();
*/
#ifdef DEBUG_POLLER_THREAD
printf("Poller: MouseUp\n");
#endif
					ServerWindow::HandleMouseEvent(msgcode,msgbuffer);
					break;
				}
				
				// Process the cursor and then pass it on
				case B_MOUSE_MOVED:
				{
					// Attached data:
					// 1) int64 - time of mouse click
					// 2) float - x coordinate of mouse click
					// 3) float - y coordinate of mouse click
					// 4) int32 - buttons down

					index=msgbuffer;
					if(!index)
					{
#ifdef DEBUG_POLLER_THREAD
printf("Poller: MouseMoved() - Empty buffer\n");
#endif
						break;
					}

					// Time sent is not necessary for cursor processing.
					index += sizeof(int64);
					
					float tempx=0,tempy=0;
					tempx=*((float*)index);
					index+=sizeof(float);
					tempy=*((float*)index);
					index+=sizeof(float);
#ifdef DEBUG_POLLER_THREAD
printf("Poller: MouseMoved(%f,%f)\n",tempx,tempy);
#endif
					driver->MoveCursorTo(tempx,tempy);

/*					active_lock->Lock();
					if(active_app>-1 && p_active_app!=NULL)
						write_port(p_active_app->receiver, msgcode,msgbuffer,buffersize);
					active_lock->Unlock();
*/
					ServerWindow::HandleMouseEvent(msgcode,msgbuffer);
					break;
				}
				default:
					//printf("Server::Poller received unexpected code %ld\n",msgcode);
					break;
			}

		}

		if(buffersize>0)
			delete msgbuffer;

		if(exit_poller)
			break;
	}
}

int32 AppServer::PollerThread(void *data)
{
#ifdef DEBUG_POLLER_THREAD
printf("Starting Poller thread...\n");
#endif
	AppServer *server=(AppServer *)data;
	
	// This won't return until it's told to
	server->Poller();
	
#ifdef DEBUG_POLLER_THREAD
printf("Exiting Poller thread...\n");
#endif
	exit_thread(B_OK);
	return B_OK;
}

void AppServer::Picasso(void)
{
	int32 i;
	ServerApp *app;
	for(;;)
	{
		applist_lock->Lock();
		for(i=0;i<applist->CountItems(); i++)
		{
			app=(ServerApp*)applist->ItemAt(i);
			ASSERT(app!=NULL);
			app->PingTarget();
		}
		applist_lock->Unlock();
		
		// if poller thread has to exit, so do we - I just was too lazy
		// to rename the variable name. ;)
		if(exit_poller)
			break;

		// we do this every other second so as not to suck *too* many CPU cycles
		snooze(2000000);
	}
}

int32 AppServer::PicassoThread(void *data)
{
#ifdef DEBUG_APPSERVER_THREAD
printf("Starting Picasso thread...\n");
#endif
	AppServer *server=(AppServer *)data;
	
	// This won't return until it's told to
	server->Picasso();
	
#ifdef DEBUG_APPSERVER_THREAD
printf("Exiting Picasso thread...\n");
#endif
	exit_thread(B_OK);
	return B_OK;
}

int main( int argc, char** argv )
{
	// There can be only one....
	if(find_port(SERVER_PORT_NAME)!=B_NAME_NOT_FOUND)
		return -1;

	app_server = new AppServer();
	app_server->Run();
	delete app_server;
	return 0;
}
