/************************************************************************
 *                                                                      *
 * GLE - Graphics Layout Engine <http://www.gle-graphics.org/>          *
 *                                                                      *
 * Modified BSD License                                                 *
 *                                                                      *
 * Copyright (C) 2009 GLE.                                              *
 *                                                                      *
 * Redistribution and use in source and binary forms, with or without   *
 * modification, are permitted provided that the following conditions   *
 * are met:                                                             *
 *                                                                      *
 *    1. Redistributions of source code must retain the above copyright *
 * notice, this list of conditions and the following disclaimer.        *
 *                                                                      *
 *    2. Redistributions in binary form must reproduce the above        *
 * copyright notice, this list of conditions and the following          *
 * disclaimer in the documentation and/or other materials provided with *
 * the distribution.                                                    *
 *                                                                      *
 *    3. The name of the author may not be used to endorse or promote   *
 * products derived from this software without specific prior written   *
 * permission.                                                          *
 *                                                                      *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR   *
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED       *
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE   *
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY       *
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL   *
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE    *
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS        *
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER *
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR      *
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN  *
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                        *
 *                                                                      *
 ************************************************************************/

#include "all.h"
#include "core.h"
#include "file_io.h"
#include "texinterface.h"
#include "cutils.h"
#include "gprint.h"

#ifdef HAVE_CAIRO

#include <cairo-pdf.h>
#include <cairo-svg.h>

extern struct gmodel g;

char *font_getname(int i);

GLECairoDevice::GLECairoDevice(bool showerror) {
	m_ShowError = showerror;
}

GLECairoDevice::~GLECairoDevice() {
}

void GLECairoDevice::arc(dbl r,dbl t1,dbl t2,dbl cx,dbl cy) {
	//double dx,dy;
	double x,y;
	g_get_xy(&x,&y);
	//polar_xy(r,t1,&dx,&dy);
	if (!g.inpath) {
		if (!g.xinline) cairo_new_path(cr);
	}
	cairo_arc(cr, cx, cy, r, t1*GLE_PI/180.0, t2*GLE_PI/180.0);
	g.xinline = true;
	if (!g.inpath) g_move(x,y);
}

void GLECairoDevice::arcto(dbl x1,dbl y1,dbl x2,dbl y2,dbl rrr) {
	if (g.xinline==false) move(g.curx,g.cury);
	cairo_curve_to(cr, x1, y1, x2, y2, x2, y2);
	g.xinline = true;
}

void GLECairoDevice::beginclip(void)  {
	cairo_save(cr);
}

void GLECairoDevice::bezier(dbl x1,dbl y1,dbl x2,dbl y2,dbl x3,dbl y3) {
	double x=g.curx,y=g.cury;
	if (g.inpath) {
		if (g.xinline==false) move(g.curx,g.cury);
		cairo_curve_to(cr, x1, y1, x2, y2, x3, y3);
	} else {
		g_flush();
		if (!g.xinline) cairo_move_to(cr, x, y);
		cairo_curve_to(cr, x1, y1, x2, y2, x3, y3);
	}
	g.xinline = true;
}

void GLECairoDevice::box_fill(dbl x1, dbl y1, dbl x2, dbl y2) {
	if (g.inpath) {
		xdbox(x1,y1,x2,y2);
	} else {
		g_flush();
		cairo_new_path(cr);
		xdbox(x1,y1,x2,y2);
		ddfill();
		cairo_new_path(cr);
	}
}

void GLECairoDevice::box_stroke(dbl x1, dbl y1, dbl x2, dbl y2, bool reverse) {
	if (g.inpath) {
		if (reverse) {
			cairo_move_to(cr, x1, y1);
			cairo_line_to(cr, x1, y2);
			cairo_line_to(cr, x2, y2);
			cairo_line_to(cr, x2, y1);
			cairo_close_path(cr);
		} else {
			xdbox(x1,y1,x2,y2);
		}
	} else {
		g_flush();
		cairo_new_path(cr);
		xdbox(x1,y1,x2,y2);
		cairo_stroke(cr);
		//ps_nvec = 0;
	}
}

void GLECairoDevice::dochar(int font, int cc) {
	if (font_get_encoding(font) > 2) {
		my_char(font, cc);
	} else {
		my_char(17, cc);
	}
}

void GLECairoDevice::resetfont() {
}

void GLECairoDevice::circle_fill(double zr) {
	double x=g.curx,y=g.cury;
	if (g.inpath) {
		cairo_arc(cr, x, y, zr, 0, 2*GLE_PI);
	} else {
		g_flush();
		cairo_new_path(cr);
		cairo_arc(cr, x, y, zr, 0, 2*GLE_PI);
		ddfill();
		cairo_new_path(cr);
	}
}

