// RouteWindow.cpp
// e.moon 14may99

#include "RouteApp.h"
#include "RouteWindow.h"
#include "MediaRoutingView.h"

#include "DormantNodeWindow.h"

#include "InspectorWindow.h"
#include "GroupInspector.h"
#include "NodeInspector.h"

#include "TipManager.h"

#include <Alert.h>
#include <Autolock.h>
#include <Debug.h>
#include <ScrollView.h>
#include <MenuBar.h>
#include <Menu.h>
#include <MenuItem.h>

#include <Font.h>

#include <algorithm>

#include "debug_tools.h"

__USE_CORTEX_NAMESPACE

// -------------------------------------------------------- //

const char* const		RouteWindow::s_windowName = "Cortex";

const BRect RouteWindow::s_initFrame(100,100,700,550);

const char* const g_aboutText =
"Cortex/Route dr2.0.2\n\n"
"Copyright 1999, The Mead Group, Inc.\n"
"All rights reserved.\n\n"
"Special thanks go to John Ashmun for his assistance on the PowerPC version!\n\n"
"The icons used herein are the property of Be, Inc.\n"
"and are used by permission.";

// -------------------------------------------------------- //
// ctor/dtor
// -------------------------------------------------------- //

RouteWindow::~RouteWindow() {}

RouteWindow::RouteWindow(NodeManager* manager) :
	BWindow(s_initFrame, s_windowName, B_DOCUMENT_WINDOW, 0),
	m_groupInspectorWindow(0) {

	// create the dormant-nodes palette
	m_dormantNodeWindow = new DormantNodeWindow();

	BRect b = Bounds();
		
	// initialize the menu bar: add all menus that target this window
	BMenuBar* pMenuBar = new BMenuBar(b, "menuBar");
	BMenu* pFileMenu = new BMenu("File");
	pFileMenu->AddItem(new BMenuItem("About Cortex/Route...",
		new BMessage(B_ABOUT_REQUESTED)));
	pFileMenu->AddItem(new BSeparatorItem());
	pFileMenu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED)));
	pMenuBar->AddItem(pFileMenu);
	AddChild(pMenuBar);

	// build the routing view		
	b.top = pMenuBar->Frame().bottom+1;
	b.right -= B_V_SCROLL_BAR_WIDTH;
	b.bottom -= B_H_SCROLL_BAR_HEIGHT;
	m_routingView = new MediaRoutingView(
		manager,
		b,
		"routingView");
		
	m_scrollView = new BScrollView(
		"scrollView", m_routingView, B_FOLLOW_ALL_SIDES, 0,
		true, true, B_NO_BORDER);
	AddChild(m_scrollView);
		
	// construct the view menu (which targets the routing view)		
	buildViewMenu(pMenuBar);
	
	// construct the Window menu
	BMenu* windowMenu = new BMenu("Window");
	m_groupInspectorItem = new BMenuItem(
		"Group Inspector",
		new BMessage(M_TOGGLE_GROUP_INSPECTOR));

	windowMenu->AddItem(m_groupInspectorItem);
	pMenuBar->AddItem(windowMenu);
	
	// init inspector positions (may be overwritten by _restoreSettings())
	BRect f = Frame();
	f.left = f.right + 8;
	m_groupInspectorPosition = f.LeftTop();
	m_nodeInspectorBasePosition = m_groupInspectorPosition;
	m_nodeInspectorBasePosition.y += 120;
	
	// fetch settings if available
	app_info ai;
	status_t err = be_app->GetAppInfo(&ai);
	ASSERT(err == B_OK);

	BFile appFile(&ai.ref, B_READ_ONLY);

	_restoreSettings(&appFile);

	// become visible	
	Show();
	
	// display group inspector
	_toggleGroupInspector();
	
//	// create a group inspector
//	m_groupInspectorWindow = new InspectorWindow(
//		manager,
//		this,
//		"Group Inspector",
//		new GroupInspector(manager, 0));
//	BRect f = Frame();
//	f.left = f.right + 8;
//	m_groupInspectorWindow->MoveTo(f.LeftTop());
//	m_groupInspectorWindow->Show();
//
//	
//	// place & display node inspector (+++++)
//	for(list<InspectorWindow*>::iterator it = m_nodeInspectorWindows.begin();
//		it != m_nodeInspectorWindows.end(); ++it) {
//		(*it)->Show();
//	}
}
	
// -------------------------------------------------------- //
// init helpers
// -------------------------------------------------------- //

