%{
/*
 * This is a plug-in for GIMP.
 *
 * Generates clickable image maps.
 *
 * Copyright (C) 1998-2005 Maurits Rijk  lpeek.mrijk@consunet.nl
 *
 * 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 3 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, see <https://www.gnu.org/licenses/>.
 *
 */

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

#include <glib/gstdio.h>

#include <gtk/gtk.h>

#include "imap_circle.h"
#include "imap_file.h"
#include "imap_main.h"
#include "imap_polygon.h"
#include "imap_rectangle.h"
#include "imap_string.h"

extern int csim_lex(void);
extern int csim_restart(FILE *csim_in);
static void csim_error(char* s);
static gchar * unescape_text(gchar *input);

static enum {UNDEFINED, RECTANGLE, CIRCLE, POLYGON} current_type;
static Object_t *current_object;
static MapInfo_t *_map_info;

%}

%union {
  int val;
  double value;
  char *id;
}

%token<val> IMG SRC WIDTH HEIGHT BORDER USEMAP
%token<val> START_MAP END_MAP NAME AREA SHAPE COORDS ALT HREF NOHREF
%token<val> TARGET ONMOUSEOVER ONMOUSEOUT ONFOCUS ONBLUR
%token<val> AUTHOR DESCRIPTION BEGIN_COMMENT END_COMMENT
%token<value> FLOAT
%token<id> STRING

%type<val> integer_value

%%

csim_file	: image start_map comment_lines area_list end_map
		;

image		: '<' IMG SRC '=' STRING image_tags xhtml_close
		{
		   g_strreplace(&_map_info->image_name, $5);
		   g_free ($5);
		}
		;

image_tags	: /* Empty */
		| image_tags image_tag
		;

image_tag	: image_width
		| image_height
		| BORDER '=' integer_value {}
		| USEMAP '=' STRING { g_free ($3); }
		| ALT '=' STRING { g_free ($3); }
		;

image_width	: WIDTH '=' integer_value
		{
		   _map_info->old_image_width = $3;
		}
		;

image_height	: HEIGHT '=' integer_value
		{
		   _map_info->old_image_height = $3;
		}
		;

integer_value	: FLOAT
		{
		  $$ = (gint) $1;
		}
		| STRING
		{
		  $$ = (gint) g_ascii_strtod ($1, NULL);
		  g_free ($1);
		}
		;

start_map	: '<' START_MAP NAME '=' STRING '>'
		{
		   g_strreplace(&_map_info->title, $5);
		   g_free ($5);
		}
		;

comment_lines	: /* empty */
		| comment_lines comment_line
		;

comment_line	: author_line
		| description_line
		| real_comment
		;

real_comment	: BEGIN_COMMENT STRING END_COMMENT
		{
		  g_free ($2);
		}
		;

author_line	: AUTHOR STRING END_COMMENT
		{
		   g_strreplace(&_map_info->author, $2);
		   g_free ($2);
		}
		;

description_line: DESCRIPTION STRING END_COMMENT
		{
		   gchar *description;

		   description = g_strconcat(_map_info->description, $2, "\n",
					     NULL);
		   g_strreplace(&_map_info->description, description);
		   g_free ($2);
		}
		;

area_list	: /* empty */
		| area_list area
		;

area		: '<' AREA tag_list xhtml_close
		{
		   if (current_type != UNDEFINED)
		      add_shape(current_object);
		}
		;

xhtml_close	: '>'
		| '/' '>'
		;

tag_list	: /* Empty */
		| tag_list tag
		;

tag		: shape_tag
		| coords_tag
		| href_tag
		| nohref_tag
		| alt_tag
		| target_tag
		| onmouseover_tag
		| onmouseout_tag
		| onfocus_tag
		| onblur_tag
		;

shape_tag	: SHAPE '=' STRING
		{
		   if (!g_ascii_strcasecmp($3, "RECT")) {
		      current_object = create_rectangle(0, 0, 0, 0);
		      current_type = RECTANGLE;
		   } else if (!g_ascii_strcasecmp($3, "CIRCLE")) {
		      current_object = create_circle(0, 0, 0);
		      current_type = CIRCLE;
		   } else if (!g_ascii_strcasecmp($3, "POLY")) {
		      current_object = create_polygon(NULL);
		      current_type = POLYGON;
		   } else if (!g_ascii_strcasecmp($3, "DEFAULT")) {
		      current_type = UNDEFINED;
		   }
		   g_free ($3);
		}
		;

