/*************************************************************************
 * Copyright (c) 2011 AT&T Intellectual Property 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: Details at https://graphviz.org
 *************************************************************************/

#include "config.h"

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

#include <common/macros.h>
#include <common/const.h>
#include <common/types.h>
#include <common/utils.h>

#include <gvc/gvplugin_render.h>
#include <gvc/gvplugin_device.h>
#include <gvc/gvio.h>
#include <cgraph/strcasecmp.h>
#include <cgraph/unreachable.h>

typedef enum { FORMAT_VML, FORMAT_VMLZ, } format_type;

unsigned int  graphHeight,graphWidth;

static void vml_bzptarray(GVJ_t * job, pointf * A, int n)
{
    int i;
    char *c;

    c = "m ";			/* first point */
    for (i = 0; i < n; i++) {
        /* integers only in path! */
	gvprintf(job, "%s%.0f,%.0f ", c, A[i].x, graphHeight-A[i].y);
	if (i == 0)
	    c = "c ";		/* second point */
	else
	    c = "";		/* remaining points */
    }
    gvputs(job, "\"");
}

static void vml_print_color(GVJ_t * job, gvcolor_t color)
{
    switch (color.type) {
    case COLOR_STRING:
	gvputs(job, color.u.string);
	break;
    case RGBA_BYTE:
	if (color.u.rgba[3] == 0) /* transparent */
	    gvputs(job, "none");
	else
	    gvprintf(job, "#%02x%02x%02x",
		color.u.rgba[0], color.u.rgba[1], color.u.rgba[2]);
	break;
    default:
	UNREACHABLE(); // internal error
    }
}

static void vml_grstroke(GVJ_t * job, int filled)
{
    (void)filled;

    obj_state_t *obj = job->obj;

    gvputs(job, "<v:stroke color=\"");
    vml_print_color(job, obj->pencolor);
    if (obj->penwidth != PENWIDTH_NORMAL)
	gvprintf(job, "\" weight=\"%.0fpt", obj->penwidth);
    if (obj->pen == PEN_DASHED) {
	gvputs(job, "\" dashstyle=\"dash");
    } else if (obj->pen == PEN_DOTTED) {
	gvputs(job, "\" dashstyle=\"dot");
    }
    gvputs(job, "\" />");
}


static void vml_grfill(GVJ_t * job, int filled)
{
    obj_state_t *obj = job->obj;

    if (filled){
        gvputs(job, " filled=\"true\" fillcolor=\"");
	vml_print_color(job, obj->fillcolor);
        gvputs(job, "\" ");
    }else{
	gvputs(job, " filled=\"false\" ");
    }
}

// wrapper around `xml_escape` to set flags for HTML escaping
static void html_puts(GVJ_t *job, const char *s) {
  const xml_flags_t flags = {.dash = 1, .nbsp = 1, .utf8 = 1};
  (void)xml_escape(s, flags, (int(*)(void*, const char*))gvputs, job);
}

static void vml_comment(GVJ_t * job, char *str)
{
    gvputs(job, "      <!-- ");
    html_puts(job, str);
    gvputs(job, " -->\n");
}
static void vml_begin_job(GVJ_t * job)
{
    gvputs(job, "<HTML>\n");
    gvputs(job, "\n<!-- Generated by ");
    html_puts(job, job->common->info[0]);
    gvputs(job, " version ");
    html_puts(job, job->common->info[1]);
    gvputs(job, " (");
    html_puts(job, job->common->info[2]);
    gvputs(job, ")\n-->\n");
}

