/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998 Alexander Larsson
 *
 * hpgl.c -- HPGL export plugin for dia
 * Copyright (C) 2000, Hans Breuer, <Hans@Breuer.Org>
 *   based on dummy.c 
 *   based on CGM plug-in Copyright (C) 1999 James Henstridge. 
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * ToDo:
 * - move draw_ellipse_by_arc into libdia to make it available for other
 *   interested renderers ?
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <glib.h>
#include <errno.h>

#include "intl.h"
#include "message.h"
#include "geometry.h"
#include "diarenderer.h"
#include "filter.h"
#include "plug-ins.h"

/* #DEFINE DEBUG_HPGL */

/* format specific */
#define HPGL_MAX_PENS 8

#define PEN_HAS_COLOR (1 << 0) 
#define PEN_HAS_WIDTH (1 << 1) 

/* GObject boiler plate */
#define HPGL_TYPE_RENDERER           (hpgl_renderer_get_type ())
#define HPGL_RENDERER(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), HPGL_TYPE_RENDERER, HpglRenderer))
#define HPGL_RENDERER_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST ((klass), HPGL_TYPE_RENDERER, HpglRendererClass))
#define HPGL_IS_RENDERER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), HPGL_TYPE_RENDERER))
#define HPGL_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), HPGL_TYPE_RENDERER, HpglRendererClass))

GType hpgl_renderer_get_type (void) G_GNUC_CONST;

typedef struct _HpglRenderer HpglRenderer;
typedef struct _HpglRendererClass HpglRendererClass;

struct _HpglRenderer
{
  DiaRenderer parent_instance;

  FILE *file;

  /*
   * The number of pens is limited. This is used to select one.
   */
  struct {
    Color color;
    float width;
    int   has_it;
  } pen[HPGL_MAX_PENS];
  int last_pen;
  real dash_length;
  real font_height;

  Point size;  /* extent size */
  real scale;
  real offset; /* in dia units */
};

struct _HpglRendererClass
{
  DiaRendererClass parent_class;
};

#ifdef DEBUG_HPGL
#  define DIAG_NOTE(action) action
#else
#  define DIAG_NOTE(action)
#endif

/* hpgl helpers */
static void
hpgl_select_pen(HpglRenderer* renderer, Color* color, real width)
{
    int nPen = 0;
    int i;
    /* look if this pen is defined already */
    if (0.0 != width) {
        /* either width ... */
        for (i = 0; i < HPGL_MAX_PENS; i++) {
            if (!(renderer->pen[i].has_it & PEN_HAS_WIDTH)) {
                nPen = i;
                break;
            }
            if (width == renderer->pen[i].width) {
                nPen = i;
                break;
            }
        }
    }
 
    if (NULL != color) {
        for (i = nPen; i < HPGL_MAX_PENS; i++) {
            if (!(renderer->pen[i].has_it & PEN_HAS_COLOR)) {
                nPen = i;
                break;
            }
            if (   (color->red == renderer->pen[i].color.red)
                && (color->green == renderer->pen[i].color.green)
                && (color->blue == renderer->pen[i].color.blue)) {
                nPen = i;
                break;
            }
        }
    }
    /* "create" new pen ... */
    if ((nPen < HPGL_MAX_PENS) && (-1 < nPen)) {
        if (0.0 != width) {
            renderer->pen[nPen].width = width;
            renderer->pen[nPen].has_it |= PEN_HAS_WIDTH;
        }
        if (NULL != color) {
            renderer->pen[nPen].color = *color;
            renderer->pen[nPen].has_it |= PEN_HAS_COLOR;
        }
    }
    /* ... or use best fitting one */
    else if (-1 == nPen) {
        nPen = 0; /* TODO: */
    }

    if (renderer->last_pen != nPen)
        fprintf(renderer->file, "SP%d;\n", nPen+1);
    renderer->last_pen = nPen;
}

static int
hpgl_scale(HpglRenderer *renderer, real val)
{
    return (int)((val + renderer->offset) * renderer->scale);
}

