/*
 * Copyright 2010-2017, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Pier Luigi Fiorini, pierluigi.fiorini@gmail.com
 *		Stephan Aßmus, superstippi@gmx.de
 *		Brian Hill, supernova@tycho.email
 */


#include <Notification.h>

#include <new>

#include <stdlib.h>
#include <string.h>

#include <notification/Notifications.h>

#include <Bitmap.h>
#include <Message.h>
#include <NodeInfo.h>
#include <Path.h>
#include <Roster.h>


BNotification::BNotification(notification_type type)
	:
	BArchivable(),
	fInitStatus(B_OK),
	fType(type),
	fProgress(0.f),
	fFile(NULL),
	fBitmap(NULL)
{
	team_info teamInfo;
	get_team_info(B_CURRENT_TEAM, &teamInfo);
	app_info appInfo;
	be_roster->GetRunningAppInfo(teamInfo.team, &appInfo);

	int32 iconSize = B_LARGE_ICON;
	fBitmap = new BBitmap(BRect(0, 0, iconSize - 1, iconSize - 1), 0, B_RGBA32);
	if (fBitmap) {
		if (BNodeInfo::GetTrackerIcon(&appInfo.ref, fBitmap,
			icon_size(iconSize)) != B_OK) {
			delete fBitmap;
			fBitmap = NULL;
		}
	}
	fSourceSignature = appInfo.signature;
	BPath path(&appInfo.ref);
	if (path.InitCheck() == B_OK)
		fSourceName = path.Leaf();
}


BNotification::BNotification(BMessage* archive)
	:
	BArchivable(archive),
	fInitStatus(B_OK),
	fProgress(0.0f),
	fFile(NULL),
	fBitmap(NULL)
{
	BString appName;
	if (archive->FindString("_appname", &appName) == B_OK)
		fSourceName = appName;

	BString signature;
	if (archive->FindString("_signature", &signature) == B_OK)
		fSourceSignature = signature;

	int32 type;
	if (archive->FindInt32("_type", &type) == B_OK)
		fType = (notification_type)type;
	else
		fInitStatus = B_ERROR;

	BString group;
	if (archive->FindString("_group", &group) == B_OK)
		SetGroup(group);

	BString title;
	if (archive->FindString("_title", &title) == B_OK)
		SetTitle(title);

	BString content;
	if (archive->FindString("_content", &content) == B_OK)
		SetContent(content);

	BString messageID;
	if (archive->FindString("_messageID", &messageID) == B_OK)
		SetMessageID(messageID);

	float progress;
	if (type == B_PROGRESS_NOTIFICATION
		&& archive->FindFloat("_progress", &progress) == B_OK)
		SetProgress(progress);

	BString onClickApp;
	if (archive->FindString("_onClickApp", &onClickApp) == B_OK)
		SetOnClickApp(onClickApp);

	entry_ref onClickFile;
	if (archive->FindRef("_onClickFile", &onClickFile) == B_OK)
		SetOnClickFile(&onClickFile);

	entry_ref onClickRef;
	int32 index = 0;
	while (archive->FindRef("_onClickRef", index++, &onClickRef) == B_OK)
		AddOnClickRef(&onClickRef);

	BString onClickArgv;
	index = 0;
	while (archive->FindString("_onClickArgv", index++, &onClickArgv) == B_OK)
		AddOnClickArg(onClickArgv);

	status_t ret = B_OK;
	BMessage icon;
	if ((ret = archive->FindMessage("_icon", &icon)) == B_OK) {
		BBitmap bitmap(&icon);
		ret = bitmap.InitCheck();
		if (ret == B_OK)
			ret = SetIcon(&bitmap);
	}
}


BNotification::~BNotification()
{
	delete fFile;
	delete fBitmap;

	for (int32 i = fRefs.CountItems() - 1; i >= 0; i--)
		delete (entry_ref*)fRefs.ItemAtFast(i);

	for (int32 i = fArgv.CountItems() - 1; i >= 0; i--)
		free(fArgv.ItemAtFast(i));
}


/*! \brief Returns initialization status.
 */
status_t
BNotification::InitCheck() const
{
	return fInitStatus;
}


