//------------------------------------------------------------------------------
//	Copyright (c) 2001-2002, OpenBeOS
//
//	Permission is hereby granted, free of charge, to any person obtaining a
//	copy of this software and associated documentation files (the "Software"),
//	to deal in the Software without restriction, including without limitation
//	the rights to use, copy, modify, merge, publish, distribute, sublicense,
//	and/or sell copies of the Software, and to permit persons to whom the
//	Software is furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//	DEALINGS IN THE SOFTWARE.
//
//	File Name:		Desktop.cpp
//	Author:			DarkWyrm <bpmagic@columbus.rr.com>
//	Description:	Functions used to work with workspaces and general desktop stuff
//  
//------------------------------------------------------------------------------
#include "DisplayDriver.h"
#include "Desktop.h"
#include "DesktopClasses.h"
#include "ServerConfig.h"

#include "ViewDriver.h"

#if DISPLAYDRIVER == SCREENDRIVER
#include "ScreenDriver.h"
#endif

#if DISPLAYDRIVER == HWDRIVER
#include "AccelerantDriver.h"
#endif

#include "ServerWindow.h"

//#define DEBUG_DESKTOP

#ifdef DEBUG_DESKTOP
#include <stdio.h>
#endif

//! This namespace encapsulates all globals specifically for the desktop
namespace desktop_private {
	int8 *dragmessage=NULL;
	int32 dragmessagesize=0;
	BLocker draglock,
			layerlock,
			workspacelock;
	BList *screenlist=NULL;
	Screen *activescreen=NULL;
}

//! locks layer access
void lock_layers(void) { desktop_private::layerlock.Lock(); }
//! unlocks layer access
void unlock_layers(void) { desktop_private::layerlock.Unlock(); }
//! locks access to drag and drop data
void lock_dragdata(void) { desktop_private::draglock.Lock(); }
//! unlocks access to drag and drop data
void unlock_dragdata(void) { desktop_private::draglock.Unlock(); }
//! locks access to the workspace list
void lock_workspaces(void) { desktop_private::workspacelock.Lock(); }
//! unlocks access to the workspace list
void unlock_workspaces(void) { desktop_private::workspacelock.Unlock(); }

/*!
	\brief Initializes the desktop for use.
	
	InitDesktop creates all workspaces, starts up the appropriate DisplayDriver, and 
	generally gets everything ready for the server to use.
*/
void InitDesktop(void)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: InitWorkspace\n");
#endif
	desktop_private::screenlist=new BList(0);
	DisplayDriver *tdriver;
	Screen *s=NULL;
	
#ifndef TEST_MODE
	// If we're using the AccelerantDriver for rendering, eventually we will loop through
	// drivers until one can't initialize in order to support multiple monitors. For now,
	// we'll just load one and be done with it.
	tdriver=new AccelerantDriver;

	if(tdriver->Initialize())
	{
		// TODO: figure out how to load the workspace data and do it here.
		// For now, we'll just use 3 workspaces.
		
		s=new Screen(tdriver,3);
		desktop_private::screenlist->AddItem(s);
	}
	else
	{
		// Uh-oh. We don't have a video card. This would be a *bad* thing. Seeing how
		// we can't see anything, we can't do anything, either. It's time to crash now. ;)
		tdriver->Shutdown();
		delete tdriver;
		tdriver=NULL;
#ifdef DEBUG_DESKTOP
printf("\t NULL display driver. OK. We crash now. :P\n");
#endif
	}
#else

#if DISPLAYDRIVER == SCREENDRIVER
	tdriver=new ScreenDriver;
#else
	tdriver=new ViewDriver;
#endif

	if(tdriver->Initialize())
	{
		// TODO: figure out how to load the workspace data and do it here.
		// For now, we'll just use 3 workspaces.
		
		s=new Screen(tdriver,3);
		desktop_private::screenlist->AddItem(s);
	}
	else
	{
		// Uh-oh. We don't have a video card. This would be a *bad* thing. Seeing how
		// we can't see anything, we can't do anything, either. It's time to crash now. ;)
		tdriver->Shutdown();
		delete tdriver;
		tdriver=NULL;
#ifdef DEBUG_DESKTOP
printf("\t NULL display driver. OK. We crash now. :P\n");
#endif
	}