/* render functions */ 
static void
begin_render(DiaRenderer *object)
{
    HpglRenderer *renderer = HPGL_RENDERER (object);
    int i;

    DIAG_NOTE(g_message("begin_render"));

    /* initialize pens */
    for (i = 0; i < HPGL_MAX_PENS; i++) {
        renderer->pen[i].color = color_black;
        renderer->pen[i].width = 0.0;
        renderer->pen[i].has_it = 0;
    }
    renderer->last_pen = -1;
    renderer->dash_length = 0.0;
}

static void
end_render(DiaRenderer *object)
{
    HpglRenderer *renderer = HPGL_RENDERER (object);

    DIAG_NOTE(g_message("end_render"));
    fclose(renderer->file);
}

static void
set_linewidth(DiaRenderer *object, real linewidth)
{  
    HpglRenderer *renderer = HPGL_RENDERER (object);

    DIAG_NOTE(g_message("set_linewidth %f", linewidth));

    hpgl_select_pen(renderer, NULL, linewidth);
}

static void
set_linecaps(DiaRenderer *object, LineCaps mode)
{
    DIAG_NOTE(g_message("set_linecaps %d", mode));

    switch(mode) {
    case LINECAPS_BUTT:
	break;
    case LINECAPS_ROUND:
	break;
    case LINECAPS_PROJECTING:
	break;
    default:
	message_error("HpglRenderer: Unsupported fill mode specified!\n");
    }
}

static void
set_linejoin(DiaRenderer *object, LineJoin mode)
{
    DIAG_NOTE(g_message("set_join %d", mode));

    switch(mode) {
    case LINEJOIN_MITER:
	break;
    case LINEJOIN_ROUND:
	break;
    case LINEJOIN_BEVEL:
	break;
    default:
	message_error("HpglRenderer : Unsupported fill mode specified!\n");
    }
}

static void
set_linestyle(DiaRenderer *object, LineStyle mode)
{
    HpglRenderer *renderer = HPGL_RENDERER (object);

    DIAG_NOTE(g_message("set_linestyle %d", mode));

    /* line type */
    switch (mode) {
    case LINESTYLE_SOLID:
      fprintf(renderer->file, "LT;\n");
      break;
    case LINESTYLE_DASHED:
      if (renderer->dash_length > 0.5) /* ??? unit of dash_lenght ? */
          fprintf(renderer->file, "LT2;\n"); /* short */
      else
          fprintf(renderer->file, "LT3;\n"); /* long */
      break;
    case LINESTYLE_DASH_DOT:
      fprintf(renderer->file, "LT4;\n");
      break;
    case LINESTYLE_DASH_DOT_DOT:
      fprintf(renderer->file, "LT5;\n"); /* ??? Mittellinie? */
      break;
    case LINESTYLE_DOTTED:
      fprintf(renderer->file, "LT1;\n");
      break;
    default:
	message_error("HpglRenderer : Unsupported fill mode specified!\n");
    }
}

static void
set_dashlength(DiaRenderer *object, real length)
{  
    HpglRenderer *renderer = HPGL_RENDERER (object);

    DIAG_NOTE(diag_note("set_dashlength %f", length));

    /* dot = 20% of len */
    renderer->dash_length = length;
}

static void
set_fillstyle(DiaRenderer *object, FillStyle mode)
{
    DIAG_NOTE(g_message("set_fillstyle %d", mode));

    switch(mode) {
    case FILLSTYLE_SOLID:
	break;
    default:
	message_error("HpglRenderer : Unsupported fill mode specified!\n");
    }
}

static void
set_font(DiaRenderer *object, DiaFont *font, real height)
{
    HpglRenderer *renderer = HPGL_RENDERER (object);

    DIAG_NOTE(g_message("set_font %f", height));
    renderer->font_height = height;
}

/* Need to translate coord system:
 * 
 *   Dia x,y -> Hpgl x,-y
 *
 * doing it before scaling.
 */
