/** \file print.c
 * Printing functions using GTK's print API
 */

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2015 Martin Fischer
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <stdlib.h>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <math.h>
#include <string.h>

#define GTK_DISABLE_SINGLE_INCLUDES
#define GDK_DISABLE_DEPRECATED
#define GTK_DISABLE_DEPRECATED
#define GSEAL_ENABLE

#define PRODUCT "XTRKCAD"

#include <gtk/gtk.h>
#include <gdk/gdk.h>

#include "gtkint.h"
#include <gtk/gtkunixprint.h>

#include "wlib.h"
#include "i18n.h"

extern wDrawColor wDrawColorWhite;
extern wDrawColor wDrawColorBlack;

/*****************************************************************************
 *
 * MACROS
 *
 */

#define PRINT_PORTRAIT  (0)
#define PRINT_LANDSCAPE (1)

#define PPI (72.0)
#define P2I( P ) ((P)/PPI)

#define CENTERMARK_LENGTH (60)				/**< size of cross marking center of circles */
#define DASH_LENGTH (8.0)					/**< length of single dash */

#define PAGESETTINGS "xtrkcad.page"			/**< filename for page settings */
#define PRINTSETTINGS "xtrkcad.printer"		/**< filename for printer settings */

/*****************************************************************************
 *
 * VARIABLES
 *
 */

static GtkPrintSettings *settings = NULL;			/**< current printer settings */
static GtkPageSetup *page_setup;			/**< current paper settings */
static GtkPrinter *selPrinter = NULL;				/**< printer selected by user */
static GtkPrintJob *curPrintJob;			/**< currently active print job */
extern struct wDraw_t psPrint_d;

static wBool_t printContinue;	/**< control print job, FALSE for cancelling */

static wIndex_t pageCount;		/**< unused, could be used for progress indicator */
//static wIndex_t totalPageCount; /**< unused, could be used for progress indicator */

static double paperWidth;		/**< physical paper width */
static double paperHeight;		/**< physical paper height */
static double tBorder;			/**< top margin */
static double rBorder;			/**< right margin */
static double lBorder;			/**< left margin */
static double bBorder;			/**< bottom margin */

static double scale_adjust = 1.0;
static double scale_text = 1.0;

//static long printFormat = PRINT_LANDSCAPE;

/*****************************************************************************
 *
 * FUNCTIONS
 *
 */

static void WlibGetPaperSize(void);

/**
 * Initialize printer und paper selection using the saved settings
 *
 * \param op IN print operation to initialize. If NULL only the global
 * 				settings are loaded.
 */

void
WlibApplySettings(GtkPrintOperation *op)
{
	gchar *filename;
	GError *err = NULL;
	GtkWidget *dialog;

	filename = g_build_filename(wGetAppWorkDir(), PRINTSETTINGS, NULL);

	if (!(settings = gtk_print_settings_new_from_file(filename, &err))) {
		if (err->code != G_FILE_ERROR_NOENT) {
			// ignore file not found error as defaults will be used
			dialog = gtk_message_dialog_new(GTK_WINDOW(gtkMainW->gtkwin),
			                                GTK_DIALOG_DESTROY_WITH_PARENT,
			                                GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
			                                "%s",err->message);
			gtk_dialog_run(GTK_DIALOG(dialog));
			gtk_widget_destroy(dialog);
		} else {
			// create  default print settings
			settings = gtk_print_settings_new();
		}
		g_error_free(err);
	}

	g_free(filename);

	if (settings && op) {
		gtk_print_operation_set_print_settings(op, settings);
	}

	err = NULL;
	filename = g_build_filename(wGetAppWorkDir(), PAGESETTINGS, NULL);

	if (!(page_setup = gtk_page_setup_new_from_file(filename, &err))) {
		// ignore file not found error as defaults will be used
		if (err->code != G_FILE_ERROR_NOENT) {
			dialog = gtk_message_dialog_new(GTK_WINDOW(gtkMainW->gtkwin),
			                                GTK_DIALOG_DESTROY_WITH_PARENT,
			                                GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
			                                "%s",err->message);
			gtk_dialog_run(GTK_DIALOG(dialog));
			gtk_widget_destroy(dialog);
		} else {
			page_setup = gtk_page_setup_new();
		}

		g_error_free(err);
	} else {
		// on success get the paper dimensions
		WlibGetPaperSize();
	}

	g_free(filename);

	if (page_setup && op) {
		gtk_print_operation_set_default_page_setup(op, page_setup);
	}

}