void RouteWindow::buildViewMenu(BMenuBar* pMenuBar) {
	m_viewMenu = new BMenu("View");
		
	BMenu* pFontMenu = new BMenu("Font");
	populateFontMenu(pFontMenu);
	pFontMenu->SetTargetForItems(m_routingView);
	m_viewMenu->AddItem(pFontMenu);
		
	BMenu* pFontSizeMenu = new BMenu("Font Size");
	populateFontSizeMenu(pFontSizeMenu);
	pFontSizeMenu->SetTargetForItems(m_routingView);
	m_viewMenu->AddItem(pFontSizeMenu);
		
	pMenuBar->AddItem(m_viewMenu);
}

void RouteWindow::populateFontMenu(BMenu* pMenu) {

	// get a sorted list of currently available font types
	list<string> familyList;
	uint32 families = count_font_families();
	for(uint32 n = 0; n < families; n++) {
		font_family family;
		if(get_font_family(n, &family) < B_OK)
			continue;
			
		familyList.push_back(family);
	}
	if(!familyList.size()) {
		PRINT((
			"* populateFontMenu(): no families found!\n"));
		return;
	}
		
	familyList.sort();
		
	for(list<string>::iterator itFamily = familyList.begin();
		itFamily != familyList.end();	itFamily++) {
		
		font_family family;
		strcpy(family, (*itFamily).c_str());

		list<string> styleList;
		BMenu* pFontMenu = new BMenu(family);

		uint32 styles = count_font_styles(family);
			
		for(uint32 n = 0; n < styles; n++) {
			font_style style;
				
			if(get_font_style(family, n, &style) < B_OK)
				continue;
					
			styleList.push_back(style);
		}
			
		if(!styleList.size())
			continue;
				
		styleList.sort();

		string defStyle;
		for(list<string>::iterator itStyle = styleList.begin();
			itStyle != styleList.end(); itStyle++) {
				
			BMessage* pFontSelect = new BMessage(
				MediaRoutingView::M_SET_FONT);
			pFontSelect->AddString("family", family);
			pFontSelect->AddString("style", (*itStyle).c_str());
				
			pFontMenu->AddItem(
				new BMenuItem((*itStyle).c_str(), pFontSelect));
				
			// see if this style looks like a good default:
			if(*itStyle == "Regular" && !defStyle.size())
				defStyle = *itStyle;
			else if(*itStyle == "Roman" && !defStyle.size())
				defStyle = *itStyle;
		}
			
		// figure default style
		if(!defStyle.size())
			defStyle = styleList.front();
			
		BMessage* pDefSelect = new BMessage(
			MediaRoutingView::M_SET_FONT);
		pDefSelect->AddString("family", family);
		pDefSelect->AddString("style", defStyle.c_str());
			
		// add item w/ submenu to the menu:
		pFontMenu->SetTargetForItems(m_routingView);
		pMenu->AddItem(new BMenuItem(pFontMenu, pDefSelect));
	}
}

void RouteWindow::populateFontSizeMenu(BMenu* pMenu) {
	char buf[16];
	for(float size = 6.0; size <= 14.0; size += (size < 10) ? 1.0 : 2.0) {
		BMessage* pMsg = new BMessage(
			MediaRoutingView::M_SET_FONT);
		pMsg->AddFloat("size", size);
		sprintf(buf, "%.0f", size);
		pMenu->AddItem(new BMenuItem(buf, pMsg));
	}
}


// -------------------------------------------------------- //
// BWindow impl
// -------------------------------------------------------- //

bool RouteWindow::QuitRequested() {

	be_app->PostMessage(B_QUIT_REQUESTED);
	return false; // [e.moon 20oct99] app now quits window
}
	
// -------------------------------------------------------- //
// BHandler impl
// -------------------------------------------------------- //

void RouteWindow::MessageReceived(BMessage* pMsg) {
//	PRINT((
//		"RouteWindow::MessageReceived()\n"));
//	pMsg->PrintToStream();
//	
	switch(pMsg->what) {
		case B_ABOUT_REQUESTED:
			(new BAlert("About", g_aboutText, "Ok"))->Go();
			break;
			
		case MediaRoutingView::M_NODE_SELECTED:
			_handleNodeSelected(pMsg);
			break;

		case MediaRoutingView::M_GROUP_SELECTED:
			_handleGroupSelected(pMsg);
			break;
			
		case M_TOGGLE_GROUP_INSPECTOR:
			_toggleGroupInspector();
			break;
		
		case M_SHOW_NODE_INSPECTOR:
			_showNodeInspector(pMsg);
			break;
			
		case M_REFRESH_TRANSPORT_SETTINGS:
			_refreshTransportSettings(pMsg);
			break;
			
		case M_INSPECTOR_CLOSED:
			_inspectorClosed(pMsg);
			break;
			
		default:
			_inherited::MessageReceived(pMsg);
			break;
	}
}

