/*
 * This file is part of libtqtvd_astronav
 *
 * Copyright (C) 2008-2017 TQTVD Software
 * Copyright (C) 2017 Mirakulo Software (mirakulo@mirakulo.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

/**
 * @file  BCGraphicsDeviceGraphicManager.cpp
 *
 * AstroNav TQTVD Implementation of BCGraphicsDeviceGraphicManager class.
 *
 */

#include "BCGraphicsDeviceGraphicManager.h"
#include "BCNativeImageGraphicManager.h"
#include "BIWindow.h"
#include <graphics/IntPoint.h>
#include <graphics/IntRect.h>
#include <graphics/IntSize.h>
#include "BTScrollView.h"
#include "BIMath.h"	/* M_PI */
#include <graphics/Color.h>

#include "IGraphicManagerAdapter.h"

extern IGraphicManagerAdapter * g_graphicManager;


IMPLEMENT_GET_DELETE(BIGraphicsDevice, BCGraphicsDeviceGraphicManager);

#define GET_NATIVE(image) ((static_cast<const BCNativeImage*>(image))->getNativeGraphicManager())


namespace BAL {

BCGraphicsDeviceGraphicManager::BCGraphicsDeviceGraphicManager()
{
#ifndef NDEBUG
	m_layoutTests = false;
#endif
	m_gm = NULL;
	m_screen = NULL;
	m_ownAstro = m_ownScreen = m_screenLessMode = false;
	m_width = 0;
	m_height = 0;
	m_depth = 0;
	// For conformity with NCL requirements, the browser's default background
	// color should be transparent. This allows "floating" XHTML content in an
	// NCL document. If a specific opaque background is required, it can be
	// defined inside the XHTML/NCL document's code.
	m_backgroundColor = 0x00000000;

	pthread_mutex_init(&m_listenersMutex, NULL);
}

BCGraphicsDeviceGraphicManager::~BCGraphicsDeviceGraphicManager()
{
	finalize();
}


void BCGraphicsDeviceGraphicManager::initialize(uint16_t width, uint16_t height, uint8_t depth)
{
	// setSuperInterface() will create if necessary  (this allows previous setting)
	if (!m_gm)
		setSuperInterface ( m_gm );

	if (m_gm)
	{
		m_width = width;
		m_height = height;
		m_depth = depth;
	}

	// setPrimarySurface() will create screen if necessary   (this allows previous setting)
	if (!m_screenLessMode && !m_screen)
		setPrimarySurface ( m_screen );
}

void BCGraphicsDeviceGraphicManager::finalize()
{
	if (NULL != m_gm)
	{
		if (!m_screenLessMode && m_ownScreen)
		{
			m_screen = m_gm->DestroySurface(m_screen);
			m_ownScreen = false;
		}
		m_screen = NULL;

		if ( m_ownAstro )
		{
			m_gm = NULL;
			m_ownAstro = false;
		}
	}
}

void BCGraphicsDeviceGraphicManager::setScreenLessMode(bool mode)
{
	m_screenLessMode = mode;
}


void *BCGraphicsDeviceGraphicManager::setSuperInterface (void* si)
{
	if (NULL != si)
	{
		m_gm = (IGraphicManagerAdapter *) si;
		m_ownAstro = false;
	}
	else
	{
		m_gm = g_graphicManager ;//&(IGraphicManagerAdapter::getInstance());
		if (NULL == m_gm)
		{
			printf( "Failed getting instance of GraphicManagerAdapter.");
		}
		else
		{
			m_ownAstro = true;
		}
	}
	return m_gm;
}


void *BCGraphicsDeviceGraphicManager::setPrimarySurface (void* ps)
{
	if (!m_screenLessMode)
	{
		if (NULL != ps)
		{
			m_screen = (IGraphicManagerAdapterSurface *) ps;
			m_ownScreen = false;
		}
		else
		{
			if (m_gm)
			{
				IGraphicManagerAdapterSurface* surface = m_gm->CreateSurface (m_width, m_height, m_depth);
				if (NULL == surface)
				{
					printf( "Failed creating screen surface.");
				}
				else
				{
					surface->Clear(m_backgroundColor);
					surface->SetBlendMode(IGraphicManagerAdapterSurface::BLENDMODE_COMPOSE);
					m_screen = surface;
					m_ownScreen = true;
				}
			}
		}
	}
	return m_screen;
}


BINativeImage* BCGraphicsDeviceGraphicManager::createNativeImage(const WebCore::IntSize size)
{
	BINativeImage* rc = NULL;
	const int depth = 32; // FIXME: 32 hard-coded, but not used.

	IGraphicManagerAdapterSurface* surface;

	if (m_gm && size.width() && size.height())
	{
		surface = m_gm->CreateSurface( size.width(), size.height(), depth );
		if (NULL == surface)
		{
			printf("Failed creating native image surface.\n");
		}
		else
		{
			surface->Clear(0x00000000);
			surface->SetBlendMode(IGraphicManagerAdapterSurface::BLENDMODE_COMPOSE);

			rc = new BCNativeImage(surface);
		}
	}

	return rc;

}

BINativeImage* BCGraphicsDeviceGraphicManager::createNativeImage(RGBA32Array& array, const WebCore::IntSize size)
{
	BINativeImage* rc = NULL;
	const int depth = 32; // FIXME: 32 hard-coded, but not used.

	IGraphicManagerAdapterSurface* surface;

	if (m_gm && size.width() && size.height())
	{
		surface = m_gm->CreateSurface( size.width(), size.height(), depth );
		if (NULL == surface)
		{
			printf("Failed creating native image surface.\n");
		}
		else
		{
			surface->Clear(0x00000000);
			surface->SetBlendMode(IGraphicManagerAdapterSurface::BLENDMODE_COMPOSE);

			surface->SetPixels(0, 0, (const _u8*)array.data(), size.width(), size.height());
/*  Código substituido pela chamada acima a SetPixels.
			int x=0, y=0 ;
			for (unsigned pix=0; pix<array.size(); pix++)
			{
				int alpha = ( array[pix] & 0xff000000 ) >> 24;
				int red   = ( array[pix] & 0x00ff0000 ) >> 16;
				int green = ( array[pix] & 0x0000ff00 ) >> 8;
				int blue  = ( array[pix] & 0x000000ff ) ;

				if (alpha != 0)
				{
					surface->SetColor (red, green, blue, alpha);
					surface->FillRectangle (x, y, 1, 1);
				}
				if (++x == size.width())
				{
					y++;
					x=0;
				}
				sched_yield();
			}
*/
			rc = new BCNativeImage(surface);
		}
	}

	return rc;
}

void BCGraphicsDeviceGraphicManager::copy(const BTWidget& widget, const BINativeImage& image, WebCore::IntRect zone, WebCore::IntPoint point, const uint8_t alphaChannel)
{
	if (!widget.backingStore())
		return;

	ARectangle source_rect(zone.x(), zone.y(), zone.width(), zone.height());
	IGraphicManagerAdapterSurface* surface = GET_NATIVE(widget.backingStore());

	if (alphaChannel != 255)
	{
		IGraphicManagerAdapterSurface *surfaceWithAlpha = applyTransparency(image, alphaChannel);
		surface->Blit( surfaceWithAlpha, &source_rect, point.x(),  point.y(), alphaChannel );
		m_gm->DestroySurface(surfaceWithAlpha);
	}
	else
	{
		surface->Blit( GET_NATIVE(&image), &source_rect , point.x(),  point.y(), alphaChannel );
	}
}

void BCGraphicsDeviceGraphicManager::drawArc(const BTWidget& widget, const WebCore::IntRect rect, uint16_t startAngle, uint16_t angleSpan, const WebCore::Color color)
{
	if (color.alpha() == 0 || !widget.backingStore() || rect.isEmpty())
		return;

	//
	//		  |y		  (This diagram is supposed to be a circle).
	//		  |
	//		 ...
	// z=1 .  |  . z=0		We know that there are 360 degrees in a circle. First we
	//	 .	  |    .		see that a circle is symetrical about the x axis, so
	//	.	  | 	.		only the first 180 degrees need to be calculated. Next
	//--.-----+-----.-- 	we see that it's also symetrical about the y axis, so now
	//	.	  | 	. x 	we only need to calculate the first 90 degrees.
	//	 .	  |    .		Each 90° region is associated to a zone (z value). Zone where
	// z=2 .  |  . z=3		we will draw is defined from startAngle and angleSpan.
	//		 ...			Thus after coord computation, we draw in the zone(s).
	//		  | 			Note: 0 <= alpha0, alpha1 <= 90
	//		  |

	int r = (rect.width() - 1) / 2;
	int xc = rect.x() + r;
	int yc = rect.y() + (rect.height() - 1)/ 2;
	int z0 = startAngle / 90;
	int z1 = (startAngle + angleSpan) / 90;
	int alpha0 = startAngle % 90;
	float xalpha0 = r * cosf((alpha0 * M_PI / 180));
	int alpha1 = (startAngle + angleSpan) % 90;
	float xalpha1 = r * cosf((alpha1 * M_PI / 180));

	float x0, y0, x1, y1;

	if (z0 == z1) {
		// Draw in the same zone from angle = z0 * 90 + alpha0 to angle = z0 * 90 + alpha1
		x0 = xalpha0;
		y0 = sqrt(pow(r, 2) - pow(x0, 2));
		for (x1 = xalpha0; x1 >= xalpha1; x1--) {
			y1 = sqrt(pow(r, 2) - pow(x1, 2));
			drawArc(widget, color, z0, xc, yc, x0, y0, x1, y1);
		}
	} else if ((z1 - z0) == 1) {
		// Begin to draw in zone Z and end in zone Z + 1
		if (alpha1 < alpha0) {
			// Draw from angle = z1 * 90 to angle = z1 * 90 + alpha1
			// Then from angle = z0 * 90 + alpha0 to angle = z1 * 90
			x0 = r;
			y0 = 0;
			for (x1 = r; x1 >= xalpha1; x1--) {
				y1 = sqrt(pow(r, 2) - pow(x1, 2));
				drawArc(widget, color, z1, xc, yc, x0, y0, x1, y1);
			}
			x0 = xalpha0;
			y0 = sqrt(pow(r, 2) - pow(x0, 2));
			for (x1 = xalpha0; x1 >= 0; x1--) {
				y1 = sqrt(pow(r, 2) - pow(x1, 2));
				drawArc(widget, color, z0, xc, yc, x0, y0, x1, y1);
			}
		} else {
			// Compute a complete quarter of circle.
			// Draw in zone1 from 0 to alpha0
			// Draw in zone0 and zone1 from alpha0 to alpha1
			// Draw in zone0 from alpha1 to 90
			x0 = r;
			y0 = 0;
			for (x1 = r; x1 >= 0; x1--) {
				y1 = sqrt(pow(r, 2) - pow(x1, 2));
				if (x1 < xalpha1)
					drawArc(widget, color, z0, xc, yc, x0, y0, x1, y1);
				else if (x1 > xalpha0)
					drawArc(widget, color, z1, xc, yc, x0, y0, x1, y1);
				else {
					drawArc(widget, color, z0, xc, yc, x0, y0, x1, y1, false);
					drawArc(widget, color, z1, xc, yc, x0, y0, x1, y1);
				}
			}
		}
	} else {
		// Draw at least a complete quarter of circle and probably many more
		x0 = r;
		y0 = 0;
		for (x1 = r; x1 >= 0; x1--) {
			y1 = sqrt(pow(r, 2) - pow(x1, 2));
			if ((z1 - z0) >= 3)
				drawArc(widget, color, z1 - 2, xc, yc, x0, y0, x1, y1, false);
			if (x1 < xalpha1)
				drawArc(widget, color, z1 % 3, xc, yc, x0, y0, x1, y1, false);
			if (x1 < xalpha0)
				drawArc(widget, color, z0, xc, yc, x0, y0, x1, y1, false);
			drawArc(widget, color, z1 - 1, xc, yc, x0, y0, x1, y1);
		}
	}
}



void BCGraphicsDeviceGraphicManager::drawEllipse(const BTWidget& widget, WebCore::IntRect rect, const WebCore::Color color)
{
	if (color.alpha() == 0 || !widget.backingStore() || rect.isEmpty())
		return;

	IGraphicManagerAdapterSurface * surface = GET_NATIVE(widget.backingStore());
	ARectangle irect(rect.x(), rect.y(),  rect.width(),  rect.height());

	surface->SetColor (color.red(), color.green(), color.blue(), color.alpha());
	surface->DrawEllipse (&irect);
}


void BCGraphicsDeviceGraphicManager::drawLine(const BTWidget& widget, WebCore::IntPoint p1, WebCore::IntPoint p2, const WebCore::Color color)
{
	if (color.alpha() == 0 || !widget.backingStore())
		return;

	IGraphicManagerAdapterSurface * surface = GET_NATIVE(widget.backingStore());

	surface->SetColor ( color.red(), color.green(), color.blue(), color.alpha());
	surface->DrawLine( p1.x(), p1.y(),  p2.x(), p2.y() );
}

void BCGraphicsDeviceGraphicManager::setLineStyle(const BTWidget& widget, const int style)
{
	if (!widget.backingStore())
		return;

	IGraphicManagerAdapterSurface * surface = GET_NATIVE(widget.backingStore());

	surface->SetLineStyle(style);
}

void BCGraphicsDeviceGraphicManager::drawRect(const BTWidget& widget, WebCore::IntRect rect, const WebCore::Color color)
{
	if (color.alpha() == 0 || !widget.backingStore() || rect.isEmpty())
		return;

	IGraphicManagerAdapterSurface * surface = GET_NATIVE(widget.backingStore());

	surface->SetColor( color.red(), color.green(), color.blue(), color.alpha());
	surface->DrawRectangle(rect.x(), rect.y(), rect.width(), rect.height());
}

void BCGraphicsDeviceGraphicManager::fillConvexPolygon(const BTWidget& widget, size_t npoints, const WebCore::IntPoint* points, const WebCore::Color color)
{
	if (color.alpha() == 0 || !widget.backingStore() ||(npoints <= 1))
		return;

	IGraphicManagerAdapterSurface * surface = GET_NATIVE(widget.backingStore());
	struct Point2d * p;

	p = (struct Point2d *)malloc(sizeof(struct Point2d) * npoints);

	for(unsigned int i=0; i<npoints; i++) {
		p[i].x = points[i].x();
		p[i].y = points[i].y();
	}

	surface->SetColor(color.red(), color.green(), color.blue(), color.alpha());
	surface->FillConvexPolygon(p, npoints);

	free(p);
}

void BCGraphicsDeviceGraphicManager::fillRect(const BTWidget& widget, WebCore::IntRect rect, const WebCore::Color color)
{
	if (color.alpha() == 0 || !widget.backingStore() || rect.isEmpty())
		return;

	IGraphicManagerAdapterSurface* surface = GET_NATIVE(widget.backingStore());

	surface->SetColor(color.red(), color.green(), color.blue(), color.alpha());
	surface->FillRectangle(rect.x(), rect.y(),  rect.width(),  rect.height());
}

void BCGraphicsDeviceGraphicManager::setClip(const BTWidget& widget, WebCore::IntRect rect)
{
	if (!widget.backingStore())
		return;

	IGraphicManagerAdapterSurface * surface = GET_NATIVE(widget.backingStore());

	if (rect.isEmpty())
	{
		surface->SetClip( NULL );
	}
	else
	{
		ARectangle region ( rect.x(), rect.y(), rect.x()+rect.width(), rect.y()+rect.height() );
		surface->SetClip( &region );
	}
}

WebCore::IntRect BCGraphicsDeviceGraphicManager::clip(const BTWidget& widget)
{
	if (!widget.backingStore())
		return WebCore::IntRect(0,0,0,0);

	ARectangle ret_clip;
	IGraphicManagerAdapterSurface * surface = GET_NATIVE(widget.backingStore());

	surface->GetClip( &ret_clip );
	WebCore::IntRect rect(ret_clip.x, ret_clip.y, ret_clip.w, ret_clip.h);
	return rect;
}

void BCGraphicsDeviceGraphicManager::stretchBlit(const BTWidget& widget, const BINativeImage& nativeImage, WebCore::IntRect src, WebCore::IntRect dst, const uint8_t alphaChannel)
{

	ARectangle srcRect (0, 0, src.width(), src.height());
	ARectangle dstRect (dst.x(), dst.y(), dst.width(), dst.height());
#if 0
    printf("\nsrc: %d %d %d %d  \n", src.x(), src.y(), src.width(), src.height());
    printf("dst: %d %d %d %d  \n", dst.x(), dst.y(), dst.width(), dst.height());

    printf("Native Image dimensions: %d %d  \n", nativeImage.size().width(), nativeImage.size().height());

    printf("srcRect: %d %d %d %d  \n", srcRect.x, srcRect.y, srcRect.w, srcRect.h);
    printf("dstRect: %d %d %d %d  \n", dstRect.x, dstRect.y, dstRect.w, dstRect.h);
#endif
    IGraphicManagerAdapterSurface* targetsurface = GET_NATIVE(widget.backingStore());
    IGraphicManagerAdapterSurface* srcSurface = GET_NATIVE(&nativeImage);

    targetsurface->StretchBlit( srcSurface, &srcRect, &dstRect);


}

WebCore::IntSize BCGraphicsDeviceGraphicManager::size() const
{
#ifndef NDEBUG
	if (m_layoutTests)
		return WebCore::IntSize(800, 600); // fixed for layout tests
#endif
	int ret_width, ret_height;

	if(m_screenLessMode)
	{
		ret_width = m_width;
		ret_height = m_height;
	}
	else
	{
		m_screen->GetSize( &ret_width, &ret_height );
	}

	return IntSize(ret_width, ret_height);
}


void BCGraphicsDeviceGraphicManager::update(const BTWidget& widget, const WebCore::IntRect rect)
{
#ifndef NDEBUG
	if (m_layoutTests)
		return;
#endif

	if (!widget.backingStore() || rect.isEmpty())
		return;

	int updateX = widget.x() + rect.x();
	int updateY = widget.y() + rect.y();
	int updateWLimit = updateX + rect.width();
	int updateHLimit = updateY + rect.height();

	if ((updateX > m_width) || (updateY > m_height))
	{
		//printf("BCGraphicsDeviceGraphicManager::update Discarding update outside screen\n");
		return;
	}

	if (updateWLimit > m_width)
	{
		updateWLimit = m_width;
	}

	if (updateHLimit > m_height)
	{
		updateHLimit = m_height;
	}

#if 0
	printf("\n=========\n");
	printf("BCGraphicsDeviceGraphicManager::update widget is x,y,w,h is (%d,%d,%d,%d)\n", widget.x(), widget.y(), widget.width(), widget.height());
	printf("BCGraphicsDeviceGraphicManager::update widget srcRect is (%d,%d,%d,%d)\n", updateX, updateY, updateWLimit - updateX, updateHLimit - updateY);
	printf("BCGraphicsDeviceGraphicManager::update screen w,h is (%d,%d)\n", m_width, m_height);
	printf("=========\n\n");
#endif

	if (!m_screenLessMode)
	{
 		ARectangle source_rect (rect.x(), rect.y(), rect.width(), rect.height());
		ARectangle region ( updateX,  updateY, updateWLimit, updateHLimit );

		m_screen->Blit(((BCNativeImage *)(widget.backingStore()))->getNativeGraphicManager(), &source_rect, updateX, updateY, 0xff /* alpha */ );

		if (m_ownScreen)
		{
			printf("\n FIX ME \n");
			abort();
			//m_screen->Flip(&region);
		}
	}

	pthread_mutex_lock(&m_listenersMutex);

	BIListener *listener = m_listenerMap[&widget];
	if (listener)
		listener->update(((BCNativeImage *)(widget.backingStore()))->getNativeGraphicManager(),
			WebCore::IntRect( rect.x(),  rect.y(), rect.width(), rect.height()),
			WebCore::IntRect( updateX,  updateY, updateWLimit - updateX, updateHLimit - updateY));

	pthread_mutex_unlock(&m_listenersMutex);
}


void BCGraphicsDeviceGraphicManager::clear(const BTWidget& widget, const WebCore::IntRect rect)
{
	if (!widget.backingStore() || rect.isEmpty())
		return;

	IGraphicManagerAdapterSurface* surface = GET_NATIVE(widget.backingStore());
	surface->Clear (m_backgroundColor);
}

inline void BCGraphicsDeviceGraphicManager::drawArc(const BTWidget& widget, const WebCore::Color color, int zone, int xc, int yc, float& x0, float& y0, float x1, float y1, bool doSwap)
{
}

IGraphicManagerAdapterSurface* BCGraphicsDeviceGraphicManager::applyTransparency(const BINativeImage& image, const uint8_t alphaChannel)
{
	const int depth = 32; // FIXME: 32 hard-coded, but not used.

	IGraphicManagerAdapterSurface* surface = NULL;
	int width=0, height=0;

	GET_NATIVE(&image)->GetSize( &width, &height );

	surface = m_gm->CreateSurface( width, height, depth );

	if (surface) {
		surface->Clear(m_backgroundColor);
		surface->Blit( GET_NATIVE(&image), NULL, 0, 0, alphaChannel );
	}

	return surface;
}

void BCGraphicsDeviceGraphicManager::setBackgroundColor(WebCore::RGBA32 color)
{
	m_backgroundColor = color;
}

WebCore::RGBA32 BCGraphicsDeviceGraphicManager::getBackgroundColor(void)
{
	return m_backgroundColor;
}

void BCGraphicsDeviceGraphicManager::addUpdateListener(BIListener* listener)
{
	pthread_mutex_lock(&m_listenersMutex);
	const BTWidget *widget = ((BIWindow *)listener->getWindow())->widget();
	m_listenerMap[widget] = listener;
	pthread_mutex_unlock(&m_listenersMutex);
}


void BCGraphicsDeviceGraphicManager::removeUpdateListener(BIListener* listener)
{
	pthread_mutex_lock(&m_listenersMutex);
	const BTWidget *widget = ((BIWindow *)listener->getWindow())->widget();
	m_listenerMap.erase(widget);
	pthread_mutex_unlock(&m_listenersMutex);
}


} // namespace BAL
