/* GraphicsScreen.cpp
 *
 * Copyright (C) 1992-2011 Paul Boersma
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * pb 2002/05/28 GPL
 * pb 2004/10/18 ReleaseDC
 * pb 2007/08/01 reintroduced yIsZeroAtTheTop
 * sdk 2008/03/24 cairo
 * sdk 2008/05/09 cairo
 * pb 2009/07/24 quartz
 * fb 2010/02/23 cairo & clipping on updateWs()
 * pb 2010/05/13 support XOR mode
 * pb 2010/07/13 split erasure of recording off from Graphics_clearWs
 * pb 2011/03/17 C++
 */

#include "GraphicsP.h"
#include "Printer.h"

#if win
	//#include "winport_on.h"
	#include <gdiplus.h>
	//#include "winport_off.h"
	//using namespace Gdiplus;
	static bool _GraphicsWindows_tryToInitializeGdiPlus (void) {
		Gdiplus::GdiplusStartupInput gdiplusStartupInput;
		ULONG_PTR gdiplusToken;
		GdiplusStartup (& gdiplusToken, & gdiplusStartupInput, NULL);
		return true;
	}
#elif mac
	#include "macport_on.h"
	static RGBColor theBlackColour = { 0, 0, 0 };
	static bool _GraphicsMacintosh_tryToInitializeQuartz (void) {
		return _GraphicsMac_tryToInitializeAtsuiFonts ();
	}
#endif

Thing_implement (GraphicsScreen, Graphics, 0);

void structGraphicsScreen :: v_destroy () {
	#if cairo
		if (d_gdkGraphicsContext != NULL) {
			g_object_unref (d_gdkGraphicsContext);			
			d_gdkGraphicsContext = NULL;
		}
		if (d_cairoGraphicsContext != NULL) {
			cairo_destroy (d_cairoGraphicsContext);
			d_cairoGraphicsContext = NULL;
		}
	#elif win
		if (d_gdiGraphicsContext != NULL) {
			SelectPen (d_gdiGraphicsContext, GetStockPen (BLACK_PEN));
			SelectBrush (d_gdiGraphicsContext, GetStockBrush (NULL_BRUSH));
			d_gdiGraphicsContext = NULL;
		}
		if (d_winPen != NULL) {
			DeleteObject (d_winPen);
			d_winPen = NULL;
		}
		if (d_winBrush != NULL) {
			DeleteObject (d_winBrush);
			d_winBrush = NULL;
		}
		/*
		 * No ReleaseDC here, because we have not created it ourselves,
		 * not even with GetDC.
		 */
	#elif mac
		if (d_macPort == NULL) {
			CGContextEndPage (d_macGraphicsContext);
			CGContextRelease (d_macGraphicsContext);
		}
	#endif
	GraphicsScreen_Parent :: v_destroy ();
}

void structGraphicsScreen :: v_flushWs () {
	#if cairo
		// Ik weet niet of dit is wat het zou moeten zijn ;)
		// gdk_window_process_updates (my window, TRUE);
		gdk_flush ();
		// TODO: een aanroep die de eventuele grafische buffer ledigt,
		// zodat de gebruiker de grafica ziet ook al blijft Praat in hetzelfde event zitten
	#elif win
		/*GdiFlush ();*/
	#elif mac
		if (d_drawingArea) GuiWindow_drain (d_drawingArea);
	#endif
}

void Graphics_flushWs (Graphics me) {
	my v_flushWs ();
}

