/*	Copyright (c) 1998-99, Be Incorporated, All Rights Reserved.
 *	Distributed under the terms of the Be Sample Code license.
 *
 *	Copyright (c) 2000-2008, Ingo Weinhold <ingo_weinhold@gmx.de>,
 *	Copyright (c) 2000-2008, Stephan Aßmus <superstippi@gmx.de>,
 *	All Rights Reserved. Distributed under the terms of the MIT license.
 */
#include "VideoConsumer.h"
 
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
 
#include <Buffer.h>
#include <BufferGroup.h>
#include <NodeInfo.h>
#include <Bitmap.h>
#include <View.h>
#include <scheduler.h>
#include <TimeSource.h>
#include <MediaRoster.h>
 
#include "ColorSpaceToString.h"
#include "NodeManager.h"
#include "VideoTarget.h"
 
 
// debugging
//#define TRACE_VIDEO_CONSUMER
#ifdef TRACE_VIDEO_CONSUMER
# define TRACE(x...)		printf(x)
# define PROGRESS(x...)		printf(x)
# define FUNCTION(x...)		printf(x)
# define LOOP(x...)			printf(x)
# define ERROR(x...)		fprintf(stderr, x)
#else
# define TRACE(x...)
# define PROGRESS(x...)
# define FUNCTION(x...)
# define LOOP(x...)
# define ERROR(x...)		fprintf(stderr, x)
#endif
 