void GLECairoDevice::circle_stroke(double zr) {
	double x,y;
	g_get_xy(&x,&y);
	if (g.inpath) {
		cairo_arc(cr, x, y, zr, 0, 2*GLE_PI);
	} else {
		g_flush();
		cairo_new_path(cr);
		cairo_arc(cr, x, y, zr, 0, 2*GLE_PI);
		cairo_close_path(cr);
		cairo_stroke(cr);
	}
}

void GLECairoDevice::clear(void) {
	//cout << "clear not yet implemented" << endl;
}

void GLECairoDevice::clip(void) {
	cairo_clip(cr);
}

void GLECairoDevice::closedev(void) {
    cairo_destroy(cr);
    cairo_surface_destroy(surface);
    printf("%s]\n", m_OutputName.getName().c_str());
}

void GLECairoDevice::closepath(void) {
	cairo_close_path(cr);
}

void GLECairoDevice::dfont(char *c) {
	cout << "dfont not yet implemented" << endl;
}

void GLECairoDevice::ellipse_fill(double rx, double ry) {
	double x=g.curx,y=g.cury;
	if (g.inpath) {
		cairo_save (cr);
		cairo_translate (cr, x, y);
		cairo_scale (cr, rx, ry);
		cairo_arc (cr, 0, 0, 1, 0, 2*GLE_PI);
		cairo_restore (cr);
	} else {
		g_flush();
		cairo_new_path(cr);
		cairo_save (cr);
		cairo_translate (cr, x, y);
		cairo_scale (cr, rx, ry);
		cairo_arc (cr, 0, 0, 1, 0, 2*GLE_PI);
		cairo_restore (cr);
		ddfill();
		cairo_new_path(cr);
	}
}

void GLECairoDevice::ellipse_stroke(double rx, double ry) {
	double x,y;
	g_get_xy(&x,&y);
	if (!g.inpath) {
		if (!g.xinline) cairo_new_path(cr);
	}
	cairo_save (cr);
	cairo_translate (cr, x, y);
	cairo_scale (cr, rx, ry);
	cairo_arc (cr, 0, 0, 1, 0, 2*GLE_PI);
	cairo_restore (cr);
	g.xinline = true;
	if (!g.inpath) g_move(x,y);
}

void GLECairoDevice::elliptical_arc(double rx,double ry,double t1,double t2,double cx,double cy) {
	double x,y;
	g_get_xy(&x,&y);
	if (!g.inpath) {
		if (!g.xinline) cairo_new_path(cr);
	}
	cairo_save (cr);
	cairo_translate (cr, cx, cy);
	cairo_scale (cr, rx, ry);
	cairo_arc (cr, 0, 0, 1, t1*GLE_PI/180.0, t2*GLE_PI/180.0);
	cairo_restore (cr);
	g.xinline = true;
	if (!g.inpath) g_move(x,y);
}

void GLECairoDevice::elliptical_narc(double rx,double ry,double t1,double t2,double cx,double cy) {
	double x,y;
	g_get_xy(&x,&y);
	if (!g.inpath) {
		if (!g.xinline) cairo_new_path(cr);
	}
	cairo_save (cr);
	cairo_translate (cr, cx, cy);
	cairo_scale (cr, rx, ry);
	cairo_arc_negative (cr, 0, 0, 1, t1*GLE_PI/180.0, t2*GLE_PI/180.0);
	cairo_restore (cr);
	g.xinline = true;
	if (!g.inpath) g_move(x,y);
}

void GLECairoDevice::endclip(void)  {
	g_flush();
	cairo_restore(cr);
	gmodel* state = (gmodel*) myallocz(SIZEOFSTATE);
	g_get_state(state);
	g_set_state(state);
	myfree(state);
}

void GLECairoDevice::fill(void) {
	//cairo_save(cr);
	ddfill();
	//cairo_restore(cr);
}

void GLECairoDevice::ddfill(void) {
	if (g_cur_fill.b[B_F] == 255) return; /* clear fill, do nothing */
	if (g_cur_fill.b[B_F] == 2) {
		shade();
		return;
	}
	set_fill();			/*because color and fill are the same*/
	//cairo_fill(cr);
	cairo_fill_preserve(cr);
	set_color();
}