// -------------------------------------------------------- //
// implementation
// -------------------------------------------------------- //

void RouteWindow::_toggleGroupInspector() {
	if(m_groupInspectorWindow) {
		BMessenger(m_groupInspectorWindow).SendMessage(B_QUIT_REQUESTED);
	}
	else {
		m_groupInspectorWindow = new InspectorWindow(
			m_routingView->manager,
			this,
			"Group Inspector",
			new GroupInspector(m_routingView->manager, 0));
				
		// ask for a selection update
		BMessenger(m_routingView).SendMessage(
			MediaRoutingView::M_BROADCAST_SELECTION);

		// place & display the window
		m_groupInspectorWindow->MoveTo(m_groupInspectorPosition);
		m_groupInspectorWindow->Show();
		m_groupInspectorItem->SetMarked(true);
	}
}

void RouteWindow::_showNodeInspector(
	BMessage*										message) {

	status_t err;
	int32 nodeID;
	
	err = message->FindInt32("nodeID", (int32*)&nodeID);
	if(err < B_OK) {
		PRINT((
			"! RouteWindow::_showNodeInspector(): no nodeID in message.\n"));
		return;
	}
	
	// create a new node inspector
	InspectorWindow* window = 
		new InspectorWindow(
			m_routingView->manager,
			this,
			"Node Inspector",
			new NodeInspector(m_routingView->manager, 0));

	BMessage m(message);
	m.what = NodeInspector::M_SELECT_NODE;
	window->inspector().SendMessage(&m);
	
	m_nodeInspectorWindows.push_back(window);
	window->MoveTo(_nextNodeInspectorPosition());
	window->Show();
}

void RouteWindow::_inspectorClosed(
	BMessage*										message) {
	
	BWindow* window;
	BRect frame;
	status_t err;
	
	err = message->FindPointer("inspector", (void**)&window);
	if(err < B_OK) {
		PRINT((
			"! RouteWindow::_inspectorClosed(): no inspector in message.\n"));
		return;
	}
	err = message->FindRect("frame", &frame);
	if(err < B_OK) {
		PRINT((
			"! RouteWindow::_inspectorClosed(): no frame in message.\n"));
		return;
	}

	ASSERT(window);
	BAutolock _l(this);
	
	if(window == m_groupInspectorWindow) {
		m_groupInspectorPosition = frame.LeftTop();
		m_groupInspectorItem->SetMarked(false);
		m_groupInspectorWindow = 0;
		return;
	}
	
	list<InspectorWindow*>::iterator it = 
		find(
			m_nodeInspectorWindows.begin(),
			m_nodeInspectorWindows.end(),
			window);
	if(it == m_nodeInspectorWindows.end()) {
		PRINT((
			"! RouteWindow::inspectorClosed(): window not found!\n"));
		return;
	}
	
	m_nodeInspectorWindows.erase(it);
	if(!m_nodeInspectorWindows.size())
		m_nodeInspectorBasePosition = frame.LeftTop();
}



void RouteWindow::_handleNodeSelected(
	BMessage*										message) {
	status_t err;
	uint32 nodeID;
	
	err = message->FindInt32("nodeID", (int32*)&nodeID);
	if(err < B_OK) {
		PRINT((
			"! RouteWindow::_handleNodeSelected(): no nodeID in message!\n"));
		return;
	}
	
	// look for an available node-inspector window
	bool foundWindow = false;
	BMessage m(NodeInspector::M_SELECT_NODE);
	m.AddInt32("nodeID", nodeID);
	for(
		list<InspectorWindow*>::iterator it = m_nodeInspectorWindows.begin();
		it != m_nodeInspectorWindows.end(); ++it) {

		NodeInspector* i = dynamic_cast<NodeInspector*>((*it)->view());
		ASSERT(i);

		if(!i->canAcceptNewTarget())
			continue;
			
		foundWindow = true;
		BMessenger(i).SendMessage(&m);
	}
	
	if(!foundWindow) {
		// +++++ spawn a new one
	}
}

void RouteWindow::_handleGroupSelected(
	BMessage*										message) {
	status_t err;
	uint32 groupID;
	
	err = message->FindInt32("groupID", (int32*)&groupID);
	if(err < B_OK) {
		PRINT((
			"! RouteWindow::_handleGroupSelected(): no groupID in message!\n"));
		return;
	}

	if(!m_groupInspectorWindow)
		return;

	BMessage m(GroupInspector::M_SELECT_GROUP);
	m.AddInt32("groupID", groupID);
	m_groupInspectorWindow->inspector().SendMessage(&m);
}