/**
 * Save the printer settings. If op is not NULL the settings are retrieved
 * from the print operation. Otherwise the state of the globals is saved.
 *
 * \param op IN printer operation. If NULL the glabal variables are used
 */

void
WlibSaveSettings(GtkPrintOperation *op)
{
	GError *err = NULL;
	gchar *filename;
	GtkWidget *dialog;

	if (op) {
		if (settings != NULL) {
			g_object_unref(settings);
		}

		settings = g_object_ref(gtk_print_operation_get_print_settings(op));
	}

	filename = g_build_filename(wGetAppWorkDir(), PRINTSETTINGS, NULL);

	if (!gtk_print_settings_to_file(settings, filename, &err)) {
		dialog = gtk_message_dialog_new(GTK_WINDOW(gtkMainW->gtkwin),
		                                GTK_DIALOG_DESTROY_WITH_PARENT,
		                                GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
		                                "%s",err->message);

		g_error_free(err);
		gtk_dialog_run(GTK_DIALOG(dialog));
		gtk_widget_destroy(dialog);
	}

	g_free(filename);

	if (op) {
		if (page_setup != NULL) {
			g_object_unref(page_setup);
		}

		page_setup = g_object_ref(gtk_print_operation_get_default_page_setup(op));
	}

	filename = g_build_filename(wGetAppWorkDir(), PAGESETTINGS, NULL);

	if (!gtk_page_setup_to_file(page_setup, filename, &err)) {
		dialog = gtk_message_dialog_new(GTK_WINDOW(gtkMainW->gtkwin),
		                                GTK_DIALOG_DESTROY_WITH_PARENT,
		                                GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
		                                "%s",err->message);

		g_error_free(err);
		gtk_dialog_run(GTK_DIALOG(dialog));
		gtk_widget_destroy(dialog);
	}

	g_free(filename);

}

/**
 * Page setup function. Previous settings are loaded and the setup
 * dialog is shown. The settings are saved after the dialog ends.
 *
 * \param callback IN unused
 */

void wPrintSetup(wPrintSetupCallBack_p callback)
{
	GtkPageSetup *new_page_setup;
//    gchar *filename;
//    GError *err;
//    GtkWidget *dialog;

	if ( !settings ) {
		WlibApplySettings(NULL);
	}

	new_page_setup = gtk_print_run_page_setup_dialog(GTK_WINDOW(gtkMainW->gtkwin),
	                 page_setup, settings);

	if (page_setup
	    && (page_setup != new_page_setup)) {      //Can be the same if no mods...
		g_object_unref(page_setup);
	}

	page_setup = new_page_setup;

	WlibGetPaperSize();
	WlibSaveSettings(NULL);
}

/*****************************************************************************
 *
 *
 *
 */


static GtkPrinter * pDefaultPrinter = NULL;
gboolean isDefaultPrinter( GtkPrinter * printer, gpointer data )
{
//const char * pPrinterName = gtk_printer_get_name( printer );
	if ( gtk_printer_is_default( printer ) ) {
		pDefaultPrinter = printer;
		return TRUE;
	}
	return FALSE;
}

static void getDefaultPrinter()
{
	pDefaultPrinter = NULL;
	gtk_enumerate_printers( isDefaultPrinter, NULL, NULL, TRUE );
}

const char * wPrintGetName()
{
	static char sPrinterName[100];
	WlibApplySettings( NULL );
	const char * pPrinterName =
	        gtk_print_settings_get( settings, "format-for-printer" );
	if ( pPrinterName == NULL ) {
		getDefaultPrinter();
		if ( pDefaultPrinter ) {
			pPrinterName = gtk_printer_get_name( pDefaultPrinter );
		}
	}
	if ( pPrinterName == NULL ) {
		pPrinterName = "";
	}
	strncpy (sPrinterName, pPrinterName, sizeof sPrinterName - 1 );
	sPrinterName[ sizeof sPrinterName - 1 ] = '\0';
	for ( char * cp = sPrinterName; *cp; cp++ )
		if ( *cp == ':' ) {
			*cp = '-';
		}
	return sPrinterName;
}
/*****************************************************************************
 *
 * BASIC PRINTING
 *
 */


