/*
 * Copyright 2004-2015 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 *	Authors:
 *		Adrien Destugues, <pulkomandy@pulkomandy.tk>
 *		Axel Dörfler, <axeld@pinc-software.de>
 *		Alexander von Gluck, <kallisti5@unixzen.com>
 */
 
 
#include "NetworkWindow.h"
 
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#include <Alert.h>
#include <Application.h>
#include <Button.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <ControlLook.h>
#include <Deskbar.h>
#include <Directory.h>
#include <LayoutBuilder.h>
#include <NetworkDevice.h>
#include <NetworkInterface.h>
#include <NetworkNotifications.h>
#include <NetworkRoster.h>
#include <OutlineListView.h>
#include <Path.h>
#include <PathFinder.h>
#include <PathMonitor.h>
#include <Roster.h>
#include <ScrollView.h>
#include <StringItem.h>
#include <SymLink.h>
 
#define ENABLE_PROFILES 0
#if ENABLE_PROFILES
#	include <PopUpMenu.h>
#endif
 
#include "InterfaceListItem.h"
#include "InterfaceView.h"
#include "ServiceListItem.h"
 
 
const char* kNetworkStatusSignature = "application/x-vnd.Haiku-NetworkStatus";
 
static const uint32 kMsgProfileSelected = 'prof';
static const uint32 kMsgProfileManage = 'mngp';
static const uint32 kMsgProfileNew = 'newp';
static const uint32 kMsgRevert = 'rvrt';
static const uint32 kMsgToggleReplicant = 'trep';
static const uint32 kMsgItemSelected = 'ItSl';
 
BMessenger gNetworkWindow;
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT	"NetworkWindow"
 
 
class TitleItem : public BStringItem {
public:
	TitleItem(const char* title)
		:
		BStringItem(title)
	{
	}
 
	void DrawItem(BView* owner, BRect bounds, bool complete)
	{
		owner->SetFont(be_bold_font);
		BStringItem::DrawItem(owner, bounds, complete);
		owner->SetFont(be_plain_font);
	}
 
