// STL includes
#include <cassert>
#include <iostream>

// Local includes
#include "grabber/DispmanxFrameGrabber.h"

DispmanxFrameGrabber::DispmanxFrameGrabber(const unsigned width, const unsigned height) :
	_vc_display(0),
	_vc_resource(0),
	_vc_flags(0),
	_width(width),
	_height(height),
	_videoMode(VIDEO_2D),
	_cropLeft(0),
	_cropRight(0),
	_cropTop(0),
	_cropBottom(0),
	_captureBuffer(new ColorRgba[0]),
	_captureBufferSize(0),
	_log(Logger::getInstance("DISPMANXGRABBER"))
{
	// Initiase BCM
	bcm_host_init();

	{
		// Check if the display can be opened and display the current resolution
		// Open the connection to the display
		_vc_display = vc_dispmanx_display_open(0);
		assert(_vc_display > 0);

		// Obtain the display information
		DISPMANX_MODEINFO_T vc_info;
		int result = vc_dispmanx_display_get_info(_vc_display, &vc_info);
		// Keep compiler happy in 'release' mode
		(void)result;
		assert(result == 0);
		Info(_log, "Display opened with resolution: %dx%d", vc_info.width, vc_info.height);

		// Close the displaye
		vc_dispmanx_display_close(_vc_display);
	}

	// Create the resources for capturing image
	uint32_t vc_nativeImageHandle;
	_vc_resource = vc_dispmanx_resource_create(
			VC_IMAGE_RGBA32,
			width,
			height,
			&vc_nativeImageHandle);
	assert(_vc_resource);

	// Define the capture rectangle with the same size
	vc_dispmanx_rect_set(&_rectangle, 0, 0, width, height);
}

DispmanxFrameGrabber::~DispmanxFrameGrabber()
{
	delete[] _captureBuffer;

	// Clean up resources
	vc_dispmanx_resource_delete(_vc_resource);

	// De-init BCM
	bcm_host_deinit();
}

void DispmanxFrameGrabber::setFlags(const int vc_flags)
{
	_vc_flags = vc_flags;
}

void DispmanxFrameGrabber::setVideoMode(const VideoMode videoMode)
{
	_videoMode = videoMode;
}

void DispmanxFrameGrabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom)
{
	if (cropLeft + cropRight >= _width || cropTop + cropBottom >= _height)
	{
		Error(_log, "Rejecting invalid crop values: left: %d, right: %d, top: %d, bottom: %d", cropLeft, cropRight, cropTop, cropBottom);
		return;
	}
	_cropLeft = cropLeft;
	_cropRight = cropRight;
	_cropTop = cropTop;
	_cropBottom = cropBottom;

	if (cropLeft > 0 || cropRight > 0 || cropTop > 0 || cropBottom > 0)
	{
		Info(_log, "Cropping image: width=%d height=%d; crop: left=%d right=%d top=%d bottom=%d ", _width, _height, cropLeft, cropRight, cropTop, cropBottom);
	}
}

void DispmanxFrameGrabber::grabFrame(Image<ColorRgba> & image)
{
	int ret;

	// vc_dispmanx_resource_read_data doesn't seem to work well
	// with arbitrary positions so we have to handle cropping by ourselves
	unsigned cropLeft = _cropLeft;
	unsigned cropRight = _cropRight;
	unsigned cropTop = _cropTop;
	unsigned cropBottom = _cropBottom;

	if (_vc_flags & DISPMANX_SNAPSHOT_FILL)
	{
		// disable cropping, we are capturing the video overlay window
		cropLeft = cropRight = cropTop = cropBottom = 0;
	}

	unsigned imageWidth = _width - cropLeft - cropRight;
	unsigned imageHeight = _height - cropTop - cropBottom;

	// calculate final image dimensions and adjust top/left cropping in 3D modes
	switch (_videoMode)
	{
	case VIDEO_3DSBS:
		imageWidth = imageWidth / 2;
		cropLeft = cropLeft / 2;
		break;
	case VIDEO_3DTAB:
		imageHeight = imageHeight / 2;
		cropTop = cropTop / 2;
		break;
	case VIDEO_2D:
	default:
		break;
	}

	// resize the given image if needed
	if (image.width() != imageWidth || image.height() != imageHeight)
	{
		image.resize(imageWidth, imageHeight);
	}

	// Open the connection to the display
	_vc_display = vc_dispmanx_display_open(0);
	if (_vc_display < 0)
	{
		Error(_log, "Cannot open display: %d", _vc_display);
		return;
	}

	// Create the snapshot (incl down-scaling)
	ret = vc_dispmanx_snapshot(_vc_display, _vc_resource, (DISPMANX_TRANSFORM_T) _vc_flags);
	if (ret < 0)
	{
		Error(_log, "Snapshot failed: %d", ret);
		vc_dispmanx_display_close(_vc_display);
		return;
	}

	// Read the snapshot into the memory
	void* imagePtr = image.memptr();
	void* capturePtr = imagePtr;

	unsigned imagePitch = imageWidth * sizeof(ColorRgba);

	// dispmanx seems to require the pitch to be a multiple of 64
	unsigned capturePitch = (_rectangle.width * sizeof(ColorRgba) + 63) & (~63);

	// grab to temp buffer if image pitch isn't valid or if we are cropping
	if (imagePitch != capturePitch 
	    || (unsigned)_rectangle.width != imageWidth 
	    || (unsigned)_rectangle.height != imageHeight)
	{
		// check if we need to resize the capture buffer
		unsigned captureSize = capturePitch * _rectangle.height / sizeof(ColorRgba);
		if (_captureBufferSize != captureSize)
		{
			delete[] _captureBuffer;
			_captureBuffer = new ColorRgba[captureSize];
			_captureBufferSize = captureSize;
		}

		capturePtr = &_captureBuffer[0];
	}

	ret = vc_dispmanx_resource_read_data(_vc_resource, &_rectangle, capturePtr, capturePitch);
	if (ret < 0)
	{
		Error(_log, "vc_dispmanx_resource_read_data failed: %d", ret);
		vc_dispmanx_display_close(_vc_display);
		return;
	}

	// copy capture data to image if we captured to temp buffer
	if (imagePtr != capturePtr)
	{
		// adjust source pointer to top/left cropping
		uint8_t* src_ptr = (uint8_t*) capturePtr
			+ cropLeft * sizeof(ColorRgba)
			+ cropTop * capturePitch;

		for (unsigned y = 0; y < imageHeight; y++)
		{
			memcpy((uint8_t*)imagePtr + y * imagePitch,
				src_ptr + y * capturePitch,
				imagePitch);
		}
	}

	// Close the displaye
	vc_dispmanx_display_close(_vc_display);
}