/*
 * Copyright 2001-2009, Haiku Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Gabe Yoder (gyoder@stny.rr.com)
 */


#include <Clipboard.h>

#include <Application.h>
#include <RegistrarDefs.h>
#ifndef HoG_GENODE
#include <RosterPrivate.h>
#else
///later-bclipboard (is RUN_WITHOUT_REGISTRAR a good start, to get use going locally?)
#define RUN_WITHOUT_REGISTRAR
	//	1) define RUN_WITHOUT_REGISTRAR ? This should allow copy-pasting within each app at least, if not across app boundaries?
	//	2) implement registrar for system-wide clipboard?
	//	3) or just use Genode's new clipboard system? :
	//		http://genodians.org/nfeske/2019-07-03-copy-paste
	//		https://github.com/genodelabs/genode/issues/3437
	//		https://github.com/genodelabs/genode/commit/1dedc9abe33f58e685f65722885c8abf44278b7c and others
#endif

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

#ifdef RUN_WITHOUT_REGISTRAR
	static BClipboard sClipboard(NULL);
	BClipboard *be_clipboard = &sClipboard;
#else
	BClipboard *be_clipboard = NULL;
#endif


using namespace BPrivate;


BClipboard::BClipboard(const char *name, bool transient)
	:
	fLock("clipboard")
{
	if (name != NULL)
		fName = strdup(name);
	else
		fName = strdup("system");

	fData = new BMessage();
	fCount = 0;

#ifdef HoG_GENODE
#else
	BMessage message(B_REG_GET_CLIPBOARD_MESSENGER), reply;
	if (BRoster::Private().SendTo(&message, &reply, false) == B_OK
		&& reply.what == B_REG_SUCCESS
		&& reply.FindMessenger("messenger", &fClipHandler) == B_OK) {
		BMessage handlerMessage(B_REG_ADD_CLIPBOARD), handlerReply;
		int32 result;
		if (handlerMessage.AddString("name", fName) == B_OK
			&& fClipHandler.SendMessage(&handlerMessage, &handlerReply) == B_OK)
			handlerReply.FindInt32("result", &result);
	}
#endif
}


BClipboard::~BClipboard()
{
	free(fName);
	delete fData;
}


const char *
BClipboard::Name() const
{
	return (const char *)fName;
}


/*!	\brief Returns the (locally cached) number of commits to the clipboard.

	The returned value is the number of successful Commit() invocations for
	the clipboard represented by this object, either invoked on this object
	or another (even from another application). This method returns a locally
	cached value, which might already be obsolete. For an up-to-date value
	SystemCount() can be invoked.

	\return The number of commits to the clipboard.
*/
uint32
BClipboard::LocalCount() const
{
	return fCount;
}


/*!	\brief Returns the number of commits to the clipboard.

	The returned value is the number of successful Commit() invocations for
	the clipboard represented by this object, either invoked on this object
	or another (even from another application). This method retrieves the
	value directly from the system service managing the clipboards, so it is
	more expensive, but more up-to-date than LocalCount(), which returns a
	locally cached value.

	\return The number of commits to the clipboard.
*/
uint32
BClipboard::SystemCount() const
{
	int32 value;
	BMessage message(B_REG_GET_CLIPBOARD_COUNT), reply;
	if (message.AddString("name", fName) == B_OK
		&& fClipHandler.SendMessage(&message, &reply) == B_OK
		&& reply.FindInt32("count", &value) == B_OK)
		return (uint32)value;

	return 0;
}


status_t
BClipboard::StartWatching(BMessenger target)
{
	if (!target.IsValid())
		return B_BAD_VALUE;

	BMessage message(B_REG_CLIPBOARD_START_WATCHING), reply;
	if (message.AddString("name", fName) == B_OK
		&& message.AddMessenger("target", target) == B_OK
		&& fClipHandler.SendMessage(&message, &reply) == B_OK) {
		int32 result;
		reply.FindInt32("result", &result);
		return result;
	}
	return B_ERROR;
}