// refresh the transport window for the given group, if any		
void RouteWindow::_refreshTransportSettings(
	BMessage*										message) {

	status_t err;
	uint32 groupID;
	
	err = message->FindInt32("groupID", (int32*)&groupID);
	if(err < B_OK) {
		PRINT((
			"! RouteWindow::_refreshTransportSettings(): no groupID in message!\n"));
		return;
	}

	if(!m_groupInspectorWindow)
		return;

	// relay the message
	m_groupInspectorWindow->inspector().SendMessage(message);
}
		

void RouteWindow::_closeInspectors() {
	BAutolock _l(this);
	
	if(m_groupInspectorWindow) {
		m_groupInspectorWindow->Lock();
		m_groupInspectorWindow->Quit();
		m_groupInspectorWindow = 0;
	}
	
	for(list<InspectorWindow*>::iterator it = m_nodeInspectorWindows.begin();
		it != m_nodeInspectorWindows.end(); ++it) {
		(*it)->Lock();
		(*it)->Quit();
	}
	m_nodeInspectorWindows.clear();
}

BPoint RouteWindow::_nextNodeInspectorPosition() {
	BPoint p = m_nodeInspectorBasePosition + BPoint(
		-4.0 * m_nodeInspectorWindows.size(),
		8.0 * m_nodeInspectorWindows.size());
	return p;
}

// window positions	& font settings are archived
// to attributes

const char* _attr_RouteWindow_frame = "__RouteWindow_frame";
const char* _attr_NodeInspector_position = "__NodeInspector_p";
const char* _attr_GroupInspector_position = "__GroupInspector_p";
const char* _attr_DormantNodeWindow_frame = "__AddOnWindow_frame";

void RouteWindow::_archiveSettings(
	BNode*											node) {
	
	status_t err;
	BRect r;
	BPoint p;
	
	r = Frame();
	err = node->WriteAttr(
		_attr_RouteWindow_frame,
		B_RECT_TYPE,
		0,
		&r,
		sizeof(BRect));
	if(err < B_OK) PRINT(("! node->WriteAttr(): %s\n", strerror(err)));

	if(m_dormantNodeWindow) {
		r = m_dormantNodeWindow->Frame();
		err = node->WriteAttr(
			_attr_DormantNodeWindow_frame,
			B_RECT_TYPE,
			0,
			&r,
			sizeof(BRect));
		if(err < B_OK) PRINT(("! node->WriteAttr(): %s\n", strerror(err)));
	}
	
	p = m_groupInspectorWindow ?
		m_groupInspectorWindow->Frame().LeftTop() :
		m_groupInspectorPosition;
		
	err = node->WriteAttr(
		_attr_GroupInspector_position,
		B_POINT_TYPE,
		0,
		&p,
		sizeof(BPoint));
	if(err < B_OK) PRINT(("! node->WriteAttr(): %s\n", strerror(err)));

	// for NodeInspectors, use most recently opened window's position
	p = m_nodeInspectorWindows.size() ?
		m_nodeInspectorWindows.back()->Frame().LeftTop() :
		m_nodeInspectorBasePosition;

	err = node->WriteAttr(
		_attr_NodeInspector_position,
		B_POINT_TYPE,
		0,
		&p,
		sizeof(BPoint));
	if(err < B_OK) PRINT(("! node->WriteAttr(): %s\n", strerror(err)));
}

void RouteWindow::_restoreSettings(
	BNode*											node) {

	status_t err;
	BRect r;
	
	err = node->ReadAttr(
		_attr_RouteWindow_frame,
		0, 0,
		&r,
		sizeof(BRect));

	if(err >= B_OK) {
		MoveTo(r.LeftTop());
		ResizeTo(r.Width(), r.Height());
	}
	
	err = node->ReadAttr(
		_attr_DormantNodeWindow_frame,
		0, 0,
		&r,
		sizeof(BRect));

	if(err >= B_OK && m_dormantNodeWindow) {
		m_dormantNodeWindow->MoveTo(r.LeftTop());
		m_dormantNodeWindow->ResizeTo(r.Width(), r.Height());
	}
	
	node->ReadAttr(
		_attr_GroupInspector_position,
		0, 0,
		&m_groupInspectorPosition,
		sizeof(BPoint));

//	if(err >= B_OK) {
//		PRINT(("groupInspector: %.1f,%.1f\n",
//			m_groupInspectorPosition.x, m_groupInspectorPosition.y));
//	}
	
	node->ReadAttr(
		_attr_NodeInspector_position,
		0, 0,
		&m_nodeInspectorBasePosition,
		sizeof(BPoint));
}


// END -- RouteWindow.cpp --