/**
 * set the current line type for printing operations
 *
 * \param lineWidth IN new line width
 * \param lineType IN flag for line type (dashed or full)
 * \param opts IN unused
 * \return
 */


static void setLineType(
        double lineWidth,
        wDrawLineType_e lineType,
        wDrawOpts opts)
{
	cairo_t *cr = psPrint_d.printContext;

//    double dashes[] = { DASH_LENGTH, 3 };							//Reduce gap in between dashes
//    static int len_dashes  = sizeof(dashes) / sizeof(dashes[0]);

	if (lineWidth < 0.0) {
		lineWidth = P2I(-lineWidth)*2.0/scale_adjust;
	}

	// make sure that there is a minimum line width used
	if (lineWidth <= 0.09) {
		lineWidth = 0.1/scale_adjust;
	}

	cairo_set_line_width(cr, lineWidth);
	switch(lineType) {
	case wDrawLineDot: {
		double dashes[] = { 1,  2, 1,  2};
		static int len_dashes  = sizeof(dashes) / sizeof(dashes[0]);
		cairo_set_dash(cr, dashes, len_dashes, 0.0);
		break;
	}
	case wDrawLineDash: {
		double dashes[] = { DASH_LENGTH, 3 };							//Reduce gap in between dashes
		static int len_dashes  = sizeof(dashes) / sizeof(dashes[0]);
		cairo_set_dash(cr, dashes, len_dashes, 0.0);
		break;
	}
	case wDrawLineDashDot: {
		double dashes[] = { 3, 2, 1, 2};
		static int len_dashes  = sizeof(dashes) / sizeof(dashes[0]);
		cairo_set_dash(cr, dashes, len_dashes, 0.0);
		break;
	}
	case wDrawLineDashDotDot: {
		double dashes[] = { 3, 2, 1, 2, 1, 2};
		static int len_dashes  = sizeof(dashes) / sizeof(dashes[0]);
		cairo_set_dash(cr, dashes, len_dashes, 0.0);
		break;
	}
	case wDrawLineCenter: {
		double dashes[] = { 1.5*DASH_LENGTH, 3, DASH_LENGTH, 3};
		static int len_dashes  = sizeof(dashes) / sizeof(dashes[0]);
		cairo_set_dash(cr, dashes, len_dashes, 0.0);
		break;
	}
	case wDrawLinePhantom: {
		double dashes[] = { 1.5*DASH_LENGTH, 3, DASH_LENGTH, 3, DASH_LENGTH, 3};
		static int len_dashes  = sizeof(dashes) / sizeof(dashes[0]);
		cairo_set_dash(cr, dashes, len_dashes, 0.0);
		break;
	}
	default:
		cairo_set_dash(cr, NULL, 0, 0.0);
	}

}

/**
 * set the color for the following print operations
 *
 * \param color IN the new color
 * \return
 */

static void psSetColor(
        wDrawColor color)
{
	cairo_t *cr = psPrint_d.printContext;
	GdkColor* const gcolor = wlibGetColor(color, TRUE);

	cairo_set_source_rgb(cr, gcolor->red / 65535.0,
	                     gcolor->green / 65535.0,
	                     gcolor->blue / 65535.0);
}

/**
 * Print a straight line
 *
 * \param x0, y0 IN  starting point in pixels
 * \param x1, y1 IN  ending point in pixels
 * \param width line width
 * \param lineType
 * \param color color
 * \param opts ?
 */

void psPrintLine(
        wDrawPix_t x0, wDrawPix_t y0,
        wDrawPix_t x1, wDrawPix_t y1,
        wDrawWidth width,
        wDrawLineType_e lineType,
        wDrawColor color,
        wDrawOpts opts)
{
	if (color == wDrawColorWhite) {
		return;
	}

	if (opts&wDrawOptTemp) {
		return;
	}

	psSetColor(color);
	setLineType(width, lineType, opts);

	cairo_move_to(psPrint_d.printContext,
	              x0, y0);
	cairo_line_to(psPrint_d.printContext,
	              x1, y1);
	cairo_stroke(psPrint_d.printContext);
}

