/*
 * Copyright 2001-2012, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Marc Flerackers (mflerackers@androme.be)
 *		Rene Gollent (rene@gollent.com)
 *		Alexandre Deckner (alex@zappotek.com)
 */


//!	BDragger represents a replicant "handle".


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

#include <Alert.h>
#include <Beep.h>
#include <Bitmap.h>
#include <Dragger.h>
#include <MenuItem.h>
#include <Message.h>
#include <PopUpMenu.h>
#ifndef HoG_GENODE
#include <Shelf.h>
#else
#include <support/String.h>
#endif
#ifndef HoG_GENODE
#include <SystemCatalog.h>
#endif
#include <Window.h>

#include <AutoLocker.h>

#ifndef HoG_GENODE
#include <AppServerLink.h>
#include <DragTrackingFilter.h>
#endif
#include <binary_compatibility/Interface.h>
#ifndef HoG_GENODE
#include <ServerProtocol.h>
#endif
#include <ViewPrivate.h>

#ifndef HoG_GENODE
#include "ZombieReplicantView.h"

using BPrivate::gSystemCatalog;
#endif

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Dragger"

#undef B_TRANSLATE
#ifndef HoG_GENODE
#define B_TRANSLATE(str) \
	gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "Dragger")

#else

#define B_TRANSLATE(str) \
	(str)
#endif
///TODO: no need for the above patch, I relented and added BCatalog !


static const uint32 kMsgDragStarted = 'Drgs';

static const unsigned char kHandBitmap[] = {
	255, 255,   0,   0,   0, 255, 255, 255,
	255, 255,   0, 131, 131,   0, 255, 255,
	  0,   0,   0,   0, 131, 131,   0,   0,
	  0, 131,   0,   0, 131, 131,   0,   0,
	  0, 131, 131, 131, 131, 131,   0,   0,
	255,   0, 131, 131, 131, 131,   0,   0,
	255, 255,   0,   0,   0,   0,   0,   0,
	255, 255, 255, 255, 255, 255,   0,   0
};


namespace {

struct DraggerManager {
	bool	visible;
	bool	visibleInitialized;
	BList	list;

	DraggerManager()
		:
		visible(false),
		visibleInitialized(false),
		list(),
		fLock("BDragger static")
	{
	}

	bool Lock()
	{
		return fLock.Lock();
	}

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

	static DraggerManager* Default()
	{
		if (sDefaultInstance == NULL)
			pthread_once(&sDefaultInitOnce, &_InitSingleton);

		return sDefaultInstance;
	}

private:
	static void _InitSingleton()
	{
		sDefaultInstance = new DraggerManager;
	}

private:
	BLocker					fLock;

	static pthread_once_t	sDefaultInitOnce;
	static DraggerManager*	sDefaultInstance;
};

pthread_once_t DraggerManager::sDefaultInitOnce = PTHREAD_ONCE_INIT;
DraggerManager* DraggerManager::sDefaultInstance = NULL;

}	// unnamed namespace


BDragger::BDragger(BRect frame, BView* target, uint32 resizingMode,
	uint32 flags)
	:
	BView(frame, "_dragger_", resizingMode, flags),
	fTarget(target),
	fRelation(TARGET_UNKNOWN),
	fShelf(NULL),
	fTransition(false),
	fIsZombie(false),
	fErrCount(0),
	fPopUpIsCustom(false),
	fBitmap(NULL),
	fPopUp(NULL)
{
	_InitData();
}


BDragger::BDragger(BView* target, uint32 flags)
	:
	BView("_dragger_", flags),
	fTarget(target),
	fRelation(TARGET_UNKNOWN),
	fShelf(NULL),
	fTransition(false),
	fIsZombie(false),
	fErrCount(0),
	fPopUpIsCustom(false),
	fBitmap(NULL),
	fPopUp(NULL)
{
	_InitData();
}