static void vml_begin_graph(GVJ_t * job)
{
    obj_state_t *obj = job->obj;
    char *name;

    graphHeight = (unsigned)(job->bb.UR.y - job->bb.LL.y);
    graphWidth  = (unsigned)(job->bb.UR.x - job->bb.LL.x);

    gvputs(job, "<HEAD>");
    gvputs(job, "<META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
 

    name = agnameof(obj->u.g);
    if (name[0]) {
        gvputs(job, "<TITLE>");
        html_puts(job, name);
        gvputs(job, "</TITLE>");
    }
    gvprintf(job, "<!-- Pages: %d -->\n", job->pagesArraySize.x * job->pagesArraySize.y);

/*  the next chunk and all the "DIV" stuff is not required,
 *  but it helps with non-IE browsers  */
    gvputs(job, "   <SCRIPT LANGUAGE='Javascript'>\n");
    gvputs(job, "   function browsercheck()\n");
    gvputs(job, "   {\n");
    gvputs(job, "      var ua = window.navigator.userAgent\n");
    gvputs(job, "      var msie = ua.indexOf ( 'MSIE ' )\n");
    gvputs(job, "      var ievers;\n");
    gvputs(job, "      var item;\n");
    gvputs(job, "      var VMLyes=new Array('_VML1_','_VML2_');\n");
    gvputs(job, "      var VMLno=new Array('_notVML1_','_notVML2_');\n");
    gvputs(job, "      if ( msie > 0 ){      // If Internet Explorer, return version number\n");
    gvputs(job, "         ievers= parseInt (ua.substring (msie+5, ua.indexOf ('.', msie )))\n");
    gvputs(job, "      }\n");
    gvputs(job, "      if (ievers>=5){\n");
    gvputs(job, "       for (x in VMLyes){\n");
    gvputs(job, "         item = document.getElementById(VMLyes[x]);\n");
    gvputs(job, "         if (item) {\n");
    gvputs(job, "           item.style.visibility='visible';\n");
    gvputs(job, "         }\n");
    gvputs(job, "       }\n");
    gvputs(job, "       for (x in VMLno){\n");
    gvputs(job, "         item = document.getElementById(VMLno[x]);\n");
    gvputs(job, "         if (item) {\n");
    gvputs(job, "           item.style.visibility='hidden';\n");
    gvputs(job, "         }\n");
    gvputs(job, "       }\n");
    gvputs(job, "     }else{\n");
    gvputs(job, "       for (x in VMLyes){\n");
    gvputs(job, "         item = document.getElementById(VMLyes[x]);\n");
    gvputs(job, "         if (item) {\n");
    gvputs(job, "           item.style.visibility='hidden';\n");
    gvputs(job, "         }\n");
    gvputs(job, "       }\n");
    gvputs(job, "       for (x in VMLno){\n");
    gvputs(job, "         item = document.getElementById(VMLno[x]);\n");
    gvputs(job, "         if (item) {\n");
    gvputs(job, "           item.style.visibility='visible';\n");
    gvputs(job, "         }\n");
    gvputs(job, "       }\n");
    gvputs(job, "     }\n");
    gvputs(job, "   }\n");
    gvputs(job, "   </SCRIPT>\n");

    gvputs(job, "</HEAD>");
    gvputs(job, "<BODY onload='browsercheck();'>\n");
    /* add 10pt pad to the bottom of the graph */
    gvputs(job, "<DIV id='_VML1_' style=\"position:relative; display:inline; visibility:hidden");
    gvprintf(job, " width: %upt; height: %upt\">\n", graphWidth, 10+graphHeight);
    gvputs(job, "<STYLE>\n");
    gvputs(job, "v\\:* { behavior: url(#default#VML);display:inline-block}\n"); 
    gvputs(job, "</STYLE>\n"); 
    gvputs(job, "<xml:namespace ns=\"urn:schemas-microsoft-com:vml\" prefix=\"v\" />\n"); 

    gvputs(job, " <v:group style=\"position:relative; ");
    gvprintf(job, " width: %upt; height: %upt\"", graphWidth, graphHeight);
    gvprintf(job, " coordorigin=\"0,0\" coordsize=\"%u,%u\" >", graphWidth, graphHeight);
}

static void vml_end_graph(GVJ_t * job)
{
   gvputs(job, "</v:group>\n");
   gvputs(job, "</DIV>\n");
   /* add 10pt pad to the bottom of the graph */
   gvputs(job, "<DIV id='_VML2_' style=\"position:relative;visibility:hidden\">\n");
   gvputs(job, "<!-- insert any other html content here -->\n");
   gvputs(job, "</DIV>\n");
   gvputs(job, "<DIV id='_notVML1_' style=\"position:relative;\">\n");
   gvputs(job, "<!-- this should only display on NON-IE browsers -->\n");
   gvputs(job, "<H2>Sorry, this diagram will only display correctly on Internet Explorer 5 (and up) browsers.</H2>\n");
   gvputs(job, "</DIV>\n");
   gvputs(job, "<DIV id='_notVML2_' style=\"position:relative;\">\n");
   gvputs(job, "<!-- insert any other NON-IE html content here -->\n");
   gvputs(job, "</DIV>\n");

   gvputs(job, "</BODY>\n</HTML>\n");
}

static void
vml_begin_anchor(GVJ_t * job, char *href, char *tooltip, char *target, char *id)
{
    (void)id;

    gvputs(job, "<a");
    if (href && href[0]) {
	gvputs(job, " href=\"");
	html_puts(job, href);
	gvputs(job, "\"");
    }
    if (tooltip && tooltip[0]) {
	gvputs(job, " title=\"");
	html_puts(job, tooltip);
	gvputs(job, "\"");
    }
    if (target && target[0]) {
	gvputs(job, " target=\"");
	html_puts(job, target);
	gvputs(job, "\"");
    }
    gvputs(job, ">\n");
}

static void vml_end_anchor(GVJ_t * job)
{
    gvputs(job, "</a>\n");
}

static void vml_textspan(GVJ_t * job, pointf p, textspan_t * span)
{
    pointf p1,p2;
    obj_state_t *obj = job->obj;
    PostscriptAlias *pA;

    switch (span->just) {
    case 'l':
	p1.x=p.x;
	break;
    case 'r':
	p1.x=p.x-span->size.x;
	break;
    default:
    case 'n':
	p1.x=p.x-(span->size.x/2);
	break;
    }
    p2.x=p1.x+span->size.x;
    if (span->size.y <  span->font->size){
      span->size.y = 1 + (1.1*span->font->size);
    }

    p1.x-=8; /* vml textbox margin fudge factor */
    p2.x+=8; /* vml textbox margin fudge factor */
    p2.y=graphHeight-(p.y);
    p1.y=(p2.y-span->size.y);
    /* text "y" was too high
     * Graphviz uses "baseline", VML seems to use bottom of descenders - so we fudge a little
     * (heuristics - based on eyeballs)  */
    if (span->font->size <12.){ /*     see graphs/directed/arrows.gv  */
      p1.y+=1.4+span->font->size/5; /* adjust by approx. descender */
      p2.y+=1.4+span->font->size/5; /* adjust by approx. descender */
    }else{
      p1.y+=2+span->font->size/5; /* adjust by approx. descender */
      p2.y+=2+span->font->size/5; /* adjust by approx. descender */
    }

    gvprintf(job, "<v:rect style=\"position:absolute; ");
    gvprintf(job, " left: %.2f; top: %.2f;", p1.x, p1.y);
    gvprintf(job, " width: %.2f; height: %.2f\"", p2.x-p1.x, p2.y-p1.y);
    gvputs(job, " stroked=\"false\" filled=\"false\">\n");
    gvputs(job, "<v:textbox inset=\"0,0,0,0\" style=\"position:absolute; v-text-wrapping:'false';padding:'0';");

    pA = span->font->postscript_alias;
    if (pA) {
        gvprintf(job, "font-family: '%s';", pA->family);
        if (pA->weight)
	    gvprintf(job, "font-weight: %s;", pA->weight);
        if (pA->stretch)
	    gvprintf(job, "font-stretch: %s;", pA->stretch);
        if (pA->style)
	    gvprintf(job, "font-style: %s;", pA->style);
    }
    else {
        gvprintf(job, "font-family: \'%s\';", span->font->name);
    }
    gvprintf(job, " font-size: %.2fpt;", span->font->size);
    switch (obj->pencolor.type) {
    case COLOR_STRING:
	if (strcasecmp(obj->pencolor.u.string, "black"))
	    gvprintf(job, "color:%s;", obj->pencolor.u.string);
	break;
    case RGBA_BYTE:
	gvprintf(job, "color:#%02x%02x%02x;",
		obj->pencolor.u.rgba[0], obj->pencolor.u.rgba[1], obj->pencolor.u.rgba[2]);
	break;
    default:
	UNREACHABLE(); // internal error
    }
    gvputs(job, "\"><center>");
    html_puts(job, span->str);
    gvputs(job, "</center></v:textbox>\n"); 
    gvputs(job, "</v:rect>\n");
}

static void vml_ellipse(GVJ_t * job, pointf * A, int filled)
{   
    double dx, dy, left, top;

    /* A[] contains 2 points: the center and corner. */
    gvputs(job, "  <v:oval style=\"position:absolute;");

    dx=A[1].x-A[0].x;
    dy=A[1].y-A[0].y;

    top=graphHeight-(A[0].y+dy);
    left=A[0].x - dx;
    gvprintf(job, " left: %.2f; top: %.2f;",left, top);
    gvprintf(job, " width: %.2f; height: %.2f\"", 2*dx, 2*dy);

    vml_grfill(job, filled);
    gvputs(job, " >");
    vml_grstroke(job, filled);
    gvputs(job, "</v:oval>\n");
}

static void
vml_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start,
	      int arrow_at_end, int filled)
{
    (void)arrow_at_start;
    (void)arrow_at_end;

    gvputs(job, " <v:shape style=\"position:absolute; ");
    gvprintf(job, " width: %u; height: %u\"", graphWidth, graphHeight);

    vml_grfill(job, filled);
    gvputs(job, " >");
    vml_grstroke(job, filled);
    gvputs(job, "<v:path  v=\"");
    vml_bzptarray(job, A, n);
    gvputs(job, "/></v:shape>\n");
}