#define M1 ((double)1000000.0)
static const bigtime_t kMaxBufferLateness = 20000LL;
 
 
VideoConsumer::VideoConsumer(const char* name, BMediaAddOn* addon,
		const uint32 internal_id, NodeManager* manager,
		VideoTarget* target)
	: BMediaNode(name),
	  BMediaEventLooper(),
	  BBufferConsumer(B_MEDIA_RAW_VIDEO),
	  fInternalID(internal_id),
	  fAddOn(addon),
	  fConnectionActive(false),
	  fMyLatency(3000),
	  fOurBuffers(false),
	  fBuffers(NULL),
	  fManager(manager),
	  fTargetLock(),
	  fTarget(target),
	  fLastBufferIndex(-1),
	  fTryOverlay(true)
{
	FUNCTION("VideoConsumer::VideoConsumer\n");
 
	AddNodeKind(B_PHYSICAL_OUTPUT);
	SetEventLatency(0);
	
	for (uint32 i = 0; i < kBufferCount; i++) {
		fBitmap[i] = NULL;
		fBufferMap[i] = NULL;
	}
	
	SetPriority(B_DISPLAY_PRIORITY);
}
 
 
VideoConsumer::~VideoConsumer()
{
	Quit();
	DeleteBuffers();
}
 
 
BMediaAddOn*
VideoConsumer::AddOn(int32* cookie) const
{
	FUNCTION("VideoConsumer::AddOn\n");
	// do the right thing if we're ever used with an add-on
	*cookie = fInternalID;
	return fAddOn;
}
 
 
void
VideoConsumer::NodeRegistered()
{
	FUNCTION("VideoConsumer::NodeRegistered\n");
	fIn.destination.port = ControlPort();
	fIn.destination.id = 0;
	fIn.source = media_source::null;
	fIn.format.type = B_MEDIA_RAW_VIDEO;
	// wild cards yet
	fIn.format.u.raw_video = media_raw_video_format::wildcard;
	fIn.format.u.raw_video.interlace = 1;
	fIn.format.u.raw_video.display.format = B_NO_COLOR_SPACE;
	fIn.format.u.raw_video.display.bytes_per_row = 0;
	fIn.format.u.raw_video.display.line_width = 0;
	fIn.format.u.raw_video.display.line_count = 0;
 
	Run();
}
 
 
status_t
VideoConsumer::RequestCompleted(const media_request_info& info)
{
	FUNCTION("VideoConsumer::RequestCompleted\n");
	switch(info.what) {
		case media_request_info::B_SET_OUTPUT_BUFFERS_FOR:
			if (info.status != B_OK)
				ERROR("VideoConsumer::RequestCompleted: Not using our "
					"buffers!\n");
			break;
 
		default:
			break;
	}
	return B_OK;
}
 
 
status_t
VideoConsumer::HandleMessage(int32 message, const void* data, size_t size)
{
	return B_OK;
}
 
 
void
VideoConsumer::BufferReceived(BBuffer* buffer)
{
	LOOP("VideoConsumer::Buffer #%" B_PRId32 " received\n", buffer->ID());
 
	if (RunState() == B_STOPPED) {
		buffer->Recycle();
		return;
	}
 
	media_timed_event event(buffer->Header()->start_time,
		BTimedEventQueue::B_HANDLE_BUFFER, buffer,
		BTimedEventQueue::B_RECYCLE_BUFFER);
	EventQueue()->AddEvent(event);
}
 
 
void
VideoConsumer::ProducerDataStatus(const media_destination& forWhom,
	int32 status, bigtime_t atMediaTime)
{
	FUNCTION("VideoConsumer::ProducerDataStatus\n");
 
	if (forWhom != fIn.destination)	
		return;
}
 
 
status_t
VideoConsumer::CreateBuffers(const media_format& format)
{
	FUNCTION("VideoConsumer::CreateBuffers\n");
	
	// delete any old buffers
	DeleteBuffers();	
 
	status_t status = B_OK;
 
	// create a buffer group
	uint32 width = format.u.raw_video.display.line_width;
	uint32 height = format.u.raw_video.display.line_count;	
	color_space colorSpace = format.u.raw_video.display.format;
	PROGRESS("VideoConsumer::CreateBuffers - Width = %" B_PRIu32 " - "
		"Height = %" B_PRIu32 " - Colorspace = %d\n",
		width, height, colorSpace);
 
	fBuffers = new BBufferGroup();
	status = fBuffers->InitCheck();
	if (B_OK != status) {
		ERROR("VideoConsumer::CreateBuffers - ERROR CREATING BUFFER GROUP\n");
		return status;
	}
 
	// and attach the bitmaps to the buffer group
	BRect bounds(0, 0, width - 1, height - 1);
	for (uint32 i = 0; i < kBufferCount; i++) {
		// figure out the bitmap creation flags
		uint32 bitmapFlags = 0;
		if (fTryOverlay) {
			// try to use hardware overlay
			bitmapFlags |= B_BITMAP_WILL_OVERLAY;
			if (i == 0)
				bitmapFlags |= B_BITMAP_RESERVE_OVERLAY_CHANNEL;
		} else
			bitmapFlags = B_BITMAP_IS_LOCKED;
			
		fBitmap[i] = new BBitmap(bounds, bitmapFlags, colorSpace);
		status = fBitmap[i]->InitCheck();
		if (status >= B_OK) {
			buffer_clone_info info;
 
			uint8* bits = (uint8*)fBitmap[i]->Bits();
			info.area = area_for(bits);
			area_info bitmapAreaInfo;
			status = get_area_info(info.area, &bitmapAreaInfo);
			if (status != B_OK) {
				fprintf(stderr, "VideoConsumer::CreateBuffers() - "
					"get_area_info(): %s\n", strerror(status));
				return status;
			}
 
//printf("area info for bitmap %ld (%p):\n", i, fBitmap[i]->Bits());
//printf("        area: %ld\n", bitmapAreaInfo.area);
//printf("        size: %ld\n", bitmapAreaInfo.size);
//printf("        lock: %ld\n", bitmapAreaInfo.lock);
//printf("  protection: %ld\n", bitmapAreaInfo.protection);
//printf("    ram size: %ld\n", bitmapAreaInfo.ram_size);
//printf("  copy_count: %ld\n", bitmapAreaInfo.copy_count);
//printf("   out_count: %ld\n", bitmapAreaInfo.out_count);
//printf("     address: %p\n", bitmapAreaInfo.address);
 
			info.offset = bits - (uint8*)bitmapAreaInfo.address;
			info.size = (size_t)fBitmap[i]->BitsLength();
			info.flags = 0;
			info.buffer = 0;
				// the media buffer id
 
			BBuffer* buffer = NULL;
			if ((status = fBuffers->AddBuffer(info, &buffer)) != B_OK) {
				ERROR("VideoConsumer::CreateBuffers - ERROR ADDING BUFFER "
					"TO GROUP (%" B_PRId32 "): %s\n", i, strerror(status));
				return status;
			} else {
				PROGRESS("VideoConsumer::CreateBuffers - SUCCESSFUL ADD "
					"BUFFER TO GROUP\n");
			}
			fBufferMap[i] = buffer;
		} else {
			ERROR("VideoConsumer::CreateBuffers - ERROR CREATING VIDEO RING "
				"BUFFER (Index %" B_PRId32 " Width %" B_PRId32 " Height %"
				B_PRId32 " Colorspace %d: %s\n", i, width, height, colorSpace,
				strerror(status));
			return status;
		}	
	}
	
	FUNCTION("VideoConsumer::CreateBuffers - EXIT\n");
	return status;
}
 
 
void
VideoConsumer::DeleteBuffers()
{
	FUNCTION("VideoConsumer::DeleteBuffers\n");
	if (fBuffers) {
		fTargetLock.Lock();
		if (fLastBufferIndex >= 0) {
			if (fTarget)
				fTarget->SetBitmap(NULL);
			fLastBufferIndex = -1;
		}
		fTargetLock.Unlock();
 
		delete fBuffers;
		fBuffers = NULL;
 
		for (uint32 i = 0; i < kBufferCount; i++) {
			snooze(20000);
			delete fBitmap[i];
			fBitmap[i] = NULL;
		}
	}
	FUNCTION("VideoConsumer::DeleteBuffers - EXIT\n");
}
 
 
void
VideoConsumer::SetTarget(VideoTarget* target)
{
	fTargetLock.Lock();
	if (fTarget)
		fTarget->SetBitmap(NULL);
	fTarget = target;
	if (fTarget && fLastBufferIndex >= 0)
		fTarget->SetBitmap(fBitmap[fLastBufferIndex]);
	fTargetLock.Unlock();
}
 
 
void
VideoConsumer::SetTryOverlay(bool tryOverlay)
{
	fTryOverlay = tryOverlay;
}
 
 
status_t
VideoConsumer::Connected(const media_source& producer,
	const media_destination& where, const media_format& format,
	media_input* outInput)
{
	FUNCTION("VideoConsumer::Connected\n");
	
	fIn.source = producer;
	fIn.format = format;
	fIn.node = Node();
	sprintf(fIn.name, "Video Consumer");
	*outInput = fIn;
 
	uint32 userData = 0;
	int32 changeTag = 1;
	status_t ret = CreateBuffers(format);
	if (ret == B_OK) {
		// TODO: With overlay bitmaps, there seems to be a problem with
		// mapping the BBitmap areas into the BBuffers. Until that is fixed,
		// don't enable a shared BBufferGroup.
		if (!fTryOverlay) {
			ret = SetOutputBuffersFor(producer, fIn.destination, 
				fBuffers, &userData, &changeTag, true);
			if (ret != B_OK)
				ERROR("SetOutputBuffersFor() failed: %s\n", strerror(ret));
		}
		fIn.format.u.raw_video.display.bytes_per_row
			= fBitmap[0]->BytesPerRow();
	} else {
		ERROR("VideoConsumer::Connected - COULDN'T CREATE BUFFERS\n");
		return ret;
	}
 
	*outInput = fIn;
		// bytes per row might have changed
	fConnectionActive = true;
 
	FUNCTION("VideoConsumer::Connected - EXIT\n");
	return B_OK;
}
 
 
void
VideoConsumer::Disconnected(const media_source& producer,
	const media_destination& where)
{
	FUNCTION("VideoConsumer::Disconnected\n");
 
	if (where != fIn.destination || producer != fIn.source)
		return;
 
	// reclaim our buffers
	int32 changeTag = 0;
	SetOutputBuffersFor(producer, fIn.destination, NULL, NULL, &changeTag,
		false);
	if (fOurBuffers) {
		status_t reclaimError = fBuffers->ReclaimAllBuffers();
		if (reclaimError != B_OK) {
			fprintf(stderr, "VideoConsumer::Disconnected() - Failed to "
				"reclaim our buffers: %s\n", strerror(reclaimError));
		}
	}
	// disconnect the connection
	fIn.source = media_source::null;
	fConnectionActive = false;
 
	// Unset the target's bitmap. Just to be safe -- since it is usually
	// done when the stop event arrives, but someone may disonnect
	// without stopping us before.
	_UnsetTargetBuffer();
}
 
 
status_t
VideoConsumer::AcceptFormat(const media_destination& dest, media_format* format)
{
	FUNCTION("VideoConsumer::AcceptFormat\n");
	
	if (dest != fIn.destination) {
		ERROR("VideoConsumer::AcceptFormat - BAD DESTINATION\n");
		return B_MEDIA_BAD_DESTINATION;	
	}
	
	if (format->type == B_MEDIA_NO_TYPE)
		format->type = B_MEDIA_RAW_VIDEO;
	
	if (format->type != B_MEDIA_RAW_VIDEO) {
		ERROR("VideoConsumer::AcceptFormat - BAD FORMAT\n");
		return B_MEDIA_BAD_FORMAT;
	}
 
	if (format->u.raw_video.display.format
			!= media_raw_video_format::wildcard.display.format) {
		uint32 flags = 0;
		bool supported = bitmaps_support_space(
			format->u.raw_video.display.format, &flags);
#ifndef HAIKU_TARGET_PLATFORM_HAIKU
		// GRRR! BeOS implementation claims not
		// to support these formats, while they work just fine.
		switch (format->u.raw_video.display.format) {
			case B_YCbCr422:
			case B_YCbCr411:
			case B_YCbCr444:
			case B_YCbCr420:
				supported = true;
				break;
			default:
				break;
		}
#endif
		if (!supported) {
			// cannot create bitmaps with such a color space
			ERROR("AcceptFormat - unsupported color space for BBitmaps "
				"(%s)!\n",
				color_space_to_string(format->u.raw_video.display.format));
			return B_MEDIA_BAD_FORMAT;
		}
		if (!fTryOverlay && (flags & B_VIEWS_SUPPORT_DRAW_BITMAP) == 0) {
			// BViews do not support drawing such a bitmap
			ERROR("AcceptFormat - BViews cannot draw bitmaps in given "
				"colorspace (%s)!\n",
				color_space_to_string(format->u.raw_video.display.format));
			return B_MEDIA_BAD_FORMAT;
		}
	}
 
	#ifdef TRACE_VIDEO_CONSUMER
		char string[256];
		string[0] = 0;
		string_for_format(*format, string, 256);
		FUNCTION("VideoConsumer::AcceptFormat: %s\n", string);
	#endif
 
	return B_OK;
}
 
 
status_t
VideoConsumer::GetNextInput(int32* cookie, media_input* outInput)
{
	FUNCTION("VideoConsumer::GetNextInput\n");
 
	// custom build a destination for this connection
	// put connection number in id
 
	if (*cookie < 1) {
		fIn.node = Node();
		fIn.destination.id = *cookie;
		sprintf(fIn.name, "Video Consumer");
		*outInput = fIn;
		(*cookie)++;
		return B_OK;
	} else {
		return B_MEDIA_BAD_DESTINATION;
	}
}
 
 
void
VideoConsumer::DisposeInputCookie(int32 /*cookie*/)
{
}
 
 
status_t
VideoConsumer::GetLatencyFor(const media_destination& whom,
	bigtime_t* _latency, media_node_id* _timeSource)
{
	FUNCTION("VideoConsumer::GetLatencyFor\n");
	
	if (whom != fIn.destination)
		return B_MEDIA_BAD_DESTINATION;
	
	*_latency = fMyLatency;
	*_timeSource = TimeSource()->ID();
	return B_OK;
}
 
 
status_t
VideoConsumer::FormatChanged(const media_source& producer,
	const media_destination& consumer, int32 fromChangeCount,
	const media_format& format)
{
	FUNCTION("VideoConsumer::FormatChanged\n");
	
	if (consumer != fIn.destination)
		return B_MEDIA_BAD_DESTINATION;
 
	if (producer != fIn.source)
		return B_MEDIA_BAD_SOURCE;
 
	fIn.format = format;
	
	return CreateBuffers(format);
}
 
 
void
VideoConsumer::HandleEvent(const media_timed_event* event, bigtime_t lateness,
	bool realTimeEvent)
{
	LOOP("VideoConsumer::HandleEvent\n");
	
	switch (event->type) {
		case BTimedEventQueue::B_START:
			PROGRESS("VideoConsumer::HandleEvent - START\n");
			_SetPerformanceTimeBase(event->event_time);
			break;
		case BTimedEventQueue::B_WARP:
		case BTimedEventQueue::B_SEEK:
			PROGRESS("VideoConsumer::HandleEvent - WARP or SEEK\n");
			_SetPerformanceTimeBase(event->bigdata);
			break;
 
		case BTimedEventQueue::B_STOP:
			PROGRESS("VideoConsumer::HandleEvent - STOP\n");
			EventQueue()->FlushEvents(event->event_time, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER);
			// unset the target's bitmap
			_UnsetTargetBuffer();
			break;
 
		case BTimedEventQueue::B_HANDLE_BUFFER:
			LOOP("VideoConsumer::HandleEvent - HANDLE BUFFER\n");
			_HandleBuffer(static_cast<BBuffer*>(event->pointer));
			break;
		default:
			ERROR("VideoConsumer::HandleEvent - BAD EVENT\n");
			break;
	}			
}
 
 
void
VideoConsumer::_SetPerformanceTimeBase(bigtime_t performanceTime)
{
	fPerformanceTimeBase = performanceTime;
}
 
 
void
VideoConsumer::_HandleBuffer(BBuffer* buffer)
{
	if (RunState() != B_STARTED || !fConnectionActive) {
		TRACE("RunState() != B_STARTED\n");
		buffer->Recycle();
		return;
	}
 
	// See if this is one of our BBitmap buffers
	uint32 index = 0;
	fOurBuffers = true;
	while (index < kBufferCount) {
		if (buffer->ID() == fBufferMap[index]->ID())
			break;
		else
			index++;
	}
	if (index == kBufferCount) {
		// Buffers belong to consumer
		// NOTE: We maintain this in a member variable, since we still need
		// to recycle this buffer later on, in case it was the last buffer
		// received before shutting down.
		fOurBuffers = false;
		index = (fLastBufferIndex + 1) % kBufferCount;
	}
 
	bool recycle = true;
	bigtime_t now = TimeSource()->Now();
	if (RunMode() == B_OFFLINE
		|| now < buffer->Header()->start_time + kMaxBufferLateness) {
		// Only display the buffer if it's not too late, or if we are
		// in B_OFFLINE run-mode.
		if (!fOurBuffers) {
			memcpy(fBitmap[index]->Bits(), buffer->Data(),
				fBitmap[index]->BitsLength());
		}
		bigtime_t tooEarly = buffer->Header()->start_time - now;
		if (tooEarly > 3000)
			snooze(tooEarly);
 
		fTargetLock.Lock();
		if (fTarget) {
			fTarget->SetBitmap(fBitmap[index]);
			if (fOurBuffers) {
				// recycle the previous but not the current buffer
				if (fLastBufferIndex >= 0)
					fBufferMap[fLastBufferIndex]->Recycle();
				recycle = false;
			}
			fLastBufferIndex = index;
		}
		fTargetLock.Unlock();
	} else {
		// Drop the buffer if it's too late.
		if (fManager->LockWithTimeout(10000) == B_OK) {
			fManager->FrameDropped();
			fManager->Unlock();
		}
		PROGRESS("VideoConsumer::HandleEvent - DROPPED FRAME\n"
			"   start_time: %" B_PRIdBIGTIME ", current: %" B_PRIdBIGTIME ", "
			"latency: %" B_PRIdBIGTIME "\n",  buffer->Header()->start_time,
			TimeSource()->Now(), SchedulingLatency());
	}
	if (recycle)
		buffer->Recycle();
}
 
 
void
VideoConsumer::_UnsetTargetBuffer()
{
	fTargetLock.Lock();
	if (fLastBufferIndex >= 0) {
		if (fTarget)
			fTarget->SetBitmap(NULL);
		if (fOurBuffers)
			fBufferMap[fLastBufferIndex]->Recycle();
		fLastBufferIndex = -1;
	}
	fTargetLock.Unlock();
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fPerformanceTimeBase.