/*
 * Copyright 2006-2012, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Jérôme Duval, korli@users.berlios.de
 *		Philippe Houdoin, philippe.houdoin@free.fr
 *		Stefano Ceccherini, burton666@libero.it
 */

#include <kernel/image.h>

#include <GLView.h>

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

#include <DirectWindow.h>
#include <GLRenderer.h>

#include "interface/DirectWindowPrivate.h"
#include "GLDispatcher.h"
#include "GLRendererRoster.h"


struct glview_direct_info {
	direct_buffer_info* direct_info;
	bool direct_connected;
	bool enable_direct_mode;

	glview_direct_info();
	~glview_direct_info();
};


BGLView::BGLView(BRect rect, const char* name, ulong resizingMode, ulong mode,
	ulong options)
	:
	BView(rect, name, B_FOLLOW_ALL_SIDES, mode | B_WILL_DRAW | B_FRAME_EVENTS),
		//  | B_FULL_UPDATE_ON_RESIZE)
	fGc(NULL),
	fOptions(options),
	fDitherCount(0),
	fDrawLock("BGLView draw lock"),
	fDisplayLock("BGLView display lock"),
	fClipInfo(NULL),
	fRenderer(NULL),
	fRoster(NULL),
	fDitherMap(NULL)
{
	fRoster = new GLRendererRoster(this, options);
	fRenderer = fRoster->GetRenderer();
}


BGLView::~BGLView()
{
	delete fClipInfo;
	if (fRenderer)
		fRenderer->Release();
}


void
BGLView::LockGL()
{
	// TODO: acquire the OpenGL API lock it on this glview

	fDisplayLock.Lock();
	if (fRenderer != NULL && fDisplayLock.CountLocks() == 1)
		fRenderer->LockGL();
}


void
BGLView::UnlockGL()
{
	if (fRenderer != NULL && fDisplayLock.CountLocks() == 1)
		fRenderer->UnlockGL();
	fDisplayLock.Unlock();

	// TODO: release the GL API lock to others glviews
}


void
BGLView::SwapBuffers()
{
	SwapBuffers(false);
}


void
BGLView::SwapBuffers(bool vSync)
{
	if (fRenderer) {
		_LockDraw();
		fRenderer->SwapBuffers(vSync);
		_UnlockDraw();
	}
}


BView*
BGLView::EmbeddedView()
{
	return NULL;
}


void*
BGLView::GetGLProcAddress(const char* procName)
{
	BGLDispatcher* glDispatcher = NULL;

	if (fRenderer)
		glDispatcher = fRenderer->GLDispatcher();

	if (glDispatcher)
		return (void*)glDispatcher->AddressOf(procName);

	return NULL;
}


status_t
BGLView::CopyPixelsOut(BPoint source, BBitmap* dest)
{
	if (!fRenderer)
		return B_ERROR;

	if (!dest || !dest->Bounds().IsValid())
		return B_BAD_VALUE;

	return fRenderer->CopyPixelsOut(source, dest);
}


status_t
BGLView::CopyPixelsIn(BBitmap* source, BPoint dest)
{
	if (!fRenderer)
		return B_ERROR;

	if (!source || !source->Bounds().IsValid())
		return B_BAD_VALUE;

	return fRenderer->CopyPixelsIn(source, dest);
}


/*!	Mesa's GLenum is not ulong but uint, so we can't use GLenum
	without breaking this method signature.
	Instead, we have to use the effective BeOS's SGI OpenGL GLenum type:
	unsigned long.
 */
void
BGLView::ErrorCallback(unsigned long errorCode)
{
	char msg[32];
	sprintf(msg, "GL: Error code $%04lx.", errorCode);
	// TODO: under BeOS R5, it call debugger(msg);
	fprintf(stderr, "%s\n", msg);
}


void
BGLView::Draw(BRect updateRect)
{
	if (fRenderer) {
		_LockDraw();
		fRenderer->Draw(updateRect);
		_UnlockDraw();
		return;
	}
	// TODO: auto-size and center the string
	MovePenTo(8, 32);
	DrawString("No OpenGL renderer available!");
}