/**
 * Print an arc around a specified center
 *
 * \param x0, y0 IN  center of arc
 * \param r IN radius
 * \param angle0, angle1 IN start and end angle
 * \param drawCenter draw marking for center
 * \param width line width
 * \param lineType
 * \param color color
 * \param opts ?
 */

void psPrintArc(
        wDrawPix_t x0, wDrawPix_t y0,
        wDrawPix_t r,
        double angle0,
        double angle1,
        wBool_t drawCenter,
        wDrawWidth width,
        wDrawLineType_e lineType,
        wDrawColor color,
        wDrawOpts opts)
{
	cairo_t *cr = psPrint_d.printContext;

	if (color == wDrawColorWhite) {
		return;
	}

	if (opts&wDrawOptTemp) {
		return;
	}

	psSetColor(color);
	setLineType(width, lineType, opts);

	if (angle1 >= 360.0) {
		angle1 = 359.999;
	}

	angle1 = 90.0-(angle0+angle1);

	while (angle1 < 0.0) {
		angle1 += 360.0;
	}

	while (angle1 >= 360.0) {
		angle1 -= 360.0;
	}

	angle0 = 90.0-angle0;

	while (angle0 < 0.0) {
		angle0 += 360.0;
	}

	while (angle0 >= 360.0) {
		angle0 -= 360.0;
	}

	// draw the curve
	cairo_arc(cr, x0, y0, r, angle1 * M_PI / 180.0, angle0 * M_PI / 180.0);

	if (drawCenter) {
		// draw crosshair for center of curve
		cairo_move_to(cr, x0 - CENTERMARK_LENGTH / 2, y0);
		cairo_line_to(cr, x0 + CENTERMARK_LENGTH / 2, y0);
		cairo_move_to(cr, x0, y0 - CENTERMARK_LENGTH / 2);
		cairo_line_to(cr, x0, y0 + CENTERMARK_LENGTH / 2);
	}

	cairo_stroke(psPrint_d.printContext);
}

/**
 * Print a filled rectangle
 *
 * \param x0, y0 IN top left corner
 * \param x1, y1 IN bottom right corner
 * \param color IN fill color
 * \param opts IN options
 * \return
 */

void psPrintFillRectangle(
        wDrawPix_t x0, wDrawPix_t y0,
        wDrawPix_t x1, wDrawPix_t y1,
        wDrawColor color,
        wDrawOpts opts)
{
	cairo_t *cr = psPrint_d.printContext;
	double width = x0 - x1;
	double height = y0 - y1;

	if (color == wDrawColorWhite) {
		return;
	}

	if (opts&wDrawOptTemp) {
		return;
	}

	psSetColor(color);

	cairo_rectangle(cr, x0, y0, width, height);

	cairo_fill(cr);
}

/**
 * Print a filled polygon
 *
 * \param p IN a list of x and y coordinates
 * \param cnt IN the number of points
 * \param color IN fill color
 * \param opts IN options
 * \paran fill IN Fill or not
 * \return
 */

