/*
 * Copyright 2003-2015, Haiku, Inc.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *		Sikosis, Jérôme Duval
 *		yourpalal, Alex Wilson
 */
 
 
#include "MediaWindow.h"
 
#include <stdio.h>
 
#include <Application.h>
#include <Autolock.h>
#include <Button.h>
#include <CardLayout.h>
#include <Catalog.h>
#include <Debug.h>
#include <Deskbar.h>
#include <IconUtils.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <MediaRoster.h>
#include <MediaTheme.h>
#include <Resources.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollView.h>
#include <SeparatorView.h>
#include <SpaceLayoutItem.h>
#include <StorageKit.h>
#include <String.h>
#include <TextView.h>
 
#include "Media.h"
#include "MediaIcons.h"
#include "MidiSettingsView.h"
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Media Window"
 
 
const uint32 ML_SELECTED_NODE = 'MlSN';
const uint32 ML_RESTART_THREAD_FINISHED = 'MlRF';
 
 
class NodeListItemUpdater : public MediaListItem::Visitor {
public:
	typedef void (NodeListItem::*UpdateMethod)(bool);
 
	NodeListItemUpdater(NodeListItem* target, UpdateMethod action)
		:
		fComparator(target),
		fAction(action)
	{
	}
 
 
	virtual	void	Visit(AudioMixerListItem*){}
	virtual	void	Visit(DeviceListItem*){}
	virtual	void	Visit(MidiListItem*){}
	virtual void	Visit(NodeListItem* item)
	{
		item->Accept(fComparator);
		(item->*(fAction))(fComparator.result == 0);
	}
 
private:
 