void
BGLView::AttachedToWindow()
{
	BView::AttachedToWindow();

	fBounds = Bounds();
	for (BView* view = this; view != NULL; view = view->Parent())
		view->ConvertToParent(&fBounds);

	if (fRenderer != NULL) {
		// Jackburton: The following code was commented because it doesn't look
		// good in "direct" mode:
		// when the window is moved, the app_server doesn't paint the view's
		// background, and the stuff behind the window itself shows up.
		// Setting the view color to black, instead, looks a bit more elegant.
#if 0
		// Don't paint white window background when resized
		SetViewColor(B_TRANSPARENT_32_BIT);
#else
		SetViewColor(0, 0, 0);
#endif

		// Set default OpenGL viewport:
		LockGL();
		glViewport(0, 0, Bounds().IntegerWidth(), Bounds().IntegerHeight());
		UnlockGL();
		fRenderer->FrameResized(Bounds().IntegerWidth(),
			Bounds().IntegerHeight());

		if (fClipInfo) {
			fRenderer->DirectConnected(fClipInfo->direct_info);
			fRenderer->EnableDirectMode(fClipInfo->enable_direct_mode);
		}

		return;
	}

	fprintf(stderr, "no renderer found! \n");

	// No Renderer, no rendering. Setup a minimal "No Renderer" string drawing
	// context
	SetFont(be_bold_font);
	// SetFontSize(16);
}


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


void
BGLView::DetachedFromWindow()
{
	if (fRenderer)
		fRenderer->Release();
	fRenderer = NULL;

	BView::DetachedFromWindow();
}


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


void
BGLView::FrameResized(float width, float height)
{
	fBounds = Bounds();
	for (BView* v = this; v; v = v->Parent())
		v->ConvertToParent(&fBounds);

	if (fRenderer) {
		LockGL();
		_LockDraw();
		_CallDirectConnected();
		fRenderer->FrameResized(width, height);
		_UnlockDraw();
		UnlockGL();
	}

	BView::FrameResized(width, height);
}


status_t
BGLView::Perform(perform_code d, void* arg)
{
	return BView::Perform(d, arg);
}


status_t
BGLView::Archive(BMessage* data, bool deep) const
{
	return BView::Archive(data, deep);
}


void
BGLView::MessageReceived(BMessage* msg)
{
	BView::MessageReceived(msg);
}


void
BGLView::SetResizingMode(uint32 mode)
{
	BView::SetResizingMode(mode);
}


void
BGLView::GetPreferredSize(float* _width, float* _height)
{
	if (_width)
		*_width = 0;
	if (_height)
		*_height = 0;
}


void
BGLView::Show()
{
	BView::Show();
}


void
BGLView::Hide()
{
	BView::Hide();
}


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


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


void
BGLView::DirectConnected(direct_buffer_info* info)
{
	if (fClipInfo == NULL) {
		fClipInfo = new (std::nothrow) glview_direct_info();
		if (fClipInfo == NULL)
			return;
	}

	direct_buffer_info* localInfo = fClipInfo->direct_info;

	switch (info->buffer_state & B_DIRECT_MODE_MASK) {
		case B_DIRECT_START:
			fClipInfo->direct_connected = true;
			memcpy(localInfo, info, DIRECT_BUFFER_INFO_AREA_SIZE);
			_UnlockDraw();
			break;

		case B_DIRECT_MODIFY:
			_LockDraw();
			memcpy(localInfo, info, DIRECT_BUFFER_INFO_AREA_SIZE);
			_UnlockDraw();
			break;

		case B_DIRECT_STOP:
			fClipInfo->direct_connected = false;
			_LockDraw();
			break;
	}

	if (fRenderer)
		_CallDirectConnected();
}


void
BGLView::EnableDirectMode(bool enabled)
{
	if (fRenderer)
		fRenderer->EnableDirectMode(enabled);
	if (fClipInfo == NULL) {
		fClipInfo = new (std::nothrow) glview_direct_info();
		if (fClipInfo == NULL)
			return;
	}

	fClipInfo->enable_direct_mode = enabled;
}


void
BGLView::_LockDraw()
{
	if (!fClipInfo || !fClipInfo->enable_direct_mode)
		return;

	fDrawLock.Lock();
}


void
BGLView::_UnlockDraw()
{
	if (!fClipInfo || !fClipInfo->enable_direct_mode)
		return;

	fDrawLock.Unlock();
}


void
BGLView::_CallDirectConnected()
{
	if (!fClipInfo)
		return;

	direct_buffer_info* localInfo = fClipInfo->direct_info;
	direct_buffer_info* info = (direct_buffer_info*)malloc(
		DIRECT_BUFFER_INFO_AREA_SIZE);
	if (info == NULL)
		return;

	memcpy(info, localInfo, DIRECT_BUFFER_INFO_AREA_SIZE);

	// Collect the rects into a BRegion, then clip to the view's bounds
	BRegion region;
	for (uint32 c = 0; c < localInfo->clip_list_count; c++)
		region.Include(localInfo->clip_list[c]);
	BRegion boundsRegion = fBounds.OffsetByCopy(localInfo->window_bounds.left,
		localInfo->window_bounds.top);
	info->window_bounds = boundsRegion.RectAtInt(0);
		// window_bounds are now view bounds
	region.IntersectWith(&boundsRegion);

	info->clip_list_count = region.CountRects();
	info->clip_bounds = region.FrameInt();

	for (uint32 c = 0; c < info->clip_list_count; c++)
		info->clip_list[c] = region.RectAtInt(c);
	fRenderer->DirectConnected(info);
	free(info);
}