/*! \brief Returns a new BNotification object from @archive.

	Returns a new BNotification object, allocated by new and created
	with the version of the constructor that takes BMessage archive.
	However, if the message doesn't contain an archived data for a
	BNotification object, this method returns NULL.

	\return BNotification object from @archive or NULL if it doesn't
			contain a valid BNotification object.
*/
BArchivable*
BNotification::Instantiate(BMessage* archive)
{
	if (validate_instantiation(archive, "BNotification"))
		return new(std::nothrow) BNotification(archive);

	return NULL;
}


/*! \brief Archives the BNotification in the BMessages @archive.

	\sa BArchivable::Archive(), Instantiate() static function.
	\return
	- \c B_OK: Everything went fine.
	- \c Other errors: Archiving has failed.
*/
status_t
BNotification::Archive(BMessage* archive, bool deep) const
{
	status_t status = BArchivable::Archive(archive, deep);

	if (status == B_OK)
		status = archive->AddString("_appname", fSourceName);

	if (status == B_OK)
		status = archive->AddString("_signature", fSourceSignature);

	if (status == B_OK)
		status = archive->AddInt32("_type", (int32)fType);

	if (status == B_OK && Group() != NULL)
		status = archive->AddString("_group", Group());

	if (status == B_OK && Title() != NULL)
		status = archive->AddString("_title", Title());

	if (status == B_OK && Content() != NULL)
		status = archive->AddString("_content", Content());

	if (status == B_OK && MessageID() != NULL)
		status = archive->AddString("_messageID", MessageID());

	if (status == B_OK && Type() == B_PROGRESS_NOTIFICATION)
		status = archive->AddFloat("_progress", Progress());

	if (status == B_OK && OnClickApp() != NULL)
		status = archive->AddString("_onClickApp", OnClickApp());

	if (status == B_OK && OnClickFile() != NULL)
		status = archive->AddRef("_onClickFile", OnClickFile());

	if (status == B_OK) {
		for (int32 i = 0; i < CountOnClickRefs(); i++) {
			status = archive->AddRef("_onClickRef", OnClickRefAt(i));
			if (status != B_OK)
				break;
		}
	}

	if (status == B_OK) {
		for (int32 i = 0; i < CountOnClickArgs(); i++) {
			status = archive->AddString("_onClickArgv", OnClickArgAt(i));
			if (status != B_OK)
				break;
		}
	}

	if (status == B_OK) {
		const BBitmap* icon = Icon();
		if (icon != NULL) {
			BMessage iconArchive;
			status = icon->Archive(&iconArchive);
			if (status == B_OK)
				archive->AddMessage("_icon", &iconArchive);
		}
	}

	return status;
}


/*! \brief Returns source application signature.

	\return Source application signature.
*/
const char*
BNotification::SourceSignature() const
{
	return fSourceSignature;
}


/*! \brief Returns source application name.

	\return Source application name.
*/
const char*
BNotification::SourceName() const
{
	return fSourceName;
}


/*! \brief Notification's type.

	\return A value of the notification_type enum that represents
			notification type.
*/
notification_type
BNotification::Type() const
{
	return fType;
}


/*! \brief Returns notification's group.

	\return Notification's group.
*/
const char*
BNotification::Group() const
{
	if (fGroup == "")
		return NULL;
	return fGroup;
}


/*! \brief Sets notification's group.

	Notifications can be grouped together setting the same group.
*/
void
BNotification::SetGroup(const BString& group)
{
	fGroup = group;
}


/*! \brief Returns notification's title.

	\return Notification's title.
*/
const char*
BNotification::Title() const
{
	if (fTitle == "")
		return NULL;
	return fTitle;
}


/*! \brief Set notification's title.
*/
void
BNotification::SetTitle(const BString& title)
{
	fTitle = title;
}


/*! \brief Returns notification's message.

	\return Notification's message.
*/
const char*
BNotification::Content() const
{
	if (fContent == "")
		return NULL;
	return fContent;
}


/*! \brief Sets notification's message.
*/
void
BNotification::SetContent(const BString& content)
{
	fContent = content;
}


/*! \brief Returns notification's message identifier.

	\return Notification's message identifier.
*/
const char*
BNotification::MessageID() const
{
	if (fID == "")
		return NULL;
	return fID;
}


/*! \brief Sets notification's message identifier.
*/
void
BNotification::SetMessageID(const BString& id)
{
	fID = id;
}


/*! \brief Returns progress information.

	If notification's type is \c B_PROGRESS_NOTIFICATION, returns a value
	between 0.0 and 1.0 that represent progress percentage.

	If notification's type is not \c B_PROGRESS_NOTIFICATION, returns -1.

	\return Percentage if notification's type is B_PROGRESS_NOTIFICATION
			or otherwise -1.
*/
float
BNotification::Progress() const
{
	if (fType != B_PROGRESS_NOTIFICATION)
		return -1;
	return fProgress;
}