static void vml_polygon(GVJ_t * job, pointf * A, int n, int filled)
{
    int i;
    double px,py;

    gvputs(job, " <v:shape style=\"position:absolute; ");
    gvprintf(job, " width: %u; height: %u\"", graphWidth, graphHeight);
    vml_grfill(job, filled);
    gvputs(job, " >");
    vml_grstroke(job, filled);

    gvputs(job, "<v:path  v=\"");
    for (i = 0; i < n; i++)
    {
        px=A[i].x;
        py= (graphHeight-A[i].y);
        if (i==0){
          gvputs(job, "m ");
        }
        /* integers only in path */
	gvprintf(job, "%.0f %.0f ", px, py);
        if (i==0) gvputs(job, "l ");
        if (i==n-1) gvputs(job, "x e \"/>");
    }
    gvputs(job, "</v:shape>\n");
}

static void vml_polyline(GVJ_t * job, pointf * A, int n)
{
    int i;

    gvputs(job, " <v:shape style=\"position:absolute; ");
    gvprintf(job, " width: %u; height: %u\" filled=\"false\">", graphWidth, graphHeight);
    gvputs(job, "<v:path v=\"");
    for (i = 0; i < n; i++)
    {
        if (i==0) gvputs(job, " m ");
	gvprintf(job, "%.0f,%.0f ", A[i].x, graphHeight-A[i].y);
        if (i==0) gvputs(job, " l ");
        if (i==n-1) gvputs(job, " e "); /* no x here for polyline */
    }
    gvputs(job, "\"/>");
    vml_grstroke(job, 0);                 /* no fill here for polyline */
    gvputs(job, "</v:shape>\n");
}