#endif
	
	// Once we utilize multiple monitors, we'll make Screen 0 the active one.
	if(tdriver)
	{
		s->Activate();
		desktop_private::activescreen=s;
		s->SetSpace(0,B_32_BIT_640x480,true);
	}
#ifdef DEBUG_DESKTOP
else
printf("ERROR: NULL display driver\n");
#endif
}

//! Shuts down the graphics subsystem
void ShutdownDesktop(void)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: ShutdownDesktop\n");
#endif
	Screen *s;
	
	for(int32 i=0;i<desktop_private::screenlist->CountItems();i++)
	{
		s=(Screen*)desktop_private::screenlist->ItemAt(i);
		if(s)
			delete s;
	}
	if(!desktop_private::dragmessage)
	{
		delete desktop_private::dragmessage;
		desktop_private::dragmessage=NULL;
		desktop_private::dragmessagesize=0;
	}
}

/*!
	\brief Add a workspace to the desktop
	\param index Place in the workspace list to add the new one.
	
	The default location to add a workspace is at the end of the list. If index 
	is less than 0 or greater than the current workspace count, AddWorkspace will place 
	the workspace at the list's end. Note that it is possible to insert a workspace 
	in the list simply by specifying an index. 
*/
void AddWorkspace(int32 index)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: AddWorkspace(%ld)\n",index+1);
#endif
	lock_workspaces();
	lock_layers();
	
	Screen *s;
	
	for(int32 i=0;i<desktop_private::screenlist->CountItems();i++)
	{
		s=(Screen*)desktop_private::screenlist->ItemAt(i);
		if(s)
			s->AddWorkspace(index);
	}
	
	unlock_layers();
	unlock_workspaces();
}

/*!
	\brief Deletes a workspace
	\param index The index to delete
	
	If the workspace specified by index does not refer to a valid workspace, this function 
	will fail. Should the workspace to be deleted be the active one, the previous 
	workspace in the list will be chosen or workspace 0 if workspace 0 is being deleted. 
	This function will fail if there is only 1 workspace used.
*/
void DeleteWorkspace(int32 index)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: DeleteWorkspace(%ld)\n",index+1);
#endif
	lock_workspaces();
	lock_layers();
	
	Screen *s;
	
	for(int32 i=0;i<desktop_private::screenlist->CountItems();i++)
	{
		s=(Screen*)desktop_private::screenlist->ItemAt(i);
		if(s)
			s->DeleteWorkspace(index);
	}
	
	unlock_layers();
	unlock_workspaces();
}

/*!
	\brief Returns the number of workspaces available
	\return the number of workspaces available
*/
int32 CountWorkspaces(void)
{
	lock_workspaces();
	lock_layers();
	
	int32 count=desktop_private::activescreen->CountWorkspaces();

	unlock_layers();
	unlock_workspaces();

	return count;
}

/*!
	\brief Sets the number of workspaces available
	\param count The new number of available workspaces
	
	This function will fail if count is less than 1 or greater than 32. The limit of 32 was 
	set by BeOS. Making the limit higher will cause unreachable workspaces because of the 
	API interface used to access them. Were this to change, the only limiting factor for 
	workspace count would be the hardware itself.
	
*/
void SetWorkspaceCount(int32 count)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: SetWorkspaceCount(%ld)\n",count);
#endif
	if(count<1 || count>32)
		return;
	lock_workspaces();
	Screen *scr;
	for(int32 i=0;i<desktop_private::screenlist->CountItems();i++)
	{
		scr=(Screen*)desktop_private::screenlist->ItemAt(i);
		if(!scr)
			continue;
		scr->SetWorkspaceCount(count);
	}
	unlock_workspaces();
}

/*!
	\brief Returns the index of the active workspace
	\return the index of the active workspace or -1 if an internal error occurred.
*/
int32 CurrentWorkspace(void)
{
	lock_workspaces();
	int32 id=-1;

	if(desktop_private::activescreen)
		id=desktop_private::activescreen->CurrentWorkspace();
	unlock_workspaces();

	return id;
}