BDragger::BDragger(BMessage* data)
	:
	BView(data),
	fTarget(NULL),
	fRelation(TARGET_UNKNOWN),
	fShelf(NULL),
	fTransition(false),
	fIsZombie(false),
	fErrCount(0),
	fPopUpIsCustom(false),
	fBitmap(NULL),
	fPopUp(NULL)
{
	data->FindInt32("_rel", (int32*)&fRelation);

	_InitData();

	BMessage popupMsg;
	if (data->FindMessage("_popup", &popupMsg) == B_OK) {
		BArchivable* archivable = instantiate_object(&popupMsg);

		if (archivable) {
			fPopUp = dynamic_cast<BPopUpMenu*>(archivable);
			fPopUpIsCustom = true;
		}
	}
}


BDragger::~BDragger()
{
	delete fPopUp;
	delete fBitmap;
}


BArchivable	*
BDragger::Instantiate(BMessage* data)
{
	if (validate_instantiation(data, "BDragger"))
		return new BDragger(data);
	return NULL;
}


status_t
BDragger::Archive(BMessage* data, bool deep) const
{
	status_t ret = BView::Archive(data, deep);
	if (ret != B_OK)
		return ret;

	BMessage popupMsg;

	if (fPopUp != NULL && fPopUpIsCustom) {
		bool windowLocked = fPopUp->Window()->Lock();

		ret = fPopUp->Archive(&popupMsg, deep);

		if (windowLocked) {
			fPopUp->Window()->Unlock();
				// TODO: Investigate, in some (rare) occasions the menu window
				//		 has already been unlocked
		}

		if (ret == B_OK)
			ret = data->AddMessage("_popup", &popupMsg);
	}

	if (ret == B_OK)
		ret = data->AddInt32("_rel", fRelation);
	return ret;
}


void
BDragger::AttachedToWindow()
{
	if (fIsZombie) {
#ifndef HoG_GENODE
		SetLowColor(kZombieColor);
		SetViewColor(kZombieColor);
#endif
	} else {
		SetLowColor(B_TRANSPARENT_COLOR);
		SetViewColor(B_TRANSPARENT_COLOR);
	}

	_DetermineRelationship();
	_AddToList();

#ifndef HoG_GENODE
	AddFilter(new DragTrackingFilter(this, kMsgDragStarted));
#endif
}


void
BDragger::DetachedFromWindow()
{
	_RemoveFromList();
}


void
BDragger::Draw(BRect update)
{
	BRect bounds(Bounds());

#ifndef HoG_GENODE
	if (AreDraggersDrawn() && (fShelf == NULL || fShelf->AllowsDragging())) {
#else
	if (AreDraggersDrawn()) {
#endif
		if (Parent() != NULL && (Parent()->Flags() & B_DRAW_ON_CHILDREN) == 0) {
			uint32 flags = Parent()->Flags();
			Parent()->SetFlags(flags | B_DRAW_ON_CHILDREN);
			SetHighColor(Parent()->ViewColor());
			FillRect(Bounds());
			Parent()->Draw(Frame() & ConvertToParent(update));
			Parent()->Flush();
			Parent()->SetFlags(flags);
		}

		BPoint where = bounds.RightBottom() - BPoint(fBitmap->Bounds().Width(),
			fBitmap->Bounds().Height());
		SetDrawingMode(B_OP_OVER);
		DrawBitmap(fBitmap, where);
		SetDrawingMode(B_OP_COPY);

		if (fIsZombie) {
			// TODO: should draw it differently ?
		}
	} else if (IsVisibilityChanging()) {
		if (Parent() != NULL) {
			if ((Parent()->Flags() & B_DRAW_ON_CHILDREN) == 0) {
				uint32 flags = Parent()->Flags();
				Parent()->SetFlags(flags | B_DRAW_ON_CHILDREN);
				Parent()->Invalidate(Frame() & ConvertToParent(update));
				Parent()->SetFlags(flags);
			}
		} else {
			SetHighColor(255, 255, 255);
			FillRect(bounds);
		}
	}
}


void
BDragger::MouseDown(BPoint where)
{
	if (fTarget == NULL || !AreDraggersDrawn())
		return;

	uint32 buttons;
	Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);

	if (fShelf != NULL && (buttons & B_SECONDARY_MOUSE_BUTTON) != 0)
		_ShowPopUp(fTarget, where);
}


void
BDragger::MouseUp(BPoint point)
{
	BView::MouseUp(point);
}


void
BDragger::MouseMoved(BPoint point, uint32 code, const BMessage* msg)
{
	BView::MouseMoved(point, code, msg);
}