/* color names from 
  http://msdn.microsoft.com/en-us/library/bb250525(VS.85).aspx#t.color
*/
/* NB.  List must be LANG_C sorted */
static char *vml_knowncolors[] = {
     "aqua",  "black",  "blue", "fuchsia", 
     "gray",  "green", "lime",  "maroon", 
     "navy",  "olive",  "purple",  "red",
     "silver",  "teal",  "white",  "yellow"
};

gvrender_engine_t vml_engine = {
    vml_begin_job,
    0,				/* vml_end_job */
    vml_begin_graph,
    vml_end_graph,
    0,                          /* vml_begin_layer */
    0,                          /* vml_end_layer */
    0,                          /* vml_begin_page */
    0,                          /* vml_end_page */
    0,                          /* vml_begin_cluster */
    0,                          /* vml_end_cluster */
    0,				/* vml_begin_nodes */
    0,				/* vml_end_nodes */
    0,				/* vml_begin_edges */
    0,				/* vml_end_edges */
    0,                          /* vml_begin_node */
    0,                          /* vml_end_node */
    0,                          /* vml_begin_edge */
    0,                          /* vml_end_edge */
    vml_begin_anchor,
    vml_end_anchor,
    0,                          /* vml_begin_label */
    0,                          /* vml_end_label */
    vml_textspan,
    0,				/* vml_resolve_color */
    vml_ellipse,
    vml_polygon,
    vml_bezier,
    vml_polyline,
    vml_comment,
    0,				/* vml_library_shape */
};

gvrender_features_t render_features_vml = {
    GVRENDER_Y_GOES_DOWN
        | GVRENDER_DOES_TRANSFORM
	| GVRENDER_DOES_LABELS
	| GVRENDER_DOES_MAPS
	| GVRENDER_DOES_TARGETS
	| GVRENDER_DOES_TOOLTIPS, /* flags */
    0.,                         /* default pad - graph units */
    vml_knowncolors,		/* knowncolors */
    sizeof(vml_knowncolors) / sizeof(char *),	/* sizeof knowncolors */
    RGBA_BYTE,			/* color_type */
};

gvdevice_features_t device_features_vml = {
    GVDEVICE_DOES_TRUECOLOR,	/* flags */
    {0.,0.},			/* default margin - points */
    {0.,0.},                    /* default page width, height - points */
    {96.,96.},			/* default dpi */
};

gvdevice_features_t device_features_vmlz = {
    GVDEVICE_DOES_TRUECOLOR
      | GVDEVICE_COMPRESSED_FORMAT,	/* flags */
    {0.,0.},			/* default margin - points */
    {0.,0.},                    /* default page width, height - points */
    {96.,96.},			/* default dpi */
};

gvplugin_installed_t gvrender_vml_types[] = {
    {FORMAT_VML, "vml", 1, &vml_engine, &render_features_vml},
    {0, NULL, 0, NULL, NULL}
};

gvplugin_installed_t gvdevice_vml_types[] = {
    {FORMAT_VML, "vml:vml", 1, NULL, &device_features_vml},
#if HAVE_LIBZ
    {FORMAT_VMLZ, "vmlz:vml", 1, NULL, &device_features_vmlz},
#endif
    {0, NULL, 0, NULL, NULL}
};