void structGraphicsScreen :: v_clearWs () {
	#if cairo
		GdkRectangle rect;
		if (this -> x1DC < this -> x2DC) {
			rect.x = this -> x1DC;
			rect.width = this -> x2DC - this -> x1DC;
		} else {
			rect.x = this -> x2DC;
			rect.width = this -> x1DC - this -> x2DC;
		}
		if (this -> y1DC < this -> y2DC) {
			rect.y = this -> y1DC;
			rect.height = this -> y2DC - this -> y1DC;
		} else {
			rect.y = this -> y2DC;
			rect.height = this -> y1DC - this -> y2DC;
		}
		if (d_cairoGraphicsContext == NULL) {
			//Melder_casual("Clear and null");
			//gdk_window_clear (this -> window);
			//gdk_window_invalidate_rect (this -> window, & rect, true);   // BUG: it seems weird that this is necessary.
		} else {
			//Melder_casual("Clear and not null");
			cairo_set_source_rgb (d_cairoGraphicsContext, 1.0, 1.0, 1.0);
			cairo_rectangle (d_cairoGraphicsContext, rect.x, rect.y, rect.width, rect.height);
			cairo_fill (d_cairoGraphicsContext);
			cairo_set_source_rgb (d_cairoGraphicsContext, 0.0, 0.0, 0.0);
		}
	#elif win
		RECT rect;
		rect. left = rect. top = 0;
		rect. right = x2DC - x1DC;
		rect. bottom = y2DC - y1DC;
		FillRect (d_gdiGraphicsContext, & rect, GetStockBrush (WHITE_BRUSH));
		/*if (d_winWindow) SendMessage (d_winWindow, WM_ERASEBKGND, (WPARAM) d_gdiGraphicsContext, 0);*/
	#elif mac
		if (d_useQuartz) {
			QDBeginCGContext (d_macPort, & d_macGraphicsContext);
			CGContextSetAlpha (d_macGraphicsContext, 1.0);
			CGContextSetBlendMode (d_macGraphicsContext, kCGBlendModeNormal);
			//CGContextSetAllowsAntialiasing (my macGraphicsContext, false);
			int shellHeight = GuiMac_clipOn_graphicsContext (d_drawingArea, d_macGraphicsContext);
			CGContextSetRGBFillColor (d_macGraphicsContext, 1.0, 1.0, 1.0, 1.0);
			CGContextFillRect (d_macGraphicsContext, CGRectMake (this -> x1DC, shellHeight - this -> y1DC, this -> x2DC - this -> x1DC, this -> y1DC - this -> y2DC));
			QDEndCGContext (d_macPort, & d_macGraphicsContext);
		} else { // QuickDraw
			Rect r;
			RGBColor white = { 65535, 65535, 65535 }, black = { 0, 0, 0 };
			if (d_drawingArea) GuiMac_clipOn (d_drawingArea);
			SetRect (& r, this -> x1DC, this -> y1DC, this -> x2DC, this -> y2DC);
			SetPort (d_macPort);
			RGBForeColor (& white);
			PaintRect (& r);
			RGBForeColor (& black);
			if (d_drawingArea) GuiMac_clipOff ();
		}
	#endif
}

void Graphics_clearWs (Graphics me) {
	my v_clearWs ();
}

void structGraphicsScreen :: v_updateWs () {
	/*
	 * A function that invalidates the graphics.
	 * This function is typically called by the owner of the drawing area
	 * whenever the data to be displayed in the drawing area has changed;
	 * the idea is to generate an expose event to which the drawing area will
	 * respond by redrawing its contents from the (changed) data.
	 */
	#if gtk
		//GdkWindow *window = gtk_widget_get_parent_window (GTK_WIDGET (my drawingArea));
		GdkRectangle rect;

		if (this -> x1DC < this -> x2DC) {
			rect.x = this -> x1DC;
			rect.width = this -> x2DC - this -> x1DC;
		} else {
			rect.x = this -> x2DC;
			rect.width = this -> x1DC - this -> x2DC;
		}

		if (this -> y1DC < this -> y2DC) {
			rect.y = this -> y1DC;
			rect.height = this -> y2DC - this -> y1DC;
		} else {
			rect.y = this -> y2DC;
			rect.height = this -> y1DC - this -> y2DC;
		}

		if (d_cairoGraphicsContext && d_drawingArea) {  // update clipping rectangle to new graphics size
			cairo_reset_clip (d_cairoGraphicsContext);
			cairo_rectangle (d_cairoGraphicsContext, rect.x, rect.y, rect.width, rect.height);
			cairo_clip (d_cairoGraphicsContext);
		}
		gdk_window_clear (d_window);
		gdk_window_invalidate_rect (d_window, & rect, true);
		//gdk_window_process_updates (d_window, true);
	#elif win
		//clear (this); // lll
		if (d_winWindow) InvalidateRect (d_winWindow, NULL, TRUE);
	#elif mac
		Rect r;
		if (d_drawingArea) GuiMac_clipOn (d_drawingArea);   // to prevent invalidating invisible parts of the canvas
		SetRect (& r, this -> x1DC, this -> y1DC, this -> x2DC, this -> y2DC);
		SetPort (d_macPort);
		/*EraseRect (& r);*/
		InvalWindowRect (GetWindowFromPort ((CGrafPtr) d_macPort), & r);
		if (d_drawingArea) GuiMac_clipOff ();
	#endif
}