void
BDragger::MessageReceived(BMessage* msg)
{
	switch (msg->what) {
		case B_TRASH_TARGET:
			if (fShelf != NULL)
				Window()->PostMessage(kDeleteReplicant, fTarget, NULL);
			else {
				BAlert* alert = new BAlert(B_TRANSLATE("Warning"),
					B_TRANSLATE("Can't delete this replicant from its original "
					"application. Life goes on."),
					B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_FROM_WIDEST,
					B_WARNING_ALERT);
				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
				alert->Go(NULL);
			}
			break;

		case _SHOW_DRAG_HANDLES_:
			// This code is used whenever the "are draggers drawn" option is
			// changed.
			if (fRelation == TARGET_IS_CHILD) {
				fTransition = true;
				Draw(Bounds());
				Flush();
				fTransition = false;
			} else {
#ifndef HoG_GENODE
				if ((fShelf != NULL && fShelf->AllowsDragging()
						&& AreDraggersDrawn())
					|| AreDraggersDrawn()) {
					Show();
				} else
					Hide();
#endif
			}
			break;

		case kMsgDragStarted:
			if (fTarget != NULL) {
				BMessage archive(B_ARCHIVED_OBJECT);

				if (fRelation == TARGET_IS_PARENT)
					fTarget->Archive(&archive);
				else if (fRelation == TARGET_IS_CHILD)
					Archive(&archive);
				else if (fTarget->Archive(&archive)) {
					BMessage archivedSelf(B_ARCHIVED_OBJECT);

					if (Archive(&archivedSelf))
						archive.AddMessage("__widget", &archivedSelf);
				}

				archive.AddInt32("be:actions", B_TRASH_TARGET);
				BPoint offset;
				drawing_mode mode;
				BBitmap* bitmap = DragBitmap(&offset, &mode);
				if (bitmap != NULL)
					DragMessage(&archive, bitmap, mode, offset, this);
				else {
					DragMessage(&archive, ConvertFromScreen(
						fTarget->ConvertToScreen(fTarget->Bounds())), this);
				}
			}
			break;

		default:
			BView::MessageReceived(msg);
			break;
	}
}


void
BDragger::FrameMoved(BPoint newPosition)
{
	BView::FrameMoved(newPosition);
}


void
BDragger::FrameResized(float newWidth, float newHeight)
{
	BView::FrameResized(newWidth, newHeight);
}


status_t
BDragger::ShowAllDraggers()
{
#ifndef HoG_GENODE
	BPrivate::AppServerLink link;
	link.StartMessage(AS_SET_SHOW_ALL_DRAGGERS);
	link.Attach<bool>(true);

	status_t status = link.Flush();
	if (status == B_OK) {
		DraggerManager* manager = DraggerManager::Default();
		AutoLocker<DraggerManager> locker(manager);
		manager->visible = true;
		manager->visibleInitialized = true;
	}

	return status;
#else
	return -1;
#endif
}


status_t
BDragger::HideAllDraggers()
{
#ifndef HoG_GENODE
	BPrivate::AppServerLink link;
	link.StartMessage(AS_SET_SHOW_ALL_DRAGGERS);
	link.Attach<bool>(false);

	status_t status = link.Flush();
	if (status == B_OK) {
		DraggerManager* manager = DraggerManager::Default();
		AutoLocker<DraggerManager> locker(manager);
		manager->visible = false;
		manager->visibleInitialized = true;
	}

	return status;
#else
	return -1;
#endif
}


bool
BDragger::AreDraggersDrawn()
{
#ifndef HoG_GENODE
	DraggerManager* manager = DraggerManager::Default();
	AutoLocker<DraggerManager> locker(manager);

	if (!manager->visibleInitialized) {
		BPrivate::AppServerLink link;
		link.StartMessage(AS_GET_SHOW_ALL_DRAGGERS);

		status_t status;
		if (link.FlushWithReply(status) == B_OK && status == B_OK) {
			link.Read<bool>(&manager->visible);
			manager->visibleInitialized = true;
		} else
			return false;
	}

	return manager->visible;
#else
	return true;
#endif
}