void GLECairoDevice::shade(void) {
	cout << "shade not yet implemented" << endl;

	int step1 = g_cur_fill.b[B_B];
	int step2 = g_cur_fill.b[B_G];
	int xstep = max(step1, step2);
	int ystep = max(step1, step2);

	cout << xstep << endl;
	cout << ystep << endl;

	cairo_surface_t *isurface;
	cairo_pattern_t *pattern;
	cairo_t *icr;
	cairo_matrix_t   matrix;

	cairo_save(cr);
	cairo_get_matrix(cr, &matrix);

	isurface = cairo_surface_create_similar (surface, CAIRO_CONTENT_COLOR_ALPHA, xstep, ystep);
	icr = cairo_create(isurface);

	//out() << "/BBox [0 0 " << xstep << " " << ystep << "]" << endl;
	//out() << "/XStep " << xstep << endl;
	//out() << "/YStep " << ystep << endl;
	//out() << "/PaintProc" << endl;
	//out() << "{ pop" << endl;
	//out() << "1 setgray" << endl;
	cairo_set_source_rgb(icr, 255.0, 255.0, 255.0);
	//out() << "2 setlinecap" << endl;
	cairo_set_line_cap(icr, (cairo_line_cap_t)2);
	//out() << "-1 -1 " << (xstep+1) << " " << (ystep+1) << " rectfill" << endl;
	cairo_rectangle(icr, -1, -1, (xstep+1), (ystep+1));
	cairo_fill(icr);

	if (g_cur_fill_color.l == GLE_COLOR_BLACK) {
		//out() << "0 setgray" << endl;
		cairo_set_source_rgb(icr, 0, 0, 0);
	} else {
		//set_color(g_cur_fill_color);
		cairo_set_source_rgb(icr, g_cur_fill_color.b[B_R]/255.0, g_cur_fill_color.b[B_G]/255.0, g_cur_fill_color.b[B_B]/255.0);
	}
	//out() << (int)g_cur_fill.b[B_R] << " setlinewidth" << endl;
	cairo_set_line_width(icr, (int)g_cur_fill.b[B_R]);
	if (step1 > 0) {
		//out() << "0 0 moveto" << endl;
		cairo_move_to(icr, 0, 0);
		//out() << xstep << " " << ystep << " l" << endl;
		cairo_line_to(icr, xstep, ystep);
		//out() << "stroke" << endl;
		cairo_stroke(icr);
		if (step2 == 0) {
			//out() << xstep/2 << " " << -ystep/2 << " moveto" << endl;
			cairo_move_to(icr, xstep/2, -ystep/2);
			//out() << 3*xstep/2 << " " << ystep/2 << " l" << endl;
			cairo_line_to(icr, 3*xstep/2, ystep/2);
			//out() << "stroke" << endl;
			cairo_stroke(icr);
			//out() << -xstep/2 << " " << ystep/2 << " moveto" << endl;
			cairo_move_to(icr, -xstep/2, ystep/2);
			//out() << xstep/2 << " " << 3*ystep/2 << " l" << endl;
			cairo_line_to(icr, xstep/2, 3*ystep/2);
			//out() << "stroke" << endl;
			cairo_stroke(icr);
		}
	}
	if (step2 > 0) {
		//out() << "0 " << ystep << " moveto" << endl;
		cairo_move_to(icr, 0, ystep);
		//out() << xstep << " 0 l" << endl;
		cairo_line_to(icr, xstep, 0);
		//out() << "stroke" << endl;
		cairo_stroke(icr);
		if (step1 == 0) {
			//out() << -xstep/2 << " " << ystep/2 << " moveto" << endl;
			cairo_move_to(icr, -xstep/2, ystep/2);
			//out() << xstep/2 << " " << -ystep/2 << " l" << endl;
			cairo_line_to(icr, xstep/2, -ystep/2);
			//out() << "stroke" << endl;
			cairo_stroke(icr);
			//out() << xstep/2 << " " << 3*ystep/2 << " moveto" << endl;
			cairo_move_to(icr, xstep/2, 3*ystep/2);
			//out() << 3*xstep/2 << " " << ystep/2 << " l" << endl;
			cairo_line_to(icr, 3*xstep/2, ystep/2);
			//out() << "stroke" << endl;
			cairo_stroke(icr);
		}
	}
	cairo_set_source_rgb(icr, g_cur_color.b[B_R]/255.0, g_cur_color.b[B_G]/255.0, g_cur_color.b[B_B]/255.0);

	pattern = cairo_pattern_create_for_surface (isurface);
	cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);

	cairo_matrix_init_scale (&matrix, 3*72, 3*72);
	cairo_pattern_set_matrix (pattern, &matrix);

	cairo_set_source (cr, pattern);

	cairo_fill (cr);
	cairo_restore(cr);

	cairo_pattern_destroy (pattern);
	cairo_destroy (icr);
	cairo_surface_destroy (isurface);

	/*

	int              w, h;
	cairo_surface_t *image;
	cairo_pattern_t *pattern;
	cairo_matrix_t   matrix;

	image = cairo_image_surface_create_from_png ("test_shade.png");
	w = cairo_image_surface_get_width (image);
	h = cairo_image_surface_get_height (image);

	pattern = cairo_pattern_create_for_surface (image);
	cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);

	cairo_save(cr);
	//cairo_translate (cr, 128.0, 128.0);
	//cairo_rotate (cr, M_PI / 4);
	//cairo_scale (cr, 1 / sqrt (2), 1 / sqrt (2));
	//cairo_translate (cr, -128.0, -128.0);


	//cairo_matrix_init_scale (&matrix, w/256.0 * 5.0, h/256.0 * 5.0);
	cairo_get_matrix(cr, &matrix);
	cairo_pattern_set_matrix (pattern, &matrix);

	cairo_set_source (cr, pattern);

	//cairo_rectangle (cr, 0, 0, 256.0, 256.0);
	cairo_fill_preserve (cr);
	cairo_restore(cr);
	cairo_pattern_destroy (pattern);
	cairo_surface_destroy (image);
*/
}