void psPrintFillPolygon(
        wDrawPix_t p[][2],
        wPolyLine_e type[],
        int cnt,
        wDrawColor color,
        wDrawOpts opts,
        int fill,
        int open )
{
	int inx;
	cairo_t *cr = psPrint_d.printContext;

	if (color == wDrawColorWhite) {
		return;
	}

	if (opts&wDrawOptTemp) {
		return;
	}

	psSetColor(color);

	wDrawPix_t mid0[2], mid1[2], /*mid2[2],*/ mid3[2], mid4[2];

	for (inx=0; inx<cnt; inx++) {
		int j = inx-1;
		int k = inx+1;
		if (j < 0) { j = cnt-1; }
		if (k > cnt-1) { k = 0; }
		double len0, len1;
		double d0x = (p[inx][0]-p[j][0]);
		double d0y = (p[inx][1]-p[j][1]);
		double d1x = (p[k][0]-p[inx][0]);
		double d1y = (p[k][1]-p[inx][1]);
		len0 = (d0x*d0x+d0y*d0y);
		len1 = (d1x*d1x+d1y*d1y);
		mid0[0] = (d0x/2)+p[j][0];
		mid0[1] = (d0y/2)+p[j][1];
		mid1[0] = (d1x/2)+p[inx][0];
		mid1[1] = (d1y/2)+p[inx][1];
		if (type && (type[inx] == wPolyLineRound) && (len1>0) && (len0>0)) {
			double ratio = sqrt(len0/len1);
			if (len0 < len1) {
				mid1[0] = ((d1x*ratio)/2)+p[inx][0];
				mid1[1] = ((d1y*ratio)/2)+p[inx][1];
			} else {
				mid0[0] = p[inx][0]-(d0x/(2*ratio));
				mid0[1] = p[inx][1]-(d0y/(2*ratio));
			}
		}
		mid3[0] = (p[inx][0]-mid0[0])/2+mid0[0];
		mid3[1] = (p[inx][1]-mid0[1])/2+mid0[1];
		mid4[0] = (mid1[0]-p[inx][0])/2+p[inx][0];
		mid4[1] = (mid1[1]-p[inx][1])/2+p[inx][1];
		wDrawPix_t save[2];
		if (inx==0) {
			if (!type || (type && type[0] == wPolyLineStraight) || open) {
				cairo_move_to(cr, p[ 0 ][ 0 ], p[ 0 ][ 1 ]);
				save[0] = p[0][0]; save[1] = p[0][1];
			} else {
				cairo_move_to(cr, mid0[0], mid0[1]);
				if (type[inx] == wPolyLineSmooth) {
					cairo_curve_to(cr, p[inx][0], p[inx][1], p[inx][0], p[inx][1], mid1[0],
					               mid1[1]);
				} else {
					cairo_curve_to(cr, mid3[0], mid3[1], mid4[0], mid4[1], mid1[0], mid1[1]);
				}
				save[0] = mid0[0]; save[1] = mid0[1];
			}
		} else if (!type || (type && type[inx] == wPolyLineStraight) || (open
		                && (inx==cnt-1)) ) {
			cairo_line_to(cr, p[ inx ][ 0 ], p[ inx ][ 1 ]);
		} else {
			cairo_line_to(cr, mid0[ 0 ], mid0[ 1 ]);
			if (type && type[inx] == wPolyLineSmooth) {
				cairo_curve_to(cr, p[inx][0],p[inx][1],p[inx][0],p[inx][1],mid1[0],mid1[1]);
			} else {
				cairo_curve_to(cr, mid3[0],mid3[1],mid4[0],mid4[1],mid1[0],mid1[1]);
			}
		}
		if ((inx==cnt-1) && !open) {
			cairo_line_to(cr, save[0], save[1]);
		}
	}

	if (fill && !open) { cairo_fill(cr); }
	else { cairo_stroke(cr); }
}

/**
 * Print a filled circle
 *
 * \param x0, y0  IN coordinates of center (in pixels )
 * \param r IN radius
 * \param color IN fill color
 * \param opts IN options
 * \return
 */

void psPrintFillCircle(
        wDrawPix_t x0, wDrawPix_t y0,
        wDrawPix_t r,
        wDrawColor color,
        wDrawOpts opts)
{
	if (color == wDrawColorWhite) {
		return;
	}

	if (opts&wDrawOptTemp) {
		return;
	}

	psSetColor(color);

	cairo_arc(psPrint_d.printContext,
	          x0, y0, r, 0.0, 2 * M_PI);

	cairo_fill(psPrint_d.printContext);
}


/**
 * Print a string at the given position using specified font and text size.
 * The orientation of the y-axis in XTrackCAD is wrong for cairo. So for
 * all other print primitives a flip operation is done. As this would
 * also affect the string orientation, printing a string has to be
 * treated differently. The starting point is transformed, then the
 * string is rotated and scaled as needed. Finally the string position
 * translated to the starting point calculated previously. The same
 * solution would have to be applied to a bitmap should printing
 * bitmaps ever be implemented.
 *
 * \param x IN x position in pixels
 * \param y IN y position in pixels
 * \param a IN angle of baseline in degrees. Positive is clockwise, 0 is direction of positive x axis
 * \param s IN string to print
 * \param fp IN font
 * \param fs IN font size
 * \param color IN text color
 * \param opts IN ???
 * \return
 */