BHandler*
BDragger::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier,
	int32 form, const char* property)
{
	return BView::ResolveSpecifier(message, index, specifier, form, property);
}


status_t
BDragger::GetSupportedSuites(BMessage* data)
{
	return BView::GetSupportedSuites(data);
}


status_t
BDragger::Perform(perform_code code, void* _data)
{
	switch (code) {
		case PERFORM_CODE_MIN_SIZE:
			((perform_data_min_size*)_data)->return_value
				= BDragger::MinSize();
			return B_OK;
		case PERFORM_CODE_MAX_SIZE:
			((perform_data_max_size*)_data)->return_value
				= BDragger::MaxSize();
			return B_OK;
		case PERFORM_CODE_PREFERRED_SIZE:
			((perform_data_preferred_size*)_data)->return_value
				= BDragger::PreferredSize();
			return B_OK;
		case PERFORM_CODE_LAYOUT_ALIGNMENT:
			((perform_data_layout_alignment*)_data)->return_value
				= BDragger::LayoutAlignment();
			return B_OK;
		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
			((perform_data_has_height_for_width*)_data)->return_value
				= BDragger::HasHeightForWidth();
			return B_OK;
		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
		{
			perform_data_get_height_for_width* data
				= (perform_data_get_height_for_width*)_data;
			BDragger::GetHeightForWidth(data->width, &data->min, &data->max,
				&data->preferred);
			return B_OK;
}
		case PERFORM_CODE_SET_LAYOUT:
		{
			perform_data_set_layout* data = (perform_data_set_layout*)_data;
			BDragger::SetLayout(data->layout);
			return B_OK;
		}
		case PERFORM_CODE_LAYOUT_INVALIDATED:
		{
			perform_data_layout_invalidated* data
				= (perform_data_layout_invalidated*)_data;
			BDragger::LayoutInvalidated(data->descendants);
			return B_OK;
		}
		case PERFORM_CODE_DO_LAYOUT:
		{
			BDragger::DoLayout();
			return B_OK;
		}
	}

	return BView::Perform(code, _data);
}


void
BDragger::ResizeToPreferred()
{
	BView::ResizeToPreferred();
}


void
BDragger::GetPreferredSize(float* _width, float* _height)
{
	BView::GetPreferredSize(_width, _height);
}


void
BDragger::MakeFocus(bool state)
{
	BView::MakeFocus(state);
}


void
BDragger::AllAttached()
{
	BView::AllAttached();
}


void
BDragger::AllDetached()
{
	BView::AllDetached();
}


status_t
BDragger::SetPopUp(BPopUpMenu* menu)
{
	if (menu != NULL && menu != fPopUp) {
		delete fPopUp;
		fPopUp = menu;
		fPopUpIsCustom = true;
		return B_OK;
	}
	return B_ERROR;
}


BPopUpMenu*
BDragger::PopUp() const
{
	if (fPopUp == NULL && fTarget)
		const_cast<BDragger*>(this)->_BuildDefaultPopUp();

	return fPopUp;
}


bool
BDragger::InShelf() const
{
	return fShelf != NULL;
}


BView*
BDragger::Target() const
{
	return fTarget;
}


BBitmap*
BDragger::DragBitmap(BPoint* /*offset*/, drawing_mode* /*mode*/)
{
	return NULL;
}


bool
BDragger::IsVisibilityChanging() const
{
	return fTransition;
}


void BDragger::_ReservedDragger2() {}
void BDragger::_ReservedDragger3() {}
void BDragger::_ReservedDragger4() {}


#ifndef HoG_GENODE
BDragger&
BDragger::operator=(const BDragger&)
{
	return *this;
}
#endif


/*static*/ void
BDragger::_UpdateShowAllDraggers(bool visible)
{
	DraggerManager* manager = DraggerManager::Default();
	AutoLocker<DraggerManager> locker(manager);

	manager->visibleInitialized = true;
	manager->visible = visible;

	for (int32 i = manager->list.CountItems(); i-- > 0;) {
		BDragger* dragger = (BDragger*)manager->list.ItemAt(i);
		BMessenger target(dragger);
		target.SendMessage(_SHOW_DRAG_HANDLES_);
	}
}