void Graphics_updateWs (Graphics me) {
	if (me)
		my v_updateWs ();
}

static int GraphicsScreen_init (GraphicsScreen me, void *voidDisplay, void *voidWindow, int resolution) {

	/* Fill in new members. */

	#if cairo
		my d_display = (GdkDisplay *) gdk_display_get_default ();
		_GraphicsScreen_text_init (me);
		my resolution = 100;
		my d_window = GDK_DRAWABLE (GTK_WIDGET (voidDisplay) -> window);
		my d_gdkGraphicsContext = gdk_gc_new (my d_window);
		my d_cairoGraphicsContext = gdk_cairo_create (my d_window);
	#elif win
		if (my printer) {
			my d_gdiGraphicsContext = (HDC) voidWindow;
		} else if (voidDisplay) {
			my d_gdiGraphicsContext = (HDC) voidDisplay;
			my metafile = TRUE;
		} else {
			my d_winWindow = (HWND) voidWindow;
			my d_gdiGraphicsContext = GetDC (my d_winWindow);   // window must have a constant display context; see XtInitialize ()
		}
		Melder_assert (my d_gdiGraphicsContext != NULL);
		my resolution = resolution;
		SetBkMode (my d_gdiGraphicsContext, TRANSPARENT);   // not the default!
		/*
		 * Create pens and brushes.
		 */
		my d_winPen = CreatePen (PS_SOLID, 0, RGB (0, 0, 0));
		my d_winBrush = CreateSolidBrush (RGB (0, 0, 0));
		SelectBrush (my d_gdiGraphicsContext, GetStockBrush (NULL_BRUSH));
		SetTextAlign (my d_gdiGraphicsContext, TA_LEFT | TA_BASELINE | TA_NOUPDATECP);   // baseline is not the default!
		_GraphicsScreen_text_init (me);
	#elif mac
		(void) voidDisplay;
		my d_macPort = (GrafPtr) voidWindow;
		my d_macColour = theBlackColour;
		my resolution = resolution;
		my d_depth = my resolution > 150 ? 1 : 8;   /* BUG: replace by true depth (1=black/white) */
		if (my d_useQuartz) {
			(void) my d_macGraphicsContext;   // will be retreived from QuickDraw with every drawing command!
		}
		_GraphicsScreen_text_init (me);
	#endif
	return 1;
}

Graphics Graphics_create_screen (void *display, void *window, int resolution) {
	GraphicsScreen me = Thing_new (GraphicsScreen);
	my screen = true;
	my yIsZeroAtTheTop = true;
	Graphics_init (me);
	Graphics_setWsViewport ((Graphics) me, 0, 100, 0, 100);
	#if mac
		GraphicsScreen_init (me, display, GetWindowPort ((WindowRef) window), resolution);
	#else
		GraphicsScreen_init (me, display, window, resolution);
	#endif
	return (Graphics) me;
}

#ifdef macintosh
Graphics Graphics_create_port (void *display, void *port, int resolution) {
	GraphicsScreen me = Thing_new (GraphicsScreen);
	my screen = true;
	my yIsZeroAtTheTop = true;
	Graphics_init (me);
	Graphics_setWsViewport ((Graphics) me, 0, 100, 0, 100);
	GraphicsScreen_init (me, display, port, resolution);
	return (Graphics) me;
}
#endif

Graphics Graphics_create_screenPrinter (void *display, void *window) {
	GraphicsScreen me = Thing_new (GraphicsScreen);
	my screen = true;
	my yIsZeroAtTheTop = true;
	my printer = true;
	#ifdef macintosh
		my d_useQuartz = _GraphicsMacintosh_tryToInitializeQuartz ();
	#endif
	Graphics_init (me);
	my paperWidth = (double) thePrinter. paperWidth / thePrinter. resolution;
	my paperHeight = (double) thePrinter. paperHeight / thePrinter. resolution;
	my x1DC = my x1DCmin = thePrinter. resolution / 2;
	my x2DC = my x2DCmax = (my paperWidth - 0.5) * thePrinter. resolution;
	my y1DC = my y1DCmin = thePrinter. resolution / 2;
	my y2DC = my y2DCmax = (my paperHeight - 0.5) * thePrinter. resolution;
	#if win
		/*
		 * Map page coordinates to paper coordinates.
		 */
		my x1DC -= GetDeviceCaps ((HDC) window, PHYSICALOFFSETX);
		my x2DC -= GetDeviceCaps ((HDC) window, PHYSICALOFFSETX);
		my y1DC -= GetDeviceCaps ((HDC) window, PHYSICALOFFSETY);
		my y2DC -= GetDeviceCaps ((HDC) window, PHYSICALOFFSETY);
	#endif
	Graphics_setWsWindow ((Graphics) me, 0, my paperWidth - 1.0, 13.0 - my paperHeight, 12.0);
	GraphicsScreen_init (me, display, window, thePrinter. resolution);
	return (Graphics) me;
}