void psPrintString(
        wDrawPix_t x, wDrawPix_t y,
        double a,
        char * s,
        wFont_p fp,
        double fs,
        wDrawColor color,
        wDrawOpts opts)
{
//    char * cp;
	double x0 = (double)x, y0 = (double)y;
	int text_height, text_width;
//    double ascent;

	cairo_t *cr;
	cairo_matrix_t matrix;

	PangoLayout *layout;
	PangoFontDescription *desc;
//    PangoFontMetrics *metrics;
	PangoContext *pcontext;

	if (color == wDrawColorWhite) {
		return;
	}

	cr = psPrint_d.printContext;

	// get the current transformation matrix and transform the starting
	// point of the string

	cairo_save(cr);

	cairo_get_matrix(cr, &matrix);

	cairo_matrix_transform_point(&matrix, &x0, &y0);

	cairo_identity_matrix(cr);

	layout = pango_cairo_create_layout(cr);

	// set the correct font and size
	/** \todo use a getter function instead of double conversion */
	desc = pango_font_description_from_string(wlibFontTranslate(fp));

	pango_font_description_set_size(desc, fs * PANGO_SCALE * scale_text);

	// render the string to a Pango layout
	pango_layout_set_font_description(layout, desc);

	gchar *utf8 = wlibConvertInput(s);

	pango_layout_set_text(layout, utf8, -1);
	pango_layout_set_width(layout, -1);
	pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
	pango_layout_get_size(layout, &text_width, &text_height);

	text_width = text_width / PANGO_SCALE;
	text_height = text_height / PANGO_SCALE;

	// get the height of the string
	pcontext = pango_cairo_create_context(cr);
//    metrics = pango_context_get_metrics(pcontext, desc,
//                                        pango_context_get_language(pcontext));

//    ascent = pango_font_metrics_get_ascent(metrics) / PANGO_SCALE;

	int baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;

	cairo_translate(cr, x0,	y0 );
	cairo_rotate(cr, -a * M_PI / 180.0);
	cairo_translate( cr, 0, -baseline );

	cairo_move_to(cr,0,0);

	pango_cairo_update_layout(cr, layout);


	// set the color
	psSetColor(color);

	// and show the string
	if(!(opts & wDrawOutlineFont)) {
		pango_cairo_show_layout(cr, layout);
		cairo_stroke( cr );
	} else {
		PangoLayoutLine *line;
		line = pango_layout_get_line_readonly (layout, 0);
		setLineType( wDrawLineSolid, 0, 0 );
		pango_cairo_layout_line_path (cr, line);
		cairo_stroke( cr );
	}
	// free unused objects
	g_object_unref(layout);
	g_object_unref(pcontext);

	cairo_restore(cr);
}

/**
 * Create clipping rectangle.
 *
 * \param x, y IN starting position
 * \param w, h IN width and height of rectangle
 * \return
 */

void wPrintClip(wDrawPix_t x, wDrawPix_t y, wDrawPix_t w, wDrawPix_t h)
{
	cairo_move_to(psPrint_d.printContext, x, y);
	cairo_rel_line_to(psPrint_d.printContext, w, 0);
	cairo_rel_line_to(psPrint_d.printContext, 0, h);
	cairo_rel_line_to(psPrint_d.printContext, -w, 0);
	cairo_close_path(psPrint_d.printContext);
	cairo_clip(psPrint_d.printContext);
}

/*****************************************************************************
 *
 * PAGE FUNCTIONS
 *
 */

/**
 * Get the paper dimensions and margins and setup the internal variables
 * \return
 */

static void
WlibGetPaperSize(void)
{
	double temp;

	bBorder = gtk_page_setup_get_bottom_margin(page_setup, GTK_UNIT_INCH);
	tBorder = gtk_page_setup_get_top_margin(page_setup, GTK_UNIT_INCH);
	lBorder = gtk_page_setup_get_left_margin(page_setup, GTK_UNIT_INCH);
	rBorder = gtk_page_setup_get_right_margin(page_setup, GTK_UNIT_INCH);
	paperHeight = gtk_page_setup_get_paper_height(page_setup, GTK_UNIT_INCH);
	paperWidth = gtk_page_setup_get_paper_width(page_setup, GTK_UNIT_INCH);

	// XTrackCAD does page orientation itself. Basic assumption is that the
	// paper is always oriented in portrait mode. Ignore settings by user
	if (paperHeight < paperWidth) {
		temp = paperHeight;
		paperHeight = paperWidth;
		paperWidth = temp;
	}
}