//---- virtual reserved methods ----------


void BGLView::_ReservedGLView1() {}
void BGLView::_ReservedGLView2() {}
void BGLView::_ReservedGLView3() {}
void BGLView::_ReservedGLView4() {}
void BGLView::_ReservedGLView5() {}
void BGLView::_ReservedGLView6() {}
void BGLView::_ReservedGLView7() {}
void BGLView::_ReservedGLView8() {}


// #pragma mark -


// BeOS compatibility: contrary to others BView's contructors,
// BGLView one wants a non-const name argument.
BGLView::BGLView(BRect rect, char* name, ulong resizingMode, ulong mode,
	ulong options)
	:
	BView(rect, name, B_FOLLOW_ALL_SIDES, mode | B_WILL_DRAW | B_FRAME_EVENTS),
	fGc(NULL),
	fOptions(options),
	fDitherCount(0),
	fDrawLock("BGLView draw lock"),
	fDisplayLock("BGLView display lock"),
	fClipInfo(NULL),
	fRenderer(NULL),
	fRoster(NULL),
	fDitherMap(NULL)
{
	fRoster = new GLRendererRoster(this, options);
}


#if 0
// TODO: implement BGLScreen class...


BGLScreen::BGLScreen(char* name, ulong screenMode, ulong options,
		status_t* error, bool debug)
	:
	BWindowScreen(name, screenMode, error, debug)
{
}


BGLScreen::~BGLScreen()
{
}


void
BGLScreen::LockGL()
{
}


void
BGLScreen::UnlockGL()
{
}


void
BGLScreen::SwapBuffers()
{
}


void
BGLScreen::ErrorCallback(unsigned long errorCode)
{
	// Mesa's GLenum is not ulong but uint!
	char msg[32];
	sprintf(msg, "GL: Error code $%04lx.", errorCode);
	// debugger(msg);
	fprintf(stderr, "%s\n", msg);
	return;
}


void
BGLScreen::ScreenConnected(bool enabled)
{
}


void
BGLScreen::FrameResized(float width, float height)
{
	return BWindowScreen::FrameResized(width, height);
}


status_t
BGLScreen::Perform(perform_code d, void* arg)
{
	return BWindowScreen::Perform(d, arg);
}


status_t
BGLScreen::Archive(BMessage* data, bool deep) const
{
	return BWindowScreen::Archive(data, deep);
}


void
BGLScreen::MessageReceived(BMessage* msg)
{
	BWindowScreen::MessageReceived(msg);
}


void
BGLScreen::Show()
{
	BWindowScreen::Show();
}


void
BGLScreen::Hide()
{
	BWindowScreen::Hide();
}


BHandler*
BGLScreen::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
	int32 form, const char* property)
{
	return BWindowScreen::ResolveSpecifier(msg, index, specifier,
		form, property);
}


status_t
BGLScreen::GetSupportedSuites(BMessage* data)
{
	return BWindowScreen::GetSupportedSuites(data);
}


//---- virtual reserved methods ----------

void BGLScreen::_ReservedGLScreen1() {}
void BGLScreen::_ReservedGLScreen2() {}
void BGLScreen::_ReservedGLScreen3() {}
void BGLScreen::_ReservedGLScreen4() {}
void BGLScreen::_ReservedGLScreen5() {}
void BGLScreen::_ReservedGLScreen6() {}
void BGLScreen::_ReservedGLScreen7() {}
void BGLScreen::_ReservedGLScreen8() {}
#endif


const char* color_space_name(color_space space)
{
#define C2N(a)	case a:	return #a

	switch (space) {
	C2N(B_RGB24);
	C2N(B_RGB32);
	C2N(B_RGBA32);
	C2N(B_RGB32_BIG);
	C2N(B_RGBA32_BIG);
	C2N(B_GRAY8);
	C2N(B_GRAY1);
	C2N(B_RGB16);
	C2N(B_RGB15);
	C2N(B_RGBA15);
	C2N(B_CMAP8);
	default:
		return "Unknown!";
	};

#undef C2N
};


glview_direct_info::glview_direct_info()
{
	// TODO: See direct_window_data() in app_server's ServerWindow.cpp
	direct_info = (direct_buffer_info*)calloc(1, DIRECT_BUFFER_INFO_AREA_SIZE);
	direct_connected = false;
	enable_direct_mode = false;
}


glview_direct_info::~glview_direct_info()
{
	free(direct_info);
}