#if mac
/* Drawing areas support resize callbacks.
 * However, Mac drawing areas also support move callbacks.
 * This is the only way for a graphics driver to get notified of a move,
 * which would bring about a change in device coordinates.
 * On Xwin and Win, we are not interested in moves, because we draw in widget coordinates.
 */
static void cb_move (GUI_ARGS) {
	iam (GraphicsScreen);
	Dimension width, height;
	XtVaGetValues (w, XmNwidth, & width, XmNheight, & height, NULL);

	/* The four values returned are probably equal to the previous ones.
	 * However, the following call forces a new computation of the device coordinates
	 * by widgetToWindowCoordinates ().
	 */

	Graphics_setWsViewport ((Graphics) me, 0 /* Left x value in widget coordinates */,
		width, 0, height);
	Graphics_updateWs ((Graphics) me);
}
#endif

Graphics Graphics_create_xmdrawingarea (void *w) {   /* w = XmDrawingArea widget */
	GraphicsScreen me = Thing_new (GraphicsScreen);
	#if gtk
		GtkRequisition realsize;
	#elif motif
		Dimension width, height;
	#endif

	my d_drawingArea = static_cast <GuiObject> (w);   /* Now !!!!!!!!!! */
	my screen = true;
	my yIsZeroAtTheTop = true;
	#if win
		my d_useGdiplus = _GraphicsWindows_tryToInitializeGdiPlus ();
	#elif mac
		my d_useQuartz = _GraphicsMacintosh_tryToInitializeQuartz ();
	#endif
	Graphics_init (me); therror
	#ifdef macintosh
		GraphicsScreen_init (me, XtDisplay (my d_drawingArea), GetWindowPort ((WindowRef) XtWindow (my d_drawingArea)), Gui_getResolution (my d_drawingArea));
	#else
		#if gtk
			GraphicsScreen_init (me, GTK_WIDGET (my d_drawingArea), GTK_WIDGET (my d_drawingArea), Gui_getResolution (my d_drawingArea));
		#elif motif
			GraphicsScreen_init (me, XtDisplay (my d_drawingArea), XtWindow (my d_drawingArea), Gui_getResolution (my d_drawingArea));
		#endif
	#endif

	#if gtk
		// fb: is really the request meant or rather the actual size, aka allocation?
		gtk_widget_size_request (GTK_WIDGET (my d_drawingArea), & realsize);
		// HIER WAS IK
		//	g_debug("--> %d %d", realsize.width, realsize.height);
		Graphics_setWsViewport ((Graphics) me, 0, realsize.width, 0, realsize.height);
	#elif motif
		XtVaGetValues (my d_drawingArea, XmNwidth, & width, XmNheight, & height, NULL);
		Graphics_setWsViewport ((Graphics) me, 0, width, 0, height);
	#endif
	#ifdef macintosh
		XtAddCallback (my d_drawingArea, XmNmoveCallback, cb_move, (XtPointer) me);
	#endif
	return (Graphics) me;
}