coords_tag	: COORDS '=' STRING
		{
		   char *p;
		   if (current_type == RECTANGLE) {
		      Rectangle_t *rectangle;

		      rectangle = ObjectToRectangle(current_object);
		      p = strtok($3, ",");
		      rectangle->x = atoi(p);
		      p = strtok(NULL, ",");
		      rectangle->y = atoi(p);
		      p = strtok(NULL, ",");
		      rectangle->width = atoi(p) - rectangle->x;
		      p = strtok(NULL, ",");
		      rectangle->height = atoi(p) - rectangle->y;
		   } else if (current_type == CIRCLE) {
		      Circle_t *circle;

		      circle = ObjectToCircle(current_object);
		      p = strtok($3, ",");
		      circle->x = atoi(p);
		      p = strtok(NULL, ",");
		      circle->y = atoi(p);
		      p = strtok(NULL, ",");
		      circle->r = atoi(p);
		   } else if (current_type == POLYGON) {
		      Polygon_t *polygon = ObjectToPolygon(current_object);
		      GList *points;
		      GdkPoint *point, *first;
		      gint x, y;

		      p = strtok($3, ",");
		      x = atoi(p);
		      p = strtok(NULL, ",");
		      y = atoi(p);
		      point = new_point(x, y);
		      points = g_list_append(NULL, (gpointer) point);

		      while(1) {
			 p = strtok(NULL, ",");
			 if (!p)
			    break;
			 x = atoi(p);
			 p = strtok(NULL, ",");
			 y = atoi(p);
			 point = new_point(x, y);
			 points = g_list_append(points, (gpointer) point);
		      }
		      /* Remove last point if duplicate */
		      first = (GdkPoint*) points->data;
		      polygon->points = points;
		      if (first->x == point->x && first->y == point->y)
			 polygon_remove_last_point(polygon);
		      polygon->points = points;
		   }

		   g_free ($3);
		}
		;

href_tag	: HREF '=' STRING
		{
		   if (current_type == UNDEFINED) {
		      g_strreplace(&_map_info->default_url, $3);
		   } else {
		      object_set_url(current_object, unescape_text($3));
		   }
		   g_free ($3);
		}
		;

nohref_tag	: NOHREF optional_value
		{
		}
		;

optional_value	: /* Empty */
		| '=' STRING
		{
		   g_free ($2);
		}
		;

alt_tag		: ALT '=' STRING
		{
		   object_set_comment(current_object, unescape_text($3));
		   g_free ($3);
		}
		;

target_tag	: TARGET '=' STRING
		{
		   object_set_target(current_object, unescape_text($3));
		   g_free ($3);
		}
		;

onmouseover_tag	: ONMOUSEOVER '=' STRING
		{
		   object_set_mouse_over(current_object, unescape_text($3));
		   g_free ($3);
		}
		;

onmouseout_tag	: ONMOUSEOUT '=' STRING
		{
		   object_set_mouse_out(current_object, unescape_text($3));
		   g_free ($3);
		}
		;

onfocus_tag	: ONFOCUS '=' STRING
		{
		   object_set_focus(current_object, unescape_text($3));
		   g_free ($3);
		}
		;

onblur_tag	: ONBLUR '=' STRING
		{
		   object_set_blur(current_object, unescape_text($3));
		   g_free ($3);
		}
		;

end_map		: '<' END_MAP '>'
		;

%%

static void
csim_error(char* s)
{
   extern FILE *csim_in;
   csim_restart(csim_in);
}

gboolean
load_csim (const char* filename)
{
  gboolean status;
  extern FILE *csim_in;
  csim_in = g_fopen(filename, "r");
  if (csim_in) {
    _map_info = get_map_info();
    status = !csim_parse();
    fclose(csim_in);
  } else {
    status = FALSE;
  }
  return status;
}

static gchar*
unescape_text (gchar *input)
{
 /*
  * We "unescape" simple things "in place", knowing that unescaped
  * strings always are shorter than the original input.
  *
  * It is a shame there is no g_markup_unescape_text() function, but
  * instead you have to create a full GMarkupParser/Context.
  */
  struct token {
    const char *escaped;
    const char  unescaped;
  };
  const struct token tab[] = {
    { "&quot;", '"'  },
    { "&apos;", '\'' },
    { "&amp;",  '&'  },
    { "&lt;",   '<'  },
    { "&gt;",   '>'  }
  };

  size_t i;
  for (i = 0; i < (sizeof tab / sizeof tab[0]); i++)
    {
      const size_t escaped_len = strlen (tab[i].escaped);
      char *p;

      /* FIXME: The following code does not perform a UTF-8 substring
         search. */
      for (p = strstr (input, tab[i].escaped);
           p != NULL;
           p = strstr (p, tab[i].escaped))
        {
          size_t copy_len;
          *p++ = tab[i].unescaped;
          copy_len = strlen (p) - escaped_len + 2;
          memmove (p, p + escaped_len - 1, copy_len);
          if (*p == 0)
            break;
        }
    }

  return input;
}