/**
 * Get the paper size. The size returned is the printable area of the
 * currently selected paper, ie. the physical size minus the margins.
 * \param w OUT printable width of the paper in inches
 * \param h OUT printable height of the paper in inches
 * \return
 */


void wPrintGetMargins(
        double * tMargin,
        double * rMargin,
        double * bMargin,
        double * lMargin )
{
	if ( tMargin ) { *tMargin = tBorder; }
	if ( rMargin ) { *rMargin = rBorder; }
	if ( bMargin ) { *bMargin = bBorder; }
	if ( lMargin ) { *lMargin = lBorder; }
}


/**
 * Get the paper size. The size returned is the physical size of the
 * currently selected paper.
 * \param w OUT physical width of the paper in inches
 * \param h OUT physical height of the paper in inches
 * \return
 */

void wPrintGetPageSize(
        double * w,
        double * h)
{
	// if necessary load the settings
	if (!settings) {
		WlibApplySettings(NULL);
	}

	WlibGetPaperSize();

	*w = paperWidth;
	*h = paperHeight;
}

/**
 * Cancel the current print job. This function is preserved here for
 * reference in case the function should be implemented again.
 * \param context IN unused
 * \return
 */
//static void printAbort(void * context)
//{
//    printContinue = FALSE;
//	wWinShow( printAbortW, FALSE );
//}

/**
 * Initialize new page.
 * The cairo_save() / cairo_restore() cycle was added to solve problems
 * with a multi page print operation. This might actually be a bug in
 * cairo but I didn't examine that any further.
 *
 * \return   print context for the print operation
 */
wDraw_p wPrintPageStart(void)
{
	pageCount++;

	cairo_save(psPrint_d.printContext);

	return &psPrint_d;
}

/**
 * End of page. This function returns the contents of printContinue. The
 * caller continues printing as long as TRUE is returned. Setting
 * printContinue to FALSE in an asynchronous handler therefore cleanly
 * terminates a print job at the end of the page.
 *
 * \param p IN ignored
 * \return    always printContinue
 */


wBool_t wPrintPageEnd(wDraw_p p)
{
	cairo_show_page(psPrint_d.printContext);

	cairo_restore(psPrint_d.printContext);

	return printContinue;
}

/*****************************************************************************
 *
 * PRINT START/END
 *
 */


/**
 * Start a new document
 *
 * \param title IN title of document ( name of layout )
 * \param fTotalPageCount IN number of pages to print (unused)
 * \param copiesP OUT ???
 * \return TRUE if successful, FALSE if cancelled by user
 */