static void
draw_line(DiaRenderer *object, 
          Point *start, Point *end, 
          Color *line_colour)
{
    HpglRenderer *renderer = HPGL_RENDERER (object);

    DIAG_NOTE(g_message("draw_line %f,%f -> %f, %f", 
              start->x, start->y, end->x, end->y));
    hpgl_select_pen(renderer, line_colour, 0.0);
    fprintf (renderer->file, 
             "PU%d,%d;PD%d,%d;\n",
             hpgl_scale(renderer, start->x), hpgl_scale(renderer, -start->y),
             hpgl_scale(renderer, end->x), hpgl_scale(renderer, -end->y));
}

static void
draw_polyline(DiaRenderer *object, 
	      Point *points, int num_points, 
	      Color *line_colour)
{
    HpglRenderer *renderer = HPGL_RENDERER (object);
    int i;

    DIAG_NOTE(g_message("draw_polyline n:%d %f,%f ...", 
              num_points, points->x, points->y));

    g_return_if_fail(1 < num_points);

    hpgl_select_pen(renderer, line_colour, 0.0);
    fprintf (renderer->file, "PU%d,%d;PD;PA",
             hpgl_scale(renderer, points[0].x),
             hpgl_scale(renderer, -points[0].y));
    /* absolute movement */
    for (i = 1; i < num_points-1; i++)
        fprintf(renderer->file, "%d,%d,",
                hpgl_scale(renderer, points[i].x),
                hpgl_scale(renderer, -points[i].y));
    i = num_points - 1;
    fprintf(renderer->file, "%d,%d;\n",
            hpgl_scale(renderer, points[i].x),
            hpgl_scale(renderer, -points[i].y));
}

static void
draw_polygon(DiaRenderer *object, 
	     Point *points, int num_points, 
	     Color *line_colour)
{
    DIAG_NOTE(g_message("draw_polygon n:%d %f,%f ...", 
              num_points, points->x, points->y));
    draw_polyline(object,points,num_points,line_colour);
    /* last to first */
    draw_line(object, &points[num_points-1], &points[0], line_colour);
}

static void
fill_polygon(DiaRenderer *object, 
	     Point *points, int num_points, 
	     Color *colour)
{
    DIAG_NOTE(g_message("fill_polygon n:%d %f,%f ...", 
              num_points, points->x, points->y));
    draw_polyline(object,points,num_points,colour);
}

static void
draw_rect(DiaRenderer *object, 
	  Point *ul_corner, Point *lr_corner,
	  Color *colour)
{
    HpglRenderer *renderer = HPGL_RENDERER (object);

    DIAG_NOTE(g_message("draw_rect %f,%f -> %f,%f", 
              ul_corner->x, ul_corner->y, lr_corner->x, lr_corner->y));
    hpgl_select_pen(renderer, colour, 0.0);
    fprintf (renderer->file, "PU%d,%d;PD;EA%d,%d;\n",
             hpgl_scale(renderer, ul_corner->x),
             hpgl_scale(renderer, -ul_corner->y),
             hpgl_scale(renderer, lr_corner->x),
             hpgl_scale(renderer, -lr_corner->y));
}

static void
fill_rect(DiaRenderer *object, 
	  Point *ul_corner, Point *lr_corner,
	  Color *colour)
{
    DIAG_NOTE(g_message("fill_rect %f,%f -> %f,%f", 
              ul_corner->x, ul_corner->y, lr_corner->x, lr_corner->y));
#if 0
    HpglRenderer *renderer = HPGL_RENDERER (object);

    hpgl_select_pen(renderer, colour, 0.0);
    fprintf (renderer->file, "PU%d,%d;PD;RA%d,%d;\n",
             hpgl_scale(renderer, ul_corner->x),
             hpgl_scale(renderer, -ul_corner->y),
             hpgl_scale(renderer, lr_corner->x),
             hpgl_scale(renderer, -lr_corner->y));
#else
    /* the fill modes aren't really compatible ... */
   draw_rect(object, ul_corner, lr_corner, colour);
#endif
}