void
BDragger::_InitData()
{
	fBitmap = new BBitmap(BRect(0.0f, 0.0f, 7.0f, 7.0f), B_CMAP8, false, false);
	fBitmap->SetBits(kHandBitmap, fBitmap->BitsLength(), 0, B_CMAP8);
}


void
BDragger::_AddToList()
{
	DraggerManager* manager = DraggerManager::Default();
	AutoLocker<DraggerManager> locker(manager);
	manager->list.AddItem(this);

	bool allowsDragging = true;
#ifndef HoG_GENODE
	if (fShelf)
		allowsDragging = fShelf->AllowsDragging();
#endif

	if (!AreDraggersDrawn() || !allowsDragging) {
		// The dragger is not shown - but we can't hide us in case we're the
		// parent of the actual target view (because then you couldn't see
		// it anymore).
		if (fRelation != TARGET_IS_CHILD && !IsHidden())
			Hide();
	}
}


void
BDragger::_RemoveFromList()
{
	DraggerManager* manager = DraggerManager::Default();
	AutoLocker<DraggerManager> locker(manager);
	manager->list.RemoveItem(this);
}


status_t
BDragger::_DetermineRelationship()
{
	if (fTarget != NULL) {
		if (fTarget == Parent())
			fRelation = TARGET_IS_PARENT;
		else if (fTarget == ChildAt(0))
			fRelation = TARGET_IS_CHILD;
		else
			fRelation = TARGET_IS_SIBLING;
	} else {
		if (fRelation == TARGET_IS_PARENT)
			fTarget = Parent();
		else if (fRelation == TARGET_IS_CHILD)
			fTarget = ChildAt(0);
		else
			return B_ERROR;
	}

	if (fRelation == TARGET_IS_PARENT) {
		BRect bounds(Frame());
		BRect parentBounds(Parent()->Bounds());
		if (!parentBounds.Contains(bounds)) {
			MoveTo(parentBounds.right - bounds.Width(),
				parentBounds.bottom - bounds.Height());
		}
	}

	return B_OK;
}


status_t
BDragger::_SetViewToDrag(BView* target)
{
	if (target->Window() != Window())
		return B_ERROR;

	fTarget = target;

	if (Window() != NULL)
		_DetermineRelationship();

	return B_OK;
}


void
BDragger::_SetShelf(BShelf* shelf)
{
	fShelf = shelf;
}


void
BDragger::_SetZombied(bool state)
{
	fIsZombie = state;

	if (state) {
#ifndef HoG_GENODE
		SetLowColor(kZombieColor);
		SetViewColor(kZombieColor);
#endif
	}
}


void
BDragger::_BuildDefaultPopUp()
{
	fPopUp = new BPopUpMenu("Shelf", false, false, B_ITEMS_IN_COLUMN);

	// About
	BMessage* msg = new BMessage(B_ABOUT_REQUESTED);

	const char* name = fTarget->Name();
	if (name != NULL)
		msg->AddString("target", name);

	BString about(B_TRANSLATE("About %app" B_UTF8_ELLIPSIS));
	about.ReplaceFirst("%app", name);

	fPopUp->AddItem(new BMenuItem(about.String(), msg));
	fPopUp->AddSeparatorItem();
	fPopUp->AddItem(new BMenuItem(B_TRANSLATE("Remove replicant"),
		new BMessage(kDeleteReplicant)));
}


void
BDragger::_ShowPopUp(BView* /*target*/, BPoint where)
{
	BPoint point = ConvertToScreen(where);

	if (fPopUp == NULL && fTarget != NULL)
		_BuildDefaultPopUp();

	fPopUp->SetTargetForItems(fTarget);

	float menuWidth, menuHeight;
	fPopUp->GetPreferredSize(&menuWidth, &menuHeight);
	BRect rect(0, 0, menuWidth, menuHeight);
	rect.InsetBy(-0.5, -0.5);
	rect.OffsetTo(point);

	fPopUp->Go(point, true, false, rect, true);
}


#if __GNUC__ < 3

extern "C" BBitmap*
_ReservedDragger1__8BDragger(BDragger* dragger, BPoint* offset,
	drawing_mode* mode)
{
	return dragger->BDragger::DragBitmap(offset, mode);
}

#endif