wBool_t wPrintDocStart(const char * title, int fTotalPageCount, int * copiesP)
{
	GtkWidget *printDialog;
	gint res;
//    cairo_surface_type_t surface_type;
//    cairo_matrix_t matrix;


	printDialog = gtk_print_unix_dialog_new(title, GTK_WINDOW(gtkMainW->gtkwin));

	// load the settings
	WlibApplySettings(NULL);

	// and apply them to the printer dialog
	gtk_print_unix_dialog_set_settings((GtkPrintUnixDialog *)printDialog, settings);
	gtk_print_unix_dialog_set_page_setup((GtkPrintUnixDialog *)printDialog,
	                                     page_setup);

	res = gtk_dialog_run((GtkDialog *)printDialog);

	if (res == GTK_RESPONSE_OK) {
		selPrinter = gtk_print_unix_dialog_get_selected_printer((
		                        GtkPrintUnixDialog *)printDialog);

		if (settings) {
			g_object_unref(settings);
		}

		settings = gtk_print_unix_dialog_get_settings((GtkPrintUnixDialog *)
		                printDialog);

		if (page_setup) {
			g_object_unref(page_setup);
		}

		page_setup = gtk_print_unix_dialog_get_page_setup((GtkPrintUnixDialog *)
		                printDialog);

		curPrintJob = gtk_print_job_new(title,
		                                selPrinter,
		                                settings,
		                                page_setup);

		psPrint_d.curPrintSurface = gtk_print_job_get_surface(curPrintJob,
		                            NULL);
		psPrint_d.printContext = cairo_create(psPrint_d.curPrintSurface);

		WlibApplySettings( NULL );
		//update the paper dimensions
		WlibGetPaperSize();

		/* for all surfaces including files the resolution is always 72 ppi (as all GTK uses PDF) */
		/*surface_type = */cairo_surface_get_type(psPrint_d.curPrintSurface);

		/*
		 * Override up-scaling for some printer drivers/Linux systems that don't support the latest CUPS
		 * - the user either sets preferences or the environment variable XTRKCADPRINTSCALE to a value
		 * and we just let the dpi default to 72ppi and set scaling to that value.
		 * And for PangoText we allow an override via preferences or variable XTRKCADPRINTTEXTSCALE
		 * Note - doing this will introduce differing artifacts.
		 *
		 */
		char * sEnvScale = PRODUCT "PRINTSCALE";
		char * sEnvTextScale = PRODUCT "PRINTTEXTSCALE";

		scale_text = 1.0;
		scale_adjust = 1.0;

		double printScale,printTextScale;

		wPrefGetFloat(PREFSECTION, PRINTSCALE, &printScale, -1.0);
		wPrefGetFloat(PREFSECTION, PRINTTEXTSCALE, &printTextScale, -1.0);


		//If the preferences are not set, look at environmental variables

		if (printScale < 0.0 ) {
			if (getenv(sEnvScale) && (atof(getenv(sEnvScale)) > 0.0)) {
				printScale = atof(getenv(sEnvScale));
			}
		}
		if (printTextScale < 0.0 ) {
			if (getenv(sEnvTextScale) && (atof(getenv(sEnvTextScale)) > 0.0)) {
				printTextScale = atof(getenv(sEnvTextScale));
			}
		}

		const char * sPrinterName = gtk_printer_get_name( selPrinter );
		if ((strcmp(sPrinterName,"Print to File") == 0) || printScale < 0.0) {
			double p_def = 600;
			cairo_surface_set_fallback_resolution(psPrint_d.curPrintSurface, p_def, p_def);
			psPrint_d.dpi = p_def;
			scale_adjust = 72/p_def;
		} else {
			if (printTextScale > 0.0) {
				scale_text = printTextScale;
			}
			if (printScale > 0.0) {
				scale_adjust = printScale;
			}
			psPrint_d.dpi = 72;
		}

		// in XTrackCAD 0,0 is top left, in cairo bottom left. This is
		// corrected via the following transformations.
		// also the translate makes sure that the drawing is rendered
		// within the paper margin

		cairo_translate(psPrint_d.printContext, lBorder*72,  (paperHeight-bBorder)*72 );

		cairo_scale(psPrint_d.printContext, 1.0 * scale_adjust,  -1.0 * scale_adjust);

		//cairo_translate(psPrint_d.printContext, 0, -paperHeight* psPrint_d.dpi);

		WlibSaveSettings(NULL);
	}

	gtk_widget_destroy(printDialog);

	if (copiesP) {
		*copiesP = 1;
	}

	printContinue = TRUE;

	if (res != GTK_RESPONSE_OK) {
		return FALSE;
	} else {
		return TRUE;
	}
}

/**
 * Callback for job finished event. Destroys the cairo context.
 *
 * \param job IN unused
 * \param data IN unused
 * \param err IN if != NULL, an error dialog ist displayed
 * \return
 */

void
doPrintJobFinished(GtkPrintJob *job, void *data, GError *err)
{
//    GtkWidget *dialog;

	cairo_destroy(psPrint_d.printContext);

	if (err) {
		/*dialog = */gtk_message_dialog_new(GTK_WINDOW(gtkMainW->gtkwin),
		                                    GTK_DIALOG_DESTROY_WITH_PARENT,
		                                    GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
		                                    "%s",err->message);
	}
}

/**
 * Finish the print operation
 * \return
 */

void wPrintDocEnd(void)
{
	cairo_surface_finish(psPrint_d.curPrintSurface);

	gtk_print_job_send(curPrintJob,
	                   doPrintJobFinished,
	                   NULL,
	                   NULL);

//	wWinShow( printAbortW, FALSE );
}


wBool_t wPrintQuit(void)
{
	return FALSE;
}


wBool_t wPrintInit(void)
{
	return TRUE;
}