static void
draw_arc(DiaRenderer *object, 
	 Point *center,
	 real width, real height,
	 real angle1, real angle2,
	 Color *colour)
{
    HpglRenderer *renderer = HPGL_RENDERER (object);
    Point start;

    DIAG_NOTE(g_message("draw_arc %fx%f <%f,<%f", 
              width, height, angle1, angle2));
    hpgl_select_pen(renderer, colour, 0.0);

    /* move to start point */
    start.x = center->x + (width / 2.0)  * cos((M_PI / 180.0) * angle1);  
    start.y = - center->y + (height / 2.0) * sin((M_PI / 180.0) * angle1);
    fprintf (renderer->file, "PU%d,%d;PD;",
             hpgl_scale(renderer, start.x),
             hpgl_scale(renderer, start.y));
    /* Arc Absolute - around center */
    fprintf (renderer->file, "AA%d,%d,%d;",
             hpgl_scale(renderer, center->x),
             hpgl_scale(renderer, - center->y),
             (int)floor(360.0 - angle1 + angle2));
}

static void
fill_arc(DiaRenderer *object, 
	 Point *center,
	 real width, real height,
	 real angle1, real angle2,
	 Color *colour)
{
    HpglRenderer *renderer = HPGL_RENDERER (object);

    DIAG_NOTE(g_message("fill_arc %fx%f <%f,<%f", 
              width, height, angle1, angle2));
    g_assert (width == height);

    /* move to center */
    fprintf (renderer->file, "PU%d,%d;PD;",
             hpgl_scale(renderer, center->x),
             hpgl_scale(renderer, -center->y));
    /* Edge Wedge */
    fprintf (renderer->file, "EW%d,%d,%d;",
             hpgl_scale(renderer, width),
             (int)angle1, (int)(angle2-angle1));
}

/* may go into lib/diarenderer.c if another renderer would be interested */
/* Draw an ellipse approximation consisting out of
 * four arcs. 
 */
static void
draw_ellipse_by_arc (DiaRenderer *renderer, 
                     Point *center,
                     real width, real height,
                     Color *colour)
{
  real a, b, e, d, alpha, c, x, y;
  real g, gamma, r;
  Point pt;
  real angle;

  a = width / 2;
  b = height / 2;
  e = sqrt(a*a - b*b);

  alpha = 0.25*M_PI - asin((e/a) * sin(0.75*M_PI));
  d = 2*a*sin(alpha);

  c = (sin(0.25*M_PI) * (2*e + d)) / sin(0.75*M_PI - alpha);

  y = c * sin (alpha);
  x = c * cos (alpha) - e;

  /* draw arcs */
  g = sqrt((a-x)*(a-x) + y*y);
  gamma = acos((a-x)/g);
  r = (sin(gamma) * g) / sin(M_PI-2*gamma);

  pt.y = center->y;
  angle = (180 * (M_PI-2*gamma)) / M_PI;
  pt.x = center->x + a - r; /* right */
  draw_arc(renderer, &pt, 2*r, 2*r, 360-angle, angle, colour);
  pt.x = center->x - a + r; /* left */
  draw_arc(renderer, &pt, 2*r, 2*r, 180-angle, 180+angle, colour);


  g = sqrt((b-y)*(b-y) + x*x);
  gamma = acos((b-y)/g);
  r = (sin(gamma) * g) / sin(M_PI-2*gamma);

  pt.x = center->x;
  angle = (180 * (M_PI-2*gamma)) / M_PI;
  pt.y = center->y - b + r; /* top */
  draw_arc(renderer, &pt, 2*r, 2*r, 90-angle, 90+angle, colour);
  pt.y = center->y + b - r; /* bottom */
  draw_arc(renderer, &pt, 2*r, 2*r, 270-angle, 270+angle, colour);
}