void GLECairoDevice::fill_ary(int nwk,double *wkx,double *wky) {
	cout << "fill_ary not yet implemented" << endl;
}

void GLECairoDevice::flush(void) {
	if (g.inpath) return;
	if (g.xinline) {
		cairo_stroke(cr);
		//ps_nvec = 0;
	}
}

void GLECairoDevice::get_type(char *t) {
	strcpy(t,"CAIRO FILLPATH");
}

void GLECairoDevice::line(double zx,double zy) {
	if (g.xinline==false) {
		move(g.curx,g.cury);
	}
	cairo_line_to(cr, zx, zy);
}

void GLECairoDevice::line_ary(int nwk,double *wkx,double *wky) {
	cout << "line_ary not yet implemented" << endl;
}

void GLECairoDevice::message(char *s) {
	cout << "message not yet implemented" << endl;
}

void GLECairoDevice::move(double zx,double zy) {
	if (g.inpath) {
		cairo_move_to(cr, zx, zy);
	} else {
		//ps_nvec++;
		cairo_new_path(cr);
		cairo_move_to(cr, zx, zy);
	}
}

void GLECairoDevice::narc(dbl r,dbl t1,dbl t2,dbl cx,dbl cy) {
	//double dx,dy;
	double x,y;
	g_get_xy(&x,&y);
	//polar_xy(r,t1,&dx,&dy);
	if (!g.inpath) {
		if (!g.xinline) cairo_new_path(cr);
	}
	cairo_arc_negative(cr, cx, cy, r, t1*GLE_PI/180.0, t2*GLE_PI/180.0);
	g.xinline = true;
	if (!g.inpath) g_move(x,y);
}

void GLECairoDevice::newpath(void) {
	cairo_new_path(cr);
}

void GLECairoDevice::opendev(double width, double height, GLEFileLocation* outputfile, const string& inputfile) throw(ParserError) {
}

void GLECairoDevice::pscomment(char* ss) {
	cout << "pscomment not yet implemented" << endl;
}

void GLECairoDevice::reverse(void)    /* reverse the order of stuff in the current path */ {
	cout << "reverse not yet implemented" << endl;
}

void GLECairoDevice::set_color(colortyp& color) {
	cairo_set_source_rgb(cr, color.b[B_R]/255.0, color.b[B_G]/255.0, color.b[B_B]/255.0);
}

void GLECairoDevice::set_color() {
	set_color(g_cur_color);
}
void GLECairoDevice::set_color(int f) {
	g_flush();
	g_cur_color.l = f;
	set_color();
}

void GLECairoDevice::set_fill(int f) {
	g_cur_fill.l = f;
}

void GLECairoDevice::set_pattern_color(int c) {
	cout << "set_pattern_color not yet implemented" << endl;
}

void GLECairoDevice::set_line_cap(int i) {
	/*  lcap, 0= butt, 1=round, 2=projecting square */
	if (!g.inpath) g_flush();
	cairo_set_line_cap(cr, (cairo_line_cap_t)i);
}

void GLECairoDevice::set_line_join(int i) {
	/* 0=miter, 1=round, 2=bevel */
	if (!g.inpath) g_flush();
	cairo_set_line_join(cr, (cairo_line_join_t)i);
}

void GLECairoDevice::set_line_miterlimit(double d) {
	if (!g.inpath) g_flush();
	cairo_set_miter_limit(cr, d);
}