status_t
BClipboard::StopWatching(BMessenger target)
{
	if (!target.IsValid())
		return B_BAD_VALUE;

	BMessage message(B_REG_CLIPBOARD_STOP_WATCHING), reply;
	if (message.AddString("name", fName) == B_OK
		&& message.AddMessenger("target", target) == B_OK
		&& fClipHandler.SendMessage(&message, &reply) == B_OK) {
		int32 result;
		if (reply.FindInt32("result", &result) == B_OK)
			return result;
	}
	return B_ERROR;
}


bool
BClipboard::Lock()
{
	// Will this work correctly if clipboard is deleted while still waiting on
	// fLock.Lock() ?
	bool locked = fLock.Lock();

#ifndef RUN_WITHOUT_REGISTRAR
	if (locked && _DownloadFromSystem() != B_OK) {
		locked = false;
		fLock.Unlock();
	}
#endif

	return locked;
}


void
BClipboard::Unlock()
{
	fLock.Unlock();
}


bool
BClipboard::IsLocked() const
{
	return fLock.IsLocked();
}


status_t
BClipboard::Clear()
{
	if (!_AssertLocked())
		return B_NOT_ALLOWED;

	return fData->MakeEmpty();
}


status_t
BClipboard::Commit()
{
	return Commit(false);
}


status_t
BClipboard::Commit(bool failIfChanged)
{
	if (!_AssertLocked())
		return B_NOT_ALLOWED;

	status_t status = B_ERROR;
	BMessage message(B_REG_UPLOAD_CLIPBOARD), reply;
	if (message.AddString("name", fName) == B_OK
		&& message.AddMessage("data", fData) == B_OK
		&& message.AddMessenger("data source", be_app_messenger) == B_OK
		&& message.AddInt32("count", fCount) == B_OK
		&& message.AddBool("fail if changed", failIfChanged) == B_OK)
		status = fClipHandler.SendMessage(&message, &reply);

	if (status == B_OK) {
		int32 count;
		if (reply.FindInt32("count", &count) == B_OK)
			fCount = count;
	}

	return status;
}


status_t
BClipboard::Revert()
{
	if (!_AssertLocked())
		return B_NOT_ALLOWED;

	status_t status = fData->MakeEmpty();
	if (status == B_OK)
		status = _DownloadFromSystem();

	return status;
}


BMessenger
BClipboard::DataSource() const
{
	return fDataSource;
}


BMessage *
BClipboard::Data() const
{
	if (!_AssertLocked())
		return NULL;

    return fData;
}


//	#pragma mark - Private methods


BClipboard::BClipboard(const BClipboard &)
{
	// This is private, and I don't use it, so I'm not going to implement it
}


BClipboard & BClipboard::operator=(const BClipboard &)
{
	// This is private, and I don't use it, so I'm not going to implement it
	return *this;
}


void BClipboard::_ReservedClipboard1() {}
void BClipboard::_ReservedClipboard2() {}
void BClipboard::_ReservedClipboard3() {}


bool
BClipboard::_AssertLocked() const
{
	// This function is for jumping to the debugger if not locked
	if (!fLock.IsLocked()) {
		debugger("The clipboard must be locked before proceeding.");
		return false;
	}
	return true;
}


status_t
BClipboard::_DownloadFromSystem(bool force)
{
	// Apparently, the force paramater was used in some sort of
	// optimization in R5. Currently, we ignore it.
	BMessage message(B_REG_DOWNLOAD_CLIPBOARD), reply;
	if (message.AddString("name", fName) == B_OK
		&& fClipHandler.SendMessage(&message, &reply) == B_OK
		&& reply.FindMessage("data", fData) == B_OK
		&& reply.FindMessenger("data source", &fDataSource) == B_OK
		&& reply.FindInt32("count", (int32 *)&fCount) == B_OK)
		return B_OK;

	return B_ERROR;
}