static void
draw_ellipse(DiaRenderer *object, 
	     Point *center,
	     real width, real height,
	     Color *colour)
{
  HpglRenderer *renderer = HPGL_RENDERER (object);

  DIAG_NOTE(g_message("draw_ellipse %fx%f center @ %f,%f", 
            width, height, center->x, center->y));

  if (width != height)
    {
      draw_ellipse_by_arc(object, center, width, height, colour);
    }  
  else
    {
      hpgl_select_pen(renderer, colour, 0.0);

      fprintf (renderer->file, "PU%d,%d;CI%d;\n",
               hpgl_scale(renderer, center->x),
               hpgl_scale(renderer, -center->y),
               hpgl_scale(renderer, width / 2.0));
    }
}

static void
fill_ellipse(DiaRenderer *object, 
	     Point *center,
	     real width, real height,
	     Color *colour)
{
    DIAG_NOTE(g_message("fill_ellipse %fx%f center @ %f,%f", 
              width, height, center->x, center->y));
}

static void
draw_string(DiaRenderer *object,
	    const char *text,
	    Point *pos, Alignment alignment,
	    Color *colour)
{
    HpglRenderer *renderer = HPGL_RENDERER (object);
    real width, height;

    DIAG_NOTE(g_message("draw_string %f,%f %s", 
              pos->x, pos->y, text));

    /* set position */
    fprintf(renderer->file, "PU%d,%d;",
            hpgl_scale(renderer, pos->x), hpgl_scale(renderer, -pos->y));

    switch (alignment) {
    case ALIGN_LEFT:
        fprintf (renderer->file, "LO1;\n");
	break;
    case ALIGN_CENTER:
        fprintf (renderer->file, "LO4;\n");
	break;
    case ALIGN_RIGHT:
        fprintf (renderer->file, "LO7;\n");
	break;
    }
    hpgl_select_pen(renderer,colour,0.0);

#if 0
    /*
     * SR - Relative Character Size >0.0 ... 127.999
     *    set the capital letter box width and height as a percentage of
     *    P2X-P1X  and P2Y-P1Y
     */
    height = (127.999 * renderer->font_height * renderer->scale) / renderer->size.y; 
    width  = 0.75 * height; /* FIXME: */
    fprintf(renderer->file, "SR%d.%03d,%d.%03d;", 
            (int)width, (int)((width * 1000) % 1000),
            (int)height, (int)((height * 1000) % 1000));
#else
    /*
     * SI - character size absolute
     *    size needed in centimeters
     */
    width = renderer->font_height * renderer->scale * 0.75 * 0.0025;
    height = renderer->font_height * renderer->scale * 0.0025;
    fprintf(renderer->file, "SI%d.%03d,%d.%03d;", 
            (int)width, ((int)(width * 1000) % 1000),
            (int)height, ((int)(height * 1000) % 1000));
#endif
    fprintf(renderer->file, "DT\003;" /* Terminator */
            "LB%s\003;\n", text);
}

static void
draw_image(DiaRenderer *object,
	   Point *point,
	   real width, real height,
	   DiaImage image)
{
    DIAG_NOTE(g_message("draw_image %fx%f @%f,%f", 
              width, height, point->x, point->y));
    g_warning("HPGL: images unsupported!");
}

/* overwrite vtable */
static void hpgl_renderer_class_init (HpglRendererClass *klass);

static gpointer parent_class = NULL;

GType
hpgl_renderer_get_type (void)
{
  static GType object_type = 0;

  if (!object_type)
    {
      static const GTypeInfo object_info =
      {
        sizeof (HpglRendererClass),
        (GBaseInitFunc) NULL,
        (GBaseFinalizeFunc) NULL,
        (GClassInitFunc) hpgl_renderer_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data */
        sizeof (HpglRenderer),
        0,              /* n_preallocs */
	NULL            /* init */
      };

      object_type = g_type_register_static (DIA_TYPE_RENDERER,
                                            "HpglRenderer",
                                            &object_info, 0);
    }
  
  return object_type;
}