void GLECairoDevice::set_line_style(const char *s) {
	/* should deal with [] for solid lines */
	static const char *defline[] = {"","","12","41","14","92","1282",
	                                "9229","4114","54","73","7337","6261","2514"};
	if (!g.inpath) g_flush();
	if (strlen(s) == 1) {
		s = defline[*s-'0'];
	}
	int nb_dashes = strlen(s);
	double *dashes = new double[nb_dashes];
	for (int i = 0; i < nb_dashes; i++) {
		dashes[i] = (s[i]-'0')*g.lstyled;
	}
	cairo_set_dash(cr, dashes, nb_dashes, 0);
	delete[] dashes;
}

void GLECairoDevice::set_line_styled(double dd) {
	//cout << "set_line_styled not yet implemented" << endl;
}

void GLECairoDevice::set_line_width(double w) {
	if (w == 0) w = 0.02;
	if (w < .0002) w = 0;
	if (!g.inpath) g_flush();
	cairo_set_line_width(cr, w);
}

void GLECairoDevice::set_matrix(double newmat[3][3]) {
    cairo_matrix_t matrix;
    matrix.xx = newmat[0][0];
    matrix.xy = newmat[0][1];
    matrix.yx = - newmat[1][0];
    matrix.yy = - newmat[1][1];
    matrix.x0 = newmat[0][2];
    matrix.y0 = m_height * 72 / CM_PER_INCH - newmat[1][2];
    cairo_set_matrix(cr, &matrix);
}

void GLECairoDevice::set_path(int onoff) {
	//cout << "set_path not yet implemented" << endl;
}

void GLECairoDevice::source(const char *s) {
	//cout << "source not yet implemented" << endl;
}

void GLECairoDevice::stroke(void) {
	//cairo_save(cr);
	//cairo_stroke(cr);
	//cairo_restore(cr);
	cairo_stroke_preserve(cr);
}

void GLECairoDevice::set_fill(void) {
	set_color(g_cur_fill);
}

void GLECairoDevice::xdbox(double x1, double y1, double x2, double y2) {
	//cout << "xdbox" << endl;
	//cairo_rectangle_t(cr, x1, y1, x2 - x1, y2 - y1);
	cairo_move_to(cr, x1, y1);
	cairo_line_to(cr, x2, y1);
	cairo_line_to(cr, x2, y2);
	cairo_line_to(cr, x1, y2);
	cairo_close_path(cr);
}

void GLECairoDevice::devcmd(const char *s) {
	cout << "devcmd not yet implemented" << endl;
}

FILE* GLECairoDevice::get_file_pointer(void) {
	return NULL;
}

int GLECairoDevice::getDeviceType() {
	return GLE_DEVICE_NONE;
}

GLECairoDeviceSVG::GLECairoDeviceSVG(bool showerror) : GLECairoDevice(showerror) {
}

GLECairoDeviceSVG::~GLECairoDeviceSVG() {
}

void GLECairoDeviceSVG::opendev(double width, double height, GLEFileLocation* outputfile, const string& inputfile) throw(ParserError) {
	m_width = width;
	m_height = height;
	m_OutputName.copy(outputfile);
	m_OutputName.addExtension("svg");
    surface = cairo_svg_surface_create(m_OutputName.getFullPath().c_str(), 72*width/CM_PER_INCH+2, 72*height/CM_PER_INCH+2);
	cr = cairo_create(surface);
	g_scale(72.0/CM_PER_INCH, 72.0/CM_PER_INCH);
	g_translate(1.0*CM_PER_INCH/72, 1.0*CM_PER_INCH/72);
}

int GLECairoDeviceSVG::getDeviceType() {
	return GLE_DEVICE_CAIRO_SVG;
}

GLECairoDevicePDF::GLECairoDevicePDF(bool showerror) : GLECairoDevice(showerror) {
}

GLECairoDevicePDF::~GLECairoDevicePDF() {
}

void GLECairoDevicePDF::opendev(double width, double height, GLEFileLocation* outputfile, const string& inputfile) throw(ParserError) {
	m_width = width;
	m_height = height;
	m_OutputName.copy(outputfile);
	m_OutputName.addExtension("pdf");
	surface = cairo_pdf_surface_create(m_OutputName.getFullPath().c_str(), 72*width/CM_PER_INCH+2, 72*height/CM_PER_INCH+2);
	cr = cairo_create(surface);
	g_scale(72.0/CM_PER_INCH, 72.0/CM_PER_INCH);
	g_translate(1.0*CM_PER_INCH/72, 1.0*CM_PER_INCH/72);
}

int GLECairoDevicePDF::getDeviceType() {
	return GLE_DEVICE_CAIRO_PDF;
}

#endif