/*!
	\brief Returns the workspace object for the active screen at the given index
*/
Workspace *WorkspaceAt(int32 index)
{
	lock_workspaces();
	Workspace *w=NULL;
	if(desktop_private::activescreen)
		w=desktop_private::activescreen->GetWorkspace(index);
	unlock_workspaces();
	return w;
}

/*!
	\brief Sets the active workspace
	\param workspace Index of the workspace to activate
	
	This function will do nothing if passed an index which does not refer to a valid 
	workspace.
*/
void SetWorkspace(int32 workspace)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: SetWorkspace(%ld)\n",workspace+1);
#endif
	lock_workspaces();
	if(workspace<0 || workspace>(CountWorkspaces()-1))
	{
		unlock_workspaces();
		return;
	}

	Screen *scr;
	for(int32 i=0;i<desktop_private::screenlist->CountItems();i++)
	{
		scr=(Screen*)desktop_private::screenlist->ItemAt(i);
		if(!scr)
			continue;
		scr->SetWorkspace(workspace);
	}
	unlock_workspaces();
}

/*!
	\brief Sets the active screen. Currently unused.
	\param id ID of the screen to activate.
*/
void SetScreen(screen_id id)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: SetScreen(%ld)\n",id.id);
#endif
	Screen *scr;
	for(int32 i=0;i<desktop_private::screenlist->CountItems();i++)
	{
		scr=(Screen*)desktop_private::screenlist->ItemAt(i);
		if(!scr)
			continue;
		if(scr->GetID().id==id.id)
		{
			desktop_private::activescreen->Activate(false);
			desktop_private::activescreen=scr;
			desktop_private::activescreen->Activate(true);
		}
	}
}

/*!
	\brief Returns the number of screens available. Currently unused.
	\return the number of screens available.
*/
int32 CountScreens(void)
{
	return desktop_private::screenlist->CountItems();;
}

/*!
	\brief Returns the index of the active screen. Currently unused.
	\return the index of the active screen
*/
screen_id ActiveScreen(void)
{
	return desktop_private::activescreen->GetID();
}

/*!
	\brief Obtains the DisplayDriver object used for the specified screen.
	\param screen ID of the screen to get the DisplayDriver for.
	\return The DisplayDriver or NULL if not available for that screen ID
	
	Like the other multiscreen functions, this is unused. Because multiple screens are 
	not utilized, specifying an ID other than B_MAIN_SCREEN_ID will return NULL 
*/
DisplayDriver *GetGfxDriver(screen_id screen)
{
	return (desktop_private::activescreen)?desktop_private::activescreen->GetGfxDriver():NULL;
}

/*!
	\brief Sets the video parameters of the specified workspace
	\param index The workspace to change
	\param res Resolution constant from GraphicsDefs.h
	\param stick If false, resolution will revert to its old value on next boot
	\param screen ID of the screen to change. Currently unused.
	\return B_OK if successful, B_ERROR if not.
	
	Because of the lack of outside multimonitor support, the screen ID is ignored for now.
*/
status_t SetSpace(int32 index, int32 res, screen_id screen, bool stick)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: SetSpace(%ld,%ld,%s,%ld)\n",index+1,res,
	stick?"stick":"non-stick",screen.id);
#endif
	desktop_private::workspacelock.Lock();
	status_t stat=desktop_private::activescreen->SetSpace(index,res,stick);
	desktop_private::workspacelock.Unlock();
	return stat;
}

/*!
	\brief Adds a window to the desktop so that it will be displayed
	\param win Window to add
	\param workspace index of the workspace to add the window to
	\param screen Optional screen specifier. Currently unused.
*/
void AddWindowToDesktop(ServerWindow *win, int32 workspace, screen_id screen)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: AddWindowToDesktop(%s,%ld,%ld)\n",win?win->GetTitle():"NULL",
	workspace+1,screen.id);
#endif
	// Workspace() will be non-NULL if it has already been added to the desktop
	if(!win || win->GetWorkspace())
		return;
	
	desktop_private::workspacelock.Lock();
	desktop_private::layerlock.Lock();

	Workspace *w=desktop_private::activescreen->GetActiveWorkspace();
	win->SetWorkspace(w);
	desktop_private::activescreen->AddWindow(win,workspace);
	
	desktop_private::layerlock.Unlock();
	desktop_private::workspacelock.Unlock();
}