			NodeListItem::Comparator		fComparator;
			UpdateMethod					fAction;
};
 
 
MediaWindow::SmartNode::SmartNode(const BMessenger& notifyHandler)
	:
	fNode(NULL),
	fMessenger(notifyHandler)
{
}
 
 
MediaWindow::SmartNode::~SmartNode()
{
	_FreeNode();
}
 
 
void
MediaWindow::SmartNode::SetTo(const dormant_node_info* info)
{
	_FreeNode();
	if (!info)
		return;
 
	fNode = new media_node();
	BMediaRoster* roster = BMediaRoster::Roster();
 
	status_t status = B_OK;
	media_node_id node_id;
	if (roster->GetInstancesFor(info->addon, info->flavor_id, &node_id) == B_OK)
		status = roster->GetNodeFor(node_id, fNode);
	else
		status = roster->InstantiateDormantNode(*info, fNode, B_FLAVOR_IS_GLOBAL);
 
	if (status != B_OK) {
		fprintf(stderr, "SmartNode::SetTo error with node %" B_PRId32
			": %s\n", fNode->node, strerror(status));
	}
 
	status = roster->StartWatching(fMessenger, *fNode, B_MEDIA_WILDCARD);
	if (status != B_OK) {
		fprintf(stderr, "SmartNode::SetTo can't start watching for"
			" node %" B_PRId32 "\n", fNode->node);
	}
}
 
 
void
MediaWindow::SmartNode::SetTo(const media_node& node)
{
	_FreeNode();
	fNode = new media_node(node);
	BMediaRoster* roster = BMediaRoster::Roster();
	roster->StartWatching(fMessenger, *fNode, B_MEDIA_WILDCARD);
}
 
 
bool
MediaWindow::SmartNode::IsSet()
{
	return fNode != NULL;
}
 
 
MediaWindow::SmartNode::operator media_node()
{
	if (fNode)
		return *fNode;
	media_node node;
	return node;
}
 
 
void
MediaWindow::SmartNode::_FreeNode()
{
	if (!IsSet())
		return;
 
	BMediaRoster* roster = BMediaRoster::Roster();
	if (roster != NULL) {
		status_t status = roster->StopWatching(fMessenger,
			*fNode, B_MEDIA_WILDCARD);
		if (status != B_OK) {
			fprintf(stderr, "SmartNode::_FreeNode can't unwatch"
				" media services for node %" B_PRId32 "\n", fNode->node);
		}
 
		roster->ReleaseNode(*fNode);
		if (status != B_OK) {
			fprintf(stderr, "SmartNode::_FreeNode can't release"
				" node %" B_PRId32 "\n", fNode->node);
		}
	}
	delete fNode;
	fNode = NULL;
}
 
 
// #pragma mark -
 
 
MediaWindow::MediaWindow(BRect frame)
	:
	BWindow(frame, B_TRANSLATE_SYSTEM_NAME("Media"), B_TITLED_WINDOW,
		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
	fCurrentNode(BMessenger(this)),
	fParamWeb(NULL),
	fAudioInputs(5, true),
	fAudioOutputs(5, true),
	fVideoInputs(5, true),
	fVideoOutputs(5, true),
	fInitCheck(B_OK),
	fRestartThread(-1),
	fRestartAlert(NULL)
{
	_InitWindow();
 
	BMediaRoster* roster = BMediaRoster::Roster();
	roster->StartWatching(BMessenger(this, this),
		B_MEDIA_SERVER_STARTED);
	roster->StartWatching(BMessenger(this, this),
		B_MEDIA_SERVER_QUIT);
}
 
 
MediaWindow::~MediaWindow()
{
	_EmptyNodeLists();
	_ClearParamView();
 
	char buffer[512];
	BRect rect = Frame();
	PRINT_OBJECT(rect);
	snprintf(buffer, 512, "# MediaPrefs Settings\n rect = %i,%i,%i,%i\n",
		int(rect.left), int(rect.top), int(rect.right), int(rect.bottom));
 
	BPath path;
	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
		path.Append(SETTINGS_FILE);
		BFile file(path.Path(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
		if (file.InitCheck() == B_OK)
			file.Write(buffer, strlen(buffer));
	}
 
	BMediaRoster* roster = BMediaRoster::CurrentRoster();
	roster->StopWatching(BMessenger(this, this),
		B_MEDIA_SERVER_STARTED);
	roster->StartWatching(BMessenger(this, this),
		B_MEDIA_SERVER_QUIT);
}
 
 
status_t
MediaWindow::InitCheck()
{
	return fInitCheck;
}
 
 
void
MediaWindow::SelectNode(const dormant_node_info* node)
{
	fCurrentNode.SetTo(node);
	_MakeParamView();
	fTitleView->SetLabel(node->name);
}
 
 
void
MediaWindow::SelectAudioSettings(const char* title)
{
	fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fAudioView));
	fTitleView->SetLabel(title);
}
 
 
void
MediaWindow::SelectVideoSettings(const char* title)
{
	fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fVideoView));
	fTitleView->SetLabel(title);
}
 
 
void
MediaWindow::SelectAudioMixer(const char* title)
{
	media_node mixerNode;
	BMediaRoster* roster = BMediaRoster::Roster();
	roster->GetAudioMixer(&mixerNode);
	fCurrentNode.SetTo(mixerNode);
	_MakeParamView();
	fTitleView->SetLabel(title);
}
 
 
void
MediaWindow::SelectMidiSettings(const char* title)
{
	fContentLayout->SetVisibleItem(fContentLayout->IndexOfView(fMidiView));
	fTitleView->SetLabel(title);
}
 
 
void
MediaWindow::UpdateInputListItem(MediaListItem::media_type type,
	const dormant_node_info* node)
{
	NodeListItem compareTo(node, type);
	NodeListItemUpdater updater(&compareTo, &NodeListItem::SetDefaultInput);
	for (int32 i = 0; i < fListView->CountItems(); i++) {
		MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i));
		item->Accept(updater);
	}
	fListView->Invalidate();
}
 
 
void
MediaWindow::UpdateOutputListItem(MediaListItem::media_type type,
	const dormant_node_info* node)
{
	NodeListItem compareTo(node, type);
	NodeListItemUpdater updater(&compareTo, &NodeListItem::SetDefaultOutput);
	for (int32 i = 0; i < fListView->CountItems(); i++) {
		MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i));
		item->Accept(updater);
	}
	fListView->Invalidate();
}
 
 
bool
MediaWindow::QuitRequested()
{
	if (fRestartThread > 0) {
		BString text(B_TRANSLATE("Quitting Media now will stop the "
			"restarting of the media services. Flaky or unavailable media "
			"functionality is the likely result."));
 
		fRestartAlert = new BAlert(B_TRANSLATE("Warning!"), text,
			B_TRANSLATE("Quit anyway"), NULL, NULL,
			B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT);
 
		fRestartAlert->Go();
	}
	// Stop watching the MediaRoster
	fCurrentNode.SetTo(NULL);
	be_app->PostMessage(B_QUIT_REQUESTED);
	return true;
}
 
 
void
MediaWindow::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case ML_RESTART_THREAD_FINISHED:
			fRestartThread = -1;
			_InitMedia(false);
			break;
 
		case ML_RESTART_MEDIA_SERVER:
		{
			fRestartThread = spawn_thread(&MediaWindow::_RestartMediaServices,
				"restart_thread", B_NORMAL_PRIORITY, this);
			if (fRestartThread < 0)
				fprintf(stderr, "couldn't create restart thread\n");
			else
				resume_thread(fRestartThread);
			break;
		}
 
		case B_MEDIA_WEB_CHANGED:
		case ML_SELECTED_NODE:
		{
			PRINT_OBJECT(*message);
 
			MediaListItem* item = static_cast<MediaListItem*>(
					fListView->ItemAt(fListView->CurrentSelection()));
			if (item == NULL)
				break;
 
			fCurrentNode.SetTo(NULL);
			_ClearParamView();
			item->AlterWindow(this);
			break;
		}
 
		case B_MEDIA_SERVER_STARTED:
		case B_MEDIA_SERVER_QUIT:
		{
			PRINT_OBJECT(*message);
			_InitMedia(false);
			break;
		}
 
		default:
			BWindow::MessageReceived(message);
			break;
	}
}
 
 
// #pragma mark - private
 
 
void
MediaWindow::_InitWindow()
{
	fListView = new BListView("media_list_view");
	fListView->SetSelectionMessage(new BMessage(ML_SELECTED_NODE));
	fListView->SetExplicitMinSize(BSize(140, B_SIZE_UNSET));
 
	// Add ScrollView to Media Menu for pretty border
	BScrollView* scrollView = new BScrollView("listscroller",
		fListView, 0, false, false, B_FANCY_BORDER);
 
	// Create the Views
	fTitleView = new BSeparatorView(B_HORIZONTAL, B_FANCY_BORDER);
	fTitleView->SetLabel(B_TRANSLATE("Audio settings"));
	fTitleView->SetFont(be_bold_font);
 
	fContentLayout = new BCardLayout();
	new BView("content view", 0, fContentLayout);
	fContentLayout->Owner()->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	fContentLayout->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
 
	fAudioView = new AudioSettingsView();
	fContentLayout->AddView(fAudioView);
 
	fVideoView = new VideoSettingsView();
	fContentLayout->AddView(fVideoView);
 
	fMidiView = new MidiSettingsView();
	fContentLayout->AddView(fMidiView);
 
	// Layout all views
	BLayoutBuilder::Group<>(this, B_HORIZONTAL)
		.SetInsets(B_USE_WINDOW_SPACING)
		.Add(scrollView, 0.0f)
		.AddGroup(B_VERTICAL)
			.SetInsets(0, 0, 0, 0)
			.Add(fTitleView)
			.Add(fContentLayout);
 
	// Start the window
	fInitCheck = _InitMedia(true);
	if (fInitCheck != B_OK)
		PostMessage(B_QUIT_REQUESTED);
	else if (IsHidden())
		Show();
}
 
 
status_t
MediaWindow::_InitMedia(bool first)
{
	status_t err = B_OK;
	BMediaRoster* roster = BMediaRoster::Roster(&err);
 
	if (first && err != B_OK) {
		BAlert* alert = new BAlert("start_media_server",
			B_TRANSLATE("Could not connect to the media server.\n"
				"Would you like to start it ?"),
			B_TRANSLATE("Quit"),
			B_TRANSLATE("Start media server"), NULL,
			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
		alert->SetShortcut(0, B_ESCAPE);
		if (alert->Go() == 0)
			return B_ERROR;
 
		Show();
 
		launch_media_server();
	}
 
	Lock();
 
	bool isVideoSelected = true;
	if (!first && fListView->ItemAt(0) != NULL
		&& fListView->ItemAt(0)->IsSelected())
		isVideoSelected = false;
 
	while (fListView->CountItems() > 0)
		delete fListView->RemoveItem((int32)0);
	_EmptyNodeLists();
 
	// Grab Media Info
	_FindNodes();
 
	// Add video nodes first. They might have an additional audio
	// output or input, but still should be listed as video node.
	_AddNodeItems(fVideoOutputs, MediaListItem::VIDEO_TYPE);
	_AddNodeItems(fVideoInputs, MediaListItem::VIDEO_TYPE);
	_AddNodeItems(fAudioOutputs, MediaListItem::AUDIO_TYPE);
	_AddNodeItems(fAudioInputs, MediaListItem::AUDIO_TYPE);
 
	fAudioView->AddOutputNodes(fAudioOutputs);
	fAudioView->AddInputNodes(fAudioInputs);
	fVideoView->AddOutputNodes(fVideoOutputs);
	fVideoView->AddInputNodes(fVideoInputs);
 
	// build our list view
	DeviceListItem* audio = new DeviceListItem(B_TRANSLATE("Audio settings"),
		MediaListItem::AUDIO_TYPE);
	fListView->AddItem(audio);
 
	MidiListItem* midi = new MidiListItem(B_TRANSLATE("MIDI Settings"));
	fListView->AddItem(midi);
 
	MediaListItem* video = new DeviceListItem(B_TRANSLATE("Video settings"),
		MediaListItem::VIDEO_TYPE);
	fListView->AddItem(video);
 
	MediaListItem* mixer = new AudioMixerListItem(B_TRANSLATE("Audio mixer"));
	fListView->AddItem(mixer);
 
	fListView->SortItems(&MediaListItem::Compare);
	_UpdateListViewMinWidth();
 
	// Set default nodes for our setting views
	media_node defaultNode;
	dormant_node_info nodeInfo;
	int32 outputID;
	BString outputName;
 
	if (roster->GetAudioInput(&defaultNode) == B_OK) {
		roster->GetDormantNodeFor(defaultNode, &nodeInfo);
		fAudioView->SetDefaultInput(&nodeInfo);
			// this causes our listview to be updated as well
	}
 
	if (roster->GetAudioOutput(&defaultNode, &outputID, &outputName) == B_OK) {
		roster->GetDormantNodeFor(defaultNode, &nodeInfo);
		fAudioView->SetDefaultOutput(&nodeInfo);
		fAudioView->SetDefaultChannel(outputID);
			// this causes our listview to be updated as well
	}
 
	if (roster->GetVideoInput(&defaultNode) == B_OK) {
		roster->GetDormantNodeFor(defaultNode, &nodeInfo);
		fVideoView->SetDefaultInput(&nodeInfo);
			// this causes our listview to be updated as well
	}
 
	if (roster->GetVideoOutput(&defaultNode) == B_OK) {
		roster->GetDormantNodeFor(defaultNode, &nodeInfo);
		fVideoView->SetDefaultOutput(&nodeInfo);
			// this causes our listview to be updated as well
	}
 
	if (first)
		fListView->Select(fListView->IndexOf(mixer));
	else if (isVideoSelected)
		fListView->Select(fListView->IndexOf(video));
	else
		fListView->Select(fListView->IndexOf(audio));
 
	Unlock();
 
	return B_OK;
}
 
 
void
MediaWindow::_FindNodes()
{
	_FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs);
	_FindNodes(B_MEDIA_RAW_AUDIO, B_PHYSICAL_INPUT, fAudioInputs);
	_FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_OUTPUT, fAudioOutputs);
	_FindNodes(B_MEDIA_ENCODED_AUDIO, B_PHYSICAL_INPUT, fAudioInputs);
	_FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs);
	_FindNodes(B_MEDIA_RAW_VIDEO, B_PHYSICAL_INPUT, fVideoInputs);
	_FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_OUTPUT, fVideoOutputs);
	_FindNodes(B_MEDIA_ENCODED_VIDEO, B_PHYSICAL_INPUT, fVideoInputs);
}
 
 
void
MediaWindow::_FindNodes(media_type type, uint64 kind, NodeList& into)
{
	dormant_node_info nodeInfo[64];
	int32 nodeInfoCount = 64;
 
	media_format format;
	media_format* nodeInputFormat = NULL;
	media_format* nodeOutputFormat = NULL;
	format.type = type;
 
	// output nodes must be BBufferConsumers => they have an input format
	// input nodes must be BBufferProducers => they have an output format
	if ((kind & B_PHYSICAL_OUTPUT) != 0)
		nodeInputFormat = &format;
	else if ((kind & B_PHYSICAL_INPUT) != 0)
		nodeOutputFormat = &format;
	else
		return;
 
	BMediaRoster* roster = BMediaRoster::Roster();
 
	if (roster->GetDormantNodes(nodeInfo, &nodeInfoCount, nodeInputFormat,
			nodeOutputFormat, NULL, kind) != B_OK) {
		// TODO: better error reporting!
		fprintf(stderr, "error\n");
		return;
	}
 
	for (int32 i = 0; i < nodeInfoCount; i++) {
		PRINT(("node : %s, media_addon %i, flavor_id %i\n",
			nodeInfo[i].name, (int)nodeInfo[i].addon,
			(int)nodeInfo[i].flavor_id));
 
		dormant_node_info* info = new dormant_node_info();
		strlcpy(info->name, nodeInfo[i].name, B_MEDIA_NAME_LENGTH);
		info->flavor_id = nodeInfo[i].flavor_id;
		info->addon = nodeInfo[i].addon;
		into.AddItem(info);
	}
}
 
 
void
MediaWindow::_AddNodeItems(NodeList& list, MediaListItem::media_type type)
{
	int32 count = list.CountItems();
	for (int32 i = 0; i < count; i++) {
		dormant_node_info* info = list.ItemAt(i);
		if (_FindNodeListItem(info) == NULL)
			fListView->AddItem(new NodeListItem(info, type));
	}
}
 
 
void
MediaWindow::_EmptyNodeLists()
{
	fAudioOutputs.MakeEmpty();
	fAudioInputs.MakeEmpty();
	fVideoOutputs.MakeEmpty();
	fVideoInputs.MakeEmpty();
}
 
 
NodeListItem*
MediaWindow::_FindNodeListItem(dormant_node_info* info)
{
	NodeListItem audioItem(info, MediaListItem::AUDIO_TYPE);
	NodeListItem videoItem(info, MediaListItem::VIDEO_TYPE);
 
	NodeListItem::Comparator audioComparator(&audioItem);
	NodeListItem::Comparator videoComparator(&videoItem);
 
	for (int32 i = 0; i < fListView->CountItems(); i++) {
		MediaListItem* item = static_cast<MediaListItem*>(fListView->ItemAt(i));
		item->Accept(audioComparator);
		if (audioComparator.result == 0)
			return static_cast<NodeListItem*>(item);
 
		item->Accept(videoComparator);
		if (videoComparator.result == 0)
			return static_cast<NodeListItem*>(item);
	}
	return NULL;
}
 
 
void
MediaWindow::_UpdateListViewMinWidth()
{
	float width = 0;
	for (int32 i = 0; i < fListView->CountItems(); i++) {
		BListItem* item = fListView->ItemAt(i);
		width = max_c(width, item->Width());
	}
	fListView->SetExplicitMinSize(BSize(width, B_SIZE_UNSET));
	fListView->InvalidateLayout();
}
 
 
status_t
MediaWindow::_RestartMediaServices(void* data)
{
	MediaWindow* window = (MediaWindow*)data;
 
	shutdown_media_server();
 
	if (window->fRestartAlert != NULL
			&& window->fRestartAlert->Lock()) {
		window->fRestartAlert->Quit();
	}
 
	return window->PostMessage(ML_RESTART_THREAD_FINISHED);
}
 
 
void
MediaWindow::_ClearParamView()
{
	BLayoutItem* item = fContentLayout->VisibleItem();
	if (!item)
		return;
 
	BView* view = item->View();
	if (view != fVideoView && view != fAudioView && view != fMidiView) {
		fContentLayout->RemoveItem(item);
		delete view;
		delete fParamWeb;
		fParamWeb = NULL;
	}
}
 
 
void
MediaWindow::_MakeParamView()
{
	if (!fCurrentNode.IsSet())
		return;
 
	fParamWeb = NULL;
	BMediaRoster* roster = BMediaRoster::Roster();
	if (roster->GetParameterWebFor(fCurrentNode, &fParamWeb) == B_OK) {
		BRect hint(fContentLayout->Frame());
		BView* paramView = BMediaTheme::ViewFor(fParamWeb, &hint);
		if (paramView) {
			fContentLayout->AddView(paramView);
			fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1);
			return;
		}
	}
 
	_MakeEmptyParamView();
}
 
 
void
MediaWindow::_MakeEmptyParamView()
{
	fParamWeb = NULL;
 
	BStringView* stringView = new BStringView("noControls",
		B_TRANSLATE("This hardware has no controls."));
 
	BSize unlimited(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
	stringView->SetExplicitMaxSize(unlimited);
 
	BAlignment centered(B_ALIGN_HORIZONTAL_CENTER,
		B_ALIGN_VERTICAL_CENTER);
	stringView->SetExplicitAlignment(centered);
	stringView->SetAlignment(B_ALIGN_CENTER);
 
	fContentLayout->AddView(stringView);
	fContentLayout->SetVisibleItem(fContentLayout->CountItems() - 1);
 
	rgb_color panel = stringView->LowColor();
	stringView->SetHighColor(tint_color(panel,
		B_DISABLED_LABEL_TINT));
}
 

V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.