static void
hpgl_renderer_finalize (GObject *object)
{
  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
hpgl_renderer_class_init (HpglRendererClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  DiaRendererClass *renderer_class = DIA_RENDERER_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  object_class->finalize = hpgl_renderer_finalize;

  /* renderer members */
  renderer_class->begin_render = begin_render;
  renderer_class->end_render   = end_render;

  renderer_class->set_linewidth  = set_linewidth;
  renderer_class->set_linecaps   = set_linecaps;
  renderer_class->set_linejoin   = set_linejoin;
  renderer_class->set_linestyle  = set_linestyle;
  renderer_class->set_dashlength = set_dashlength;
  renderer_class->set_fillstyle  = set_fillstyle;

  renderer_class->set_font  = set_font;

  renderer_class->draw_line    = draw_line;
  renderer_class->fill_polygon = fill_polygon;
  renderer_class->draw_rect    = draw_rect;
  renderer_class->fill_rect    = fill_rect;
  renderer_class->draw_arc     = draw_arc;
  renderer_class->fill_arc     = fill_arc;
  renderer_class->draw_ellipse = draw_ellipse;
  renderer_class->fill_ellipse = fill_ellipse;

  renderer_class->draw_string  = draw_string;
  renderer_class->draw_image   = draw_image;

  /* medium level functions */
  renderer_class->draw_rect = draw_rect;
  renderer_class->draw_polyline  = draw_polyline;
  renderer_class->draw_polygon   = draw_polygon;
}

/* plug-in interface : export function */
static void
export_data(DiagramData *data, const gchar *filename, 
            const gchar *diafilename, void* user_data)
{
    HpglRenderer *renderer;
    FILE *file;
    Rectangle *extent;
    real width, height;

    file = fopen(filename, "w"); /* "wb" for binary! */

    if (file == NULL) {
	message_error(_("Can't open output file %s: %s\n"), 
		      dia_message_filename(filename), strerror(errno));
	return;
    }

    renderer = g_object_new(HPGL_TYPE_RENDERER, NULL);

    renderer->file = file;

    extent = &data->extents;

    /* use extents */
    DIAG_NOTE(g_message("export_data extents %f,%f -> %f,%f", 
              extent->left, extent->top, extent->right, extent->bottom));

    width  = extent->right - extent->left;
    height = extent->bottom - extent->top;
    renderer->scale = 0.001;
    if (width > height)
        while (renderer->scale * width < 3276.7) renderer->scale *= 10.0;
    else
        while (renderer->scale * height < 3276.7) renderer->scale *= 10.0;
    renderer->offset = 0.0; /* just to have one */

    renderer->size.x = width * renderer->scale;
    renderer->size.y = height * renderer->scale;
#if 0
    /* OR: set page size and scale */
    fprintf(renderer->file, "PS0;SC%d,%d,%d,%d;\n",
            hpgl_scale(renderer, extent->left),
            hpgl_scale(renderer, extent->right),
            hpgl_scale(renderer, extent->bottom),
            hpgl_scale(renderer, extent->top));
#endif
    data_render(data, DIA_RENDERER(renderer), NULL, NULL, NULL);

    g_object_unref(renderer);
}

static const gchar *extensions[] = { "plt", "hpgl", NULL };
static DiaExportFilter my_export_filter = {
    N_("HP Graphics Language"),
    extensions,
    export_data
};


/* --- dia plug-in interface --- */
static gboolean
_plugin_can_unload (PluginInfo *info)
{
    return TRUE;
}

static void
_plugin_unload (PluginInfo *info)
{
    filter_unregister_export(&my_export_filter);
}

DIA_PLUGIN_CHECK_INIT

PluginInitResult
dia_plugin_init(PluginInfo *info)
{
    if (!dia_plugin_info_init(info, "HPGL",
                              _("HP Graphics Language export filter"),
			      _plugin_can_unload,
                              _plugin_unload))
	return DIA_PLUGIN_INIT_ERROR;

    filter_register_export(&my_export_filter);

    return DIA_PLUGIN_INIT_OK;
}