/*!
	\brief Removes a window from the desktop
	\param win Window to remove
*/
void RemoveWindowFromDesktop(ServerWindow *win)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: RemoveWindowFromDesktop(%s)\n",win?win->GetTitle():"NULL");
#endif
	lock_workspaces();
	lock_layers();
	
	win->SetWorkspace(NULL);
	desktop_private::activescreen->RemoveWindow(win);
	
	unlock_layers();
	unlock_workspaces();
}

/*!
	\brief Returns the active window in the current workspace of the active screen
	\return The active window in the current workspace of the active screen
*/
ServerWindow *GetActiveWindow(void)
{
	lock_workspaces();
	ServerWindow *w=desktop_private::activescreen->ActiveWindow();
	unlock_workspaces();

	return w;
}

/*!
	\brief Sets the active window in the current workspace of the active screen
	\param win The window to activate
	
	If the window is not in the current workspace of the active screen, this call fails
*/
void SetActiveWindow(ServerWindow *win)
{
#ifdef DEBUG_DESKTOP
printf("Desktop: SetActiveWindow(%s)\n",win?win->GetTitle():"NULL");
#endif
	lock_workspaces();
	Workspace *w=desktop_private::activescreen->GetActiveWorkspace();
	if(win->GetWorkspace()!=w)
	{
		desktop_private::workspacelock.Unlock();
		return;
	}
	
	ServerWindow *oldwin=desktop_private::activescreen->ActiveWindow();
	ActivateWindow(oldwin,win);
	
	unlock_workspaces();
}

/*!
	\brief Returns the root layer for the specified workspace
	\param workspace Index of the workspace to use
	\param screen Index of the screen to use. Currently ignored.
	\return The root layer or NULL if there was an invalid parameter.
*/
Layer *GetRootLayer(int32 workspace, screen_id screen)
{
	lock_workspaces();
	lock_layers();
	Layer *r=desktop_private::activescreen->GetRootLayer();
	unlock_layers();
	unlock_workspaces();

	return r;
}

/*!
	\brief Assigns a flattened BMessage to the global drag data.
	\param size Size of the data buffer
	\param flattened Flattened BMessage data buffer
	
	This assigns a BMessage in its flattened state to the internal storage. Only 
	one message can be stored at a time. Once an assignment is made, another cannot 
	be made until the current one is emptied. If additional calls are made while 
	there is a drag message assigned, the function will fail and the new assignment 
	will not be made. Note that this merely passes a pointer around. No copies are 
	made and the caller is responsible for freeing any allocated objects from the heap.
*/
void set_drag_message(int32 size, int8 *flattened)
{
	lock_dragdata();

	if(desktop_private::dragmessage)
	{
		desktop_private::draglock.Unlock();
		return;
	}
	desktop_private::dragmessage=flattened;
	desktop_private::dragmessagesize=size;

	unlock_dragdata();
}

/*!
	\brief Gets the current drag data
	\param size Receives the size of the drag data buffer
	\return the drag data buffer or NULL if size is NULL
	
	Retrieve the current drag message in use. This function will fail if a NULL pointer 
	is passed to it. Note that this does not free the current drag message from use. 
	The size of the flattened message is stored in the size parameter. Also, note that if 
	there is no message in use, it will return NULL and set size to 0.
*/
int8 *get_drag_message(int32 *size)
{
	int8 *ptr=NULL;
	
	lock_dragdata();

	if(desktop_private::dragmessage)
	{
		ptr=desktop_private::dragmessage;
		*size=desktop_private::dragmessagesize;
	}
	unlock_dragdata();
	return ptr;
}

//! Empties current drag data and allows for new data to be assigned
void empty_drag_message(void)
{
	lock_dragdata();

	if(desktop_private::dragmessage)
		delete desktop_private::dragmessage;
	desktop_private::dragmessage=NULL;
	desktop_private::dragmessagesize=0;

	unlock_dragdata();
}