	void Update(BView* owner, const BFont* font)
	{
		BStringItem::Update(owner, be_bold_font);
	}
};
 
 
// #pragma mark -
 
 
NetworkWindow::NetworkWindow()
	:
	BWindow(BRect(100, 100, 400, 400), B_TRANSLATE("Network"), B_TITLED_WINDOW,
		B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS),
	fServicesItem(NULL),
	fDialUpItem(NULL),
	fVPNItem(NULL),
	fOtherItem(NULL)
{
	// Profiles section
#if ENABLE_PROFILES
	BPopUpMenu* profilesPopup = new BPopUpMenu("<none>");
	_BuildProfilesMenu(profilesPopup, kMsgProfileSelected);
 
	BMenuField* profilesMenuField = new BMenuField("profiles_menu",
		B_TRANSLATE("Profile:"), profilesPopup);
 
	profilesMenuField->SetFont(be_bold_font);
	profilesMenuField->SetEnabled(false);
#endif
 
	// Settings section
 
	fRevertButton = new BButton("revert", B_TRANSLATE("Revert"),
		new BMessage(kMsgRevert));
 
	BMessage* message = new BMessage(kMsgToggleReplicant);
	BCheckBox* showReplicantCheckBox = new BCheckBox("showReplicantCheckBox",
		B_TRANSLATE("Show network status in Deskbar"), message);
	showReplicantCheckBox->SetExplicitMaxSize(
		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
	showReplicantCheckBox->SetValue(_IsReplicantInstalled());
 
	fListView = new BOutlineListView("list", B_SINGLE_SELECTION_LIST,
		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS | B_NAVIGABLE);
	fListView->SetSelectionMessage(new BMessage(kMsgItemSelected));
 
	BScrollView* scrollView = new BScrollView("ScrollView", fListView,
		0, false, true);
	scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
 
	fAddOnShellView = new BView("add-on shell", 0,
		new BGroupLayout(B_VERTICAL));
	fAddOnShellView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
 
	fInterfaceView = new InterfaceView();
 
	// Build the layout
	BLayoutBuilder::Group<>(this, B_VERTICAL)
		.SetInsets(B_USE_WINDOW_SPACING)
 
#if ENABLE_PROFILES
		.AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING)
			.Add(profilesMenuField)
			.AddGlue()
		.End()
#endif
		.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
			.Add(scrollView)
			.Add(fAddOnShellView)
			.End()
		.Add(showReplicantCheckBox)
		.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
			.Add(fRevertButton)
			.AddGlue()
		.End();
 
	gNetworkWindow = this;
 
	_ScanInterfaces();
	_ScanAddOns();
	_UpdateRevertButton();
 
	fListView->Select(0);
	_SelectItem(fListView->ItemAt(0));
		// Call this manually, so that CenterOnScreen() below already
		// knows the final window size.
 
	// Set size of the list view from its contents
	float width;
	float height;
	fListView->GetPreferredSize(&width, &height);
	width += 2 * be_control_look->DefaultItemSpacing();
	fListView->SetExplicitSize(BSize(width, B_SIZE_UNSET));
	fListView->SetExplicitMinSize(BSize(width, std::min(height, 400.f)));
 
	CenterOnScreen();
 
	fSettings.StartMonitoring(this);
	start_watching_network(B_WATCH_NETWORK_INTERFACE_CHANGES
		| B_WATCH_NETWORK_LINK_CHANGES | B_WATCH_NETWORK_WLAN_CHANGES, this);
}
 
 
NetworkWindow::~NetworkWindow()
{
	stop_watching_network(this);
	fSettings.StopMonitoring(this);
}
 
 
bool
NetworkWindow::QuitRequested()
{
	be_app->PostMessage(B_QUIT_REQUESTED);
	return true;
}
 
 
void
NetworkWindow::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case kMsgProfileNew:
			break;
 
		case kMsgProfileSelected:
		{
			const char* path;
			if (message->FindString("path", &path) != B_OK)
				break;
 
			// TODO!
			break;
		}
 
		case kMsgItemSelected:
		{
			BListItem* listItem = fListView->FullListItemAt(
				fListView->FullListCurrentSelection());
			if (listItem == NULL)
				break;
 
			_SelectItem(listItem);
			break;
		}
 
		case kMsgRevert:
		{
			SettingsMap::const_iterator iterator = fSettingsMap.begin();
			for (; iterator != fSettingsMap.end(); iterator++)
				iterator->second->Revert();
			break;
		}
 
		case kMsgToggleReplicant:
		{
			_ShowReplicant(
				message->GetInt32("be:value", B_CONTROL_OFF) == B_CONTROL_ON);
			break;
		}
 
		case B_PATH_MONITOR:
		{
			fSettings.Update(message);
			break;
		}
 
		case B_NETWORK_MONITOR:
			_BroadcastConfigurationUpdate(*message);
			break;
 
		case BNetworkSettings::kMsgInterfaceSettingsUpdated:
		case BNetworkSettings::kMsgNetworkSettingsUpdated:
		case BNetworkSettings::kMsgServiceSettingsUpdated:
			_BroadcastSettingsUpdate(message->what);
			break;
 
		case kMsgSettingsItemUpdated:
			// TODO: update list item
			_UpdateRevertButton();
			break;
 
		default:
			inherited::MessageReceived(message);
	}
}
 
 
void
NetworkWindow::_BuildProfilesMenu(BMenu* menu, int32 what)
{
	char currentProfile[256] = { 0 };
 
	menu->SetRadioMode(true);
 
	BDirectory dir("/boot/system/settings/network/profiles");
	if (dir.InitCheck() == B_OK) {
		BEntry entry;
 
		dir.Rewind();
		while (dir.GetNextEntry(&entry) >= 0) {
			BPath name;
			entry.GetPath(&name);
 
			if (entry.IsSymLink() &&
				strcmp("current", name.Leaf()) == 0) {
				BSymLink symlink(&entry);
 
				if (symlink.IsAbsolute())
					// oh oh, sorry, wrong symlink...
					continue;
 
				symlink.ReadLink(currentProfile, sizeof(currentProfile));
				continue;
			};
 
			if (!entry.IsDirectory())
				continue;
 
			BMessage* message = new BMessage(what);
			message->AddString("path", name.Path());
 
			BMenuItem* item = new BMenuItem(name.Leaf(), message);
			menu->AddItem(item);
		}
	}
 
	menu->AddSeparatorItem();
	menu->AddItem(new BMenuItem(B_TRANSLATE("New" B_UTF8_ELLIPSIS),
		new BMessage(kMsgProfileNew)));
	menu->AddItem(new BMenuItem(B_TRANSLATE("Manage" B_UTF8_ELLIPSIS),
		new BMessage(kMsgProfileManage)));
 
	if (currentProfile[0] != '\0') {
		BMenuItem* item = menu->FindItem(currentProfile);
		if (item != NULL) {
			// TODO: translate
			BString label(item->Label());
			label << " (current)";
			item->SetLabel(label.String());
			item->SetMarked(true);
		}
	}
}
 
 
void
NetworkWindow::_ScanInterfaces()
{
	// Try existing devices first
	BNetworkRoster& roster = BNetworkRoster::Default();
	BNetworkInterface interface;
	uint32 cookie = 0;
 
	while (roster.GetNextInterface(&cookie, interface) == B_OK) {
		if ((interface.Flags() & IFF_LOOPBACK) != 0)
			continue;
 
		BNetworkDevice device(interface.Name());
		BNetworkInterfaceType type = B_NETWORK_INTERFACE_TYPE_OTHER;
 
		if (device.IsWireless())
			type = B_NETWORK_INTERFACE_TYPE_WIFI;
		else if (device.IsEthernet())
			type = B_NETWORK_INTERFACE_TYPE_ETHERNET;
 
		InterfaceListItem* item = new InterfaceListItem(interface.Name(), type);
		item->SetExpanded(true);
 
		fInterfaceItemMap.insert(std::pair<BString, InterfaceListItem*>(
			BString(interface.Name()), item));
		fListView->AddItem(item);
	}
 
	// TODO: Then consider those from the settings (for example, for USB)
}
 
 
void
NetworkWindow::_ScanAddOns()
{
	BStringList paths;
	BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "Network Settings",
		paths);
 
	// Collect add-on paths by name, so that each name will only be
	// loaded once.
	typedef std::map<BString, BPath> PathMap;
	PathMap addOnMap;
 
	for (int32 i = 0; i < paths.CountStrings(); i++) {
		BDirectory directory(paths.StringAt(i));
		BEntry entry;
		while (directory.GetNextEntry(&entry) == B_OK) {
			BPath path;
			if (entry.GetPath(&path) != B_OK)
				continue;
 
			if (addOnMap.find(path.Leaf()) == addOnMap.end())
				addOnMap.insert(std::pair<BString, BPath>(path.Leaf(), path));
		}
	}
 
	for (PathMap::const_iterator addOnIterator = addOnMap.begin();
			addOnIterator != addOnMap.end(); addOnIterator++) {
		const BPath& path = addOnIterator->second;
 
		image_id image = load_add_on(path.Path());
		if (image < 0) {
			printf("Failed to load %s addon: %s.\n", path.Path(),
				strerror(image));
			continue;
		}
 
		BNetworkSettingsAddOn* (*instantiateAddOn)(image_id image,
			BNetworkSettings& settings);
 
		status_t status = get_image_symbol(image,
			"instantiate_network_settings_add_on",
			B_SYMBOL_TYPE_TEXT, (void**)&instantiateAddOn);
		if (status != B_OK) {
			// No "addon instantiate function" symbol found in this addon
			printf("No symbol \"instantiate_network_settings_add_on\" found "
				"in %s addon: not a network setup addon!\n", path.Path());
			unload_add_on(image);
			continue;
		}
 
		BNetworkSettingsAddOn* addOn = instantiateAddOn(image, fSettings);
		if (addOn == NULL) {
			unload_add_on(image);
			continue;
		}
 
		fAddOns.AddItem(addOn);
 
		// Per interface items
		ItemMap::const_iterator iterator = fInterfaceItemMap.begin();
		for (; iterator != fInterfaceItemMap.end(); iterator++) {
			const BString& interface = iterator->first;
			BListItem* interfaceItem = iterator->second;
 
			uint32 cookie = 0;
			while (true) {
				BNetworkSettingsItem* item = addOn->CreateNextInterfaceItem(
					cookie, interface.String());
				if (item == NULL)
					break;
 
				fSettingsMap[item->ListItem()] = item;
				fListView->AddUnder(item->ListItem(), interfaceItem);
			}
			fListView->SortItemsUnder(interfaceItem, true,
				NetworkWindow::_CompareListItems);
		}
 
		// Generic items
		uint32 cookie = 0;
		while (true) {
			BNetworkSettingsItem* item = addOn->CreateNextItem(cookie);
			if (item == NULL)
				break;
 
			fSettingsMap[item->ListItem()] = item;
			fListView->AddUnder(item->ListItem(),
				_ListItemFor(item->Type()));
		}
 
		_SortItemsUnder(fServicesItem);
		_SortItemsUnder(fDialUpItem);
		_SortItemsUnder(fVPNItem);
		_SortItemsUnder(fOtherItem);
	}
 
	fListView->SortItemsUnder(NULL, true,
		NetworkWindow::_CompareTopLevelListItems);
}
 
 
BNetworkSettingsItem*
NetworkWindow::_SettingsItemFor(BListItem* item)
{
	SettingsMap::const_iterator found = fSettingsMap.find(item);
	if (found != fSettingsMap.end())
		return found->second;
 
	return NULL;
}
 
 
void
NetworkWindow::_SortItemsUnder(BListItem* item)
{
	if (item != NULL)
		fListView->SortItemsUnder(item, true, NetworkWindow::_CompareListItems);
}
 
 
BListItem*
NetworkWindow::_ListItemFor(BNetworkSettingsType type)
{
	switch (type) {
		case B_NETWORK_SETTINGS_TYPE_SERVICE:
			if (fServicesItem == NULL)
				fServicesItem = _CreateItem(B_TRANSLATE("Services"));
			return fServicesItem;
 
		case B_NETWORK_SETTINGS_TYPE_DIAL_UP:
			if (fDialUpItem == NULL)
				fDialUpItem = _CreateItem(B_TRANSLATE("Dial Up"));
			return fDialUpItem;
 
		case B_NETWORK_SETTINGS_TYPE_VPN:
			if (fVPNItem == NULL)
				fVPNItem = _CreateItem(B_TRANSLATE("VPN"));
			return fVPNItem;
 
		case B_NETWORK_SETTINGS_TYPE_OTHER:
			if (fOtherItem == NULL)
				fOtherItem = _CreateItem(B_TRANSLATE("Other"));
			return fOtherItem;
 
		default:
			return NULL;
	}
}
 
 
BListItem*
NetworkWindow::_CreateItem(const char* label)
{
	BListItem* item = new TitleItem(label);
	item->SetExpanded(true);
	fListView->AddItem(item);
	return item;
}
 
 
void
NetworkWindow::_SelectItem(BListItem* listItem)
{
	while (fAddOnShellView->CountChildren() > 0)
		fAddOnShellView->ChildAt(0)->RemoveSelf();
 
	BView* nextView = NULL;
 
	BNetworkSettingsItem* item = _SettingsItemFor(listItem);
	if (item != NULL) {
		nextView = item->View();
	} else {
		InterfaceListItem* item = dynamic_cast<InterfaceListItem*>(
			listItem);
		if (item != NULL) {
			fInterfaceView->SetTo(item->Name());
			nextView = fInterfaceView;
		}
	}
 
	if (nextView != NULL)
		fAddOnShellView->AddChild(nextView);
}
 
 
void
NetworkWindow::_BroadcastSettingsUpdate(uint32 type)
{
	for (int32 index = 0; index < fListView->FullListCountItems(); index++) {
		BNetworkSettingsListener* listener
			= dynamic_cast<BNetworkSettingsListener*>(
				fListView->FullListItemAt(index));
		if (listener != NULL)
			listener->SettingsUpdated(type);
	}
 
	SettingsMap::const_iterator iterator = fSettingsMap.begin();
	for (; iterator != fSettingsMap.end(); iterator++)
		iterator->second->SettingsUpdated(type);
 
	_UpdateRevertButton();
}
 
 
void
NetworkWindow::_BroadcastConfigurationUpdate(const BMessage& message)
{
	for (int32 index = 0; index < fListView->FullListCountItems(); index++) {
		BNetworkConfigurationListener* listener
			= dynamic_cast<BNetworkConfigurationListener*>(
				fListView->FullListItemAt(index));
		if (listener != NULL)
			listener->ConfigurationUpdated(message);
	}
 
	SettingsMap::const_iterator iterator = fSettingsMap.begin();
	for (; iterator != fSettingsMap.end(); iterator++)
		iterator->second->ConfigurationUpdated(message);
 
	// TODO: improve invalidated region to the one that matters
	fListView->Invalidate();
	_UpdateRevertButton();
}
 
 
void
NetworkWindow::_UpdateRevertButton()
{
	bool enabled = false;
	SettingsMap::const_iterator iterator = fSettingsMap.begin();
	for (; iterator != fSettingsMap.end(); iterator++) {
		if (iterator->second->IsRevertable()) {
			enabled = true;
			break;
		}
	}
 
	fRevertButton->SetEnabled(enabled);
}
 
 
void
NetworkWindow::_ShowReplicant(bool show)
{
	if (show) {
		const char* argv[] = {"--deskbar", NULL};
 
		status_t status = be_roster->Launch(kNetworkStatusSignature, 1, argv);
		if (status != B_OK) {
			BString errorMessage;
			errorMessage.SetToFormat(
				B_TRANSLATE("Installing NetworkStatus in Deskbar failed: %s"),
				strerror(status));
			BAlert* alert = new BAlert(B_TRANSLATE("launch error"),
				errorMessage, B_TRANSLATE("Ok"));
			alert->Go(NULL);
		}
	} else {
		BDeskbar deskbar;
		deskbar.RemoveItem("NetworkStatus");
	}
}
 
 
bool
NetworkWindow::_IsReplicantInstalled()
{
	BDeskbar deskbar;
	return deskbar.HasItem("NetworkStatus");
}
 
 
/*static*/ const char*
NetworkWindow::_ItemName(const BListItem* item)
{
	if (const BNetworkInterfaceListItem* listItem = dynamic_cast<
			const BNetworkInterfaceListItem*>(item))
		return listItem->Label();
 
	if (const ServiceListItem* listItem = dynamic_cast<
			const ServiceListItem*>(item))
		return listItem->Label();
 
	if (const BStringItem* stringItem = dynamic_cast<const BStringItem*>(item))
		return stringItem->Text();
 
	return NULL;
}
 
 
/*static*/ int
NetworkWindow::_CompareTopLevelListItems(const BListItem* a, const BListItem* b)
{
	if (a == b)
		return 0;
 
	if (const InterfaceListItem* itemA
			= dynamic_cast<const InterfaceListItem*>(a)) {
		if (const InterfaceListItem* itemB
				= dynamic_cast<const InterfaceListItem*>(b)) {
			return strcasecmp(itemA->Name(), itemB->Name());
		}
		return -1;
	} else if (dynamic_cast<const InterfaceListItem*>(b) != NULL)
		return 1;
/*
	if (a == fDialUpItem)
		return -1;
	if (b == fDialUpItem)
		return 1;
 
	if (a == fServicesItem)
		return -1;
	if (b == fServicesItem)
		return 1;
*/
	return _CompareListItems(a, b);
}
 
 
/*static*/ int
NetworkWindow::_CompareListItems(const BListItem* a, const BListItem* b)
{
	if (a == b)
		return 0;
 
	const char* nameA = _ItemName(a);
	const char* nameB = _ItemName(b);
 
	if (nameA != NULL && nameB != NULL)
		return strcasecmp(nameA, nameB);
	if (nameA != NULL)
		return 1;
	if (nameB != NULL)
		return -1;
 
	return (addr_t)a > (addr_t)b ? 1 : -1;
}

V773 Visibility scope of the 'alert' pointer was exited without releasing the memory. A memory leak is possible.