/*! \brief Sets progress information.

	Sets progress percentage, this information will be used only
	if notification's type is \c B_PROGRESS_NOTIFICATION.

	The value of @progress must be between 0.0 and 1.0.
*/
void
BNotification::SetProgress(float progress)
{
	if (progress < 0)
		fProgress = 0;
	else if (progress > 1)
		fProgress = 1;
	else
		fProgress = progress;
}


const char*
BNotification::OnClickApp() const
{
	if (fApp == "")
		return NULL;
	return fApp;
}


void
BNotification::SetOnClickApp(const BString& app)
{
	fApp = app;
}


const entry_ref*
BNotification::OnClickFile() const
{
	return fFile;
}


status_t
BNotification::SetOnClickFile(const entry_ref* file)
{
	delete fFile;

	if (file != NULL) {
		fFile = new(std::nothrow) entry_ref(*file);
		if (fFile == NULL)
			return B_NO_MEMORY;
	} else
		fFile = NULL;

	return B_OK;
}


status_t
BNotification::AddOnClickRef(const entry_ref* ref)
{
	if (ref == NULL)
		return B_BAD_VALUE;

	entry_ref* clonedRef = new(std::nothrow) entry_ref(*ref);
	if (clonedRef == NULL || !fRefs.AddItem(clonedRef))
		return B_NO_MEMORY;

	return B_OK;
}


int32
BNotification::CountOnClickRefs() const
{
	return fRefs.CountItems();
}


const entry_ref*
BNotification::OnClickRefAt(int32 index) const
{
	return (entry_ref*)fRefs.ItemAt(index);
}


status_t
BNotification::AddOnClickArg(const BString& arg)
{
	char* clonedArg = strdup(arg.String());
	if (clonedArg == NULL || !fArgv.AddItem(clonedArg))
		return B_NO_MEMORY;

	return B_OK;
}


int32
BNotification::CountOnClickArgs() const
{
	return fArgv.CountItems();
}


const char*
BNotification::OnClickArgAt(int32 index) const
{
	return (char*)fArgv.ItemAt(index);
}


/*! \brief Notification's icon.

	\return Notification's icon.
*/
const BBitmap*
BNotification::Icon() const
{
	return fBitmap;
}


/*! \brief Sets notification's icon.

	Sets notification's icon.
	This method does not assume ownership of @icon.

	\param icon Icon
	\return
	- \c B_OK: Everything went fine.
	- \c B_NO_MEMORY: Allocation of @icon copy has failed.
	- \c Other errors: Creation of @icon copy failed for some reason.
*/
status_t
BNotification::SetIcon(const BBitmap* icon)
{
	delete fBitmap;

	if (icon != NULL) {
		fBitmap = new(std::nothrow) BBitmap(icon);
		if (fBitmap == NULL)
			return B_NO_MEMORY;
		return fBitmap->InitCheck();
	}

	fBitmap = NULL;
	return B_OK;
}


/*! \brief Sends a notification to the notification_server.

	The notification is delivered asynchronously to the notification_server,
	which will display it according to its settings and filters.

	\param timeout Microseconds after the message fades out.
	\return
	- \c B_OK: Everything went fine.
	- \c B_BAD_PORT_ID: A connection to notification_server could not be
	  established or the server is not up and running anymore.
	- \c Other errors: Building the message from the notification failed.
*/
status_t
BNotification::Send(bigtime_t timeout)
{
	BMessage msg(kNotificationMessage);

	// Archive notification
	status_t ret = Archive(&msg);

	// Custom time out
	if (ret == B_OK && timeout > 0)
		ret = msg.AddInt64("timeout", timeout);

	// Send message
	if (ret == B_OK) {
		BMessenger server(kNotificationServerSignature);
		ret = server.SendMessage(&msg);
	}

	return ret;
}


void BNotification::_ReservedNotification1() {}
void BNotification::_ReservedNotification2() {}
void BNotification::_ReservedNotification3() {}
void BNotification::_ReservedNotification4() {}
void BNotification::_ReservedNotification5() {}
void BNotification::_ReservedNotification6() {}
void BNotification::_ReservedNotification7() {}
void BNotification::_ReservedNotification8() {}