Graphics Graphics_create_pdffile (MelderFile file, int resolution,
	double x1inches, double x2inches, double y1inches, double y2inches)
{
	GraphicsScreen me = Thing_new (GraphicsScreen);
	my screen = true;
	my yIsZeroAtTheTop = true;
	#ifdef macintosh
		my d_useQuartz = _GraphicsMacintosh_tryToInitializeQuartz ();
	#endif
	Graphics_init (me); therror
	my resolution = resolution;
	#ifdef macintosh
		CFURLRef url = CFURLCreateWithFileSystemPath (NULL, (CFStringRef) Melder_peekWcsToCfstring (file -> path), kCFURLPOSIXPathStyle, false);
		CGRect rect = CGRectMake (0, 0, (x2inches - x1inches) * 72, (y2inches - y1inches) * 72);   // don't tire PDF viewers with funny origins
		CFStringRef key = (CFStringRef) Melder_peekWcsToCfstring (L"Creator");
		CFStringRef value = (CFStringRef) Melder_peekWcsToCfstring (L"Praat");
		CFIndex numberOfValues = 1;
		CFDictionaryRef dictionary = CFDictionaryCreate (NULL, (const void **) & key, (const void **) & value, numberOfValues,
			& kCFTypeDictionaryKeyCallBacks, & kCFTypeDictionaryValueCallBacks);
		my d_macGraphicsContext = CGPDFContextCreateWithURL (url, & rect, dictionary);
		CFRelease (url);
		CFRelease (dictionary);
		my x1DC = my x1DCmin = 0;
		my x2DC = my x2DCmax = 7.5 * resolution;
		my y1DC = my y1DCmin = 0;
		my y2DC = my y2DCmax = 11.0 * resolution;
		Graphics_setWsWindow ((Graphics) me, 0, 7.5, 1.0, 12.0);
		CGContextBeginPage (my d_macGraphicsContext, & rect);
		CGContextScaleCTM (my d_macGraphicsContext, 72.0/resolution, 72.0/resolution);
		CGContextTranslateCTM (my d_macGraphicsContext, - x1inches * resolution, (12.0 - y1inches) * resolution);
		CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
	#endif
	return (Graphics) me;
}
Graphics Graphics_create_pdf (void *context, int resolution,
	double x1inches, double x2inches, double y1inches, double y2inches)
{
	GraphicsScreen me = Thing_new (GraphicsScreen);
	my screen = true;
	my yIsZeroAtTheTop = true;
	#ifdef macintosh
		my d_useQuartz = _GraphicsMacintosh_tryToInitializeQuartz ();
	#endif
	Graphics_init (me);
	my resolution = resolution;
	#ifdef macintosh
		my d_macGraphicsContext = static_cast <CGContext *> (context);
		CGRect rect = CGRectMake (0, 0, (x2inches - x1inches) * 72, (y2inches - y1inches) * 72);   // don't tire PDF viewers with funny origins
		my x1DC = my x1DCmin = 0;
		my x2DC = my x2DCmax = 7.5 * resolution;
		my y1DC = my y1DCmin = 0;
		my y2DC = my y2DCmax = 11.0 * resolution;
		Graphics_setWsWindow ((Graphics) me, 0, 7.5, 1.0, 12.0);
		CGContextBeginPage (my d_macGraphicsContext, & rect);
		CGContextScaleCTM (my d_macGraphicsContext, 72.0/resolution, 72.0/resolution);
		CGContextTranslateCTM (my d_macGraphicsContext, - x1inches * resolution, (12.0 - y1inches) * resolution);
		CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
	#endif
	return (Graphics) me;
}

#if cairo
	void *Graphics_x_getCR (Graphics me) {
		return ((GraphicsScreen) me) -> d_cairoGraphicsContext;
	}
	void Graphics_x_setCR (Graphics me, void *cairoGraphicsContext) {
		((GraphicsScreen) me) -> d_cairoGraphicsContext = (cairo_t *) cairoGraphicsContext;
	}
#endif

#if mac
	void GraphicsQuartz_initDraw (GraphicsScreen me) {
		if (my d_macPort) {
			QDBeginCGContext (my d_macPort, & my d_macGraphicsContext);
			//CGContextSetAlpha (my macGraphicsContext, 1.0);
			//CGContextSetAllowsAntialiasing (my macGraphicsContext, false);
			if (my d_drawingArea != NULL) {
				int shellHeight = GuiMac_clipOn_graphicsContext (my d_drawingArea, my d_macGraphicsContext);
				CGContextTranslateCTM (my d_macGraphicsContext, 0, shellHeight);
			} else if (my printer) {
				CGContextTranslateCTM (my d_macGraphicsContext, 0, my y2DC - my y1DC);
			}
			CGContextScaleCTM (my d_macGraphicsContext, 1.0, -1.0);
		}
	}
	void GraphicsQuartz_exitDraw (GraphicsScreen me) {
		if (my d_macPort) {
			CGContextSynchronize (my d_macGraphicsContext);
			QDEndCGContext (my d_macPort, & my d_macGraphicsContext);
		}
	}
#endif

/* End of file GraphicsScreen.cpp */
