/*
 * EasyChem
 * A program for creating and editing molecular formulas.
 *
 * Copyright (C) 2003, 2004, 2005 François-Xavier Coudert
 * 
 * Distributed under the General Public Licence (GPL).
 * See file COPYING in the source distribution for more details.
 *
 */

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <gtk/gtk.h>
#include <pango/pango.h>

#include "common.h"
#include "auxi.h"
#include "drawing.h"


/*****************************************
 *                                       *
 *           O R N A M E N T S           *
 *                                       *
 *****************************************/

/* Draw only one ornaments, around point (x,y) and rotated with angle t */
void
draw_one_ornament (struct Ornament *orn, const LLINT xc, const LLINT yc,
                   double t, GdkGC *gc, GdkPixmap *pix, const double zoom)
{
  switch (orn->type)
  {
  case ORN_LONE_PAIR:
    gdk_draw_line (pix, gc,
      U2S(xc - ORN_DEF_SIZE * orn->size / 2 * sin (t)),
      U2S(yc - ORN_DEF_SIZE * orn->size / 2 * cos (t)),
      U2S(xc + ORN_DEF_SIZE * orn->size / 2 * sin (t)),
      U2S(yc + ORN_DEF_SIZE * orn->size / 2 * cos (t)));
    break;
    
  case ORN_GAP:
    gdk_draw_rectangle (pix, gc, FALSE,
      U2S(xc - (orn->size * ORN_DEF_SIZE) / 2),
      U2S(yc - (orn->size * ORN_DEF_SIZE) / 2),
      U2S(orn->size * ORN_DEF_SIZE),
      U2S(orn->size * ORN_DEF_SIZE));
    break;
    
  case ORN_GAP2:
    gdk_draw_rectangle (pix, gc, FALSE,
      U2S(xc - (orn->size * ORN_DEF_SIZE) / 2),
      U2S(yc - (orn->size * ORN_DEF_SIZE) / 4),
      U2S(orn->size * ORN_DEF_SIZE),
      U2S(orn->size * ORN_DEF_SIZE / 2));
    break;
    
  case ORN_RADICAL:
    gdk_draw_arc (pix, gc, TRUE,
      U2S(xc - (orn->size * ORN_DEF_SIZE * 0.6) / 2),
      U2S(yc - (orn->size * ORN_DEF_SIZE * 0.6) / 2),
      U2S(orn->size * ORN_DEF_SIZE * 0.6), 
      U2S(orn->size * ORN_DEF_SIZE * 0.6), 0, 23040);
    break;

  case ORN_LONE_PAIR_DOTS:
    gdk_draw_arc (pix, gc, TRUE,
      U2S(xc - ORN_DEF_SIZE * orn->size * 1 / 4 * sin(t)),
      U2S(yc - ORN_DEF_SIZE * orn->size * 1 / 4 * cos(t)),
      U2S(orn->size * ORN_DEF_SIZE * 0.1),
      U2S(orn->size * ORN_DEF_SIZE * 0.1), 0, 23040);
    gdk_draw_arc (pix, gc, TRUE,
      U2S(xc + ORN_DEF_SIZE * orn->size * 1 / 4 * sin(t)),
      U2S(yc + ORN_DEF_SIZE * orn->size * 1 / 4 * cos(t)),
      U2S(orn->size * ORN_DEF_SIZE * 0.1),
      U2S(orn->size * ORN_DEF_SIZE * 0.1), 0, 23040);
    break;

  case ORN_LONE_ELECTRON:
    gdk_draw_arc (pix, gc, TRUE,
      U2S(xc - ORN_DEF_SIZE * orn->size * 1 / 4 * sin(t)),
      U2S(yc - ORN_DEF_SIZE * orn->size * 1 / 4 * cos(t)),
      U2S(orn->size * ORN_DEF_SIZE * 0.1),
      U2S(orn->size * ORN_DEF_SIZE * 0.1), 0, 23040);
    break;
  } 
} 


/* To draw a whole list of ornaments */
void
draw_ornaments (struct Ornament *orn, const LLINT x, const LLINT y,
                const double zoom, GdkGC *gc, GdkPixmap *pix)
{
  struct Ornament *current = orn;
  unsigned int i;
  double tc, t_first, t, t_spacing;

  while (current != NULL)
  {
    tc = current->angle * G_PI / 180;
    t_spacing = current->spacing * G_PI / 180;
    t_first = tc - ((current->number - 1) * t_spacing) / 2;

    for (i = 0; i < current->number; i++)
    {
      t = t_first + i * t_spacing;
      draw_one_ornament (current, x + ORN_DEF_DIST * current->dist * sin (t),
                         y - ORN_DEF_DIST * current->dist * cos (t),
                         G_PI_2 - t, gc, pix, zoom);
    }

    current = current->next;
  }
}



/*****************************************
 *                                       *
 *     H A N D L I N G       T E X T     *
 *                                       *
 *****************************************/

#define FONT_SIZE       20000

void
pango_layout_set_rich_text (PangoLayout * p_layout, const gchar * text,
                            GdkColor * color, const int mode,
                            const double size, const double zoom)
{
  gchar *buff;

  if (mode == MODE_ORNAMENT)
  {
    buff =
      g_strdup_printf ("<span face=\"Serif\" size=\"%d\"><span foreground=\"#7F7F7F\">%s</span></span>",
                             (int) (FONT_SIZE * size * zoom), text);
  }
  else
  {
  if (color == NULL)
  buff =
    g_strdup_printf ("<span face=\"Serif\" size=\"%d\"><span foreground=\"#0000FF\">%s</span></span>",
                     (int) (FONT_SIZE * size * zoom), text);
  else
  {
    buff =
      g_strdup_printf ("<span face=\"Serif\" size=\"%d\"><span foreground=\"#%.2x%.2x%.2x\">%s</span></span>",
                     (int) (FONT_SIZE * size * zoom),
                     color->red / 256, color->green / 256, color->blue / 256,
                     text);
  }
  }

  pango_layout_set_markup (p_layout, buff, -1);
  g_free (buff);
}


void
adjust_atom (struct Bond *atom, GtkWidget * area, const int mode,
             const double zoom)
{
  PangoLayout *p_layout;
  PangoRectangle rect, rect2;
  int width, height, w2, h2;

  if (atom == NULL)
    return;

/* The x2 and y2 defined here are not the final ones, but the size of the
 * pango layout */
  p_layout = gtk_widget_create_pango_layout (area, "");
  pango_layout_set_rich_text (p_layout, atom->pango, NULL, mode, atom->width,
                              zoom);
  pango_layout_get_pixel_size (p_layout, &width, &height);
  width = S2U (width);
  height = S2U (height);
  g_object_unref (p_layout);

  p_layout = gtk_widget_create_pango_layout (area, "");
  switch (atom->type)
    {
    case BOND_ATOM:
      pango_layout_set_rich_text (p_layout, atom->pango, NULL,
	                          mode, atom->width, zoom);
      pango_layout_get_extents (p_layout, &rect, NULL);
      atom->x1 = atom->x3 - width / 2;
      atom->y1 = atom->y3 - height / 2;
      atom->x4 = atom->x3 - S2U (rect.width / PANGO_SCALE) / 2;
      atom->y4 = atom->y3 - S2U (rect.height / PANGO_SCALE) / 2;
      atom->x2 = S2U (rect.width / PANGO_SCALE);
      atom->y2 = S2U (rect.height / PANGO_SCALE);
      break;

    case BOND_GROUP_L:
      pango_layout_set_rich_text (p_layout, atom->pango_left, NULL,
	                          mode, atom->width, zoom);
      pango_layout_get_extents (p_layout, &rect, NULL);
      pango_layout_get_pixel_size (p_layout, &w2, &h2);
      atom->x1 = atom->x3 - S2U (w2) / 2;
      atom->y1 = atom->y3 - S2U (h2) / 2;
      atom->x4 = atom->x3 - S2U (rect.width / PANGO_SCALE) / 2;
      atom->y4 = atom->y3 - S2U (rect.height / PANGO_SCALE) / 2;
      pango_layout_set_rich_text (p_layout, atom->pango, NULL,
	                          mode, atom->width, zoom);
      pango_layout_get_extents (p_layout, &rect, NULL);
      atom->x2 = S2U (rect.width / PANGO_SCALE);
      atom->y2 = S2U (rect.height / PANGO_SCALE);
      break;

    case BOND_GROUP_R:
      pango_layout_set_rich_text (p_layout, atom->pango_right, NULL,
	                          mode, atom->width, zoom);
      pango_layout_get_extents (p_layout, &rect, NULL);
      pango_layout_get_pixel_size (p_layout, &w2, &h2);
      atom->x1 = atom->x3 - width + S2U (w2) / 2;
      atom->y1 = atom->y3 - height + S2U (h2) / 2;
      pango_layout_set_rich_text (p_layout, atom->pango, NULL, mode,
	                          atom->width, zoom);
      pango_layout_get_extents (p_layout, &rect2, NULL);
      atom->x4 = atom->x3 - S2U (rect2.width / PANGO_SCALE)
        + S2U (rect.width / PANGO_SCALE) / 2;
      atom->y4 = atom->y3 - S2U (rect2.height / PANGO_SCALE)
        + S2U (rect2.height / PANGO_SCALE) / 2;
/*                 ^
 * This rect2.height should be a rect.height, but it doesn't work, and
 * the approximation is often quite good :) */
      atom->x2 = S2U (rect2.width / PANGO_SCALE);
      atom->y2 = S2U (rect2.height / PANGO_SCALE);
      break;
    }

  g_object_unref (p_layout);
}


void
adjust_all_atom_in_list (struct Bond *list, GtkWidget * area, const int mode,
                         const double zoom)
{
  struct Bond * current = list;

  while (current != NULL)
  {
    if (BOND_HAS_TEXT (current))
      adjust_atom (current, area, mode, zoom);
    if (current->type == BOND_GROUP)
      adjust_all_atom_in_list (current->group_members, area, mode, zoom);
    current = current->next;
  }
}



/*****************************************
 *                                       *
 *        D R A W     B O N D S          *
 *                                       *
 *****************************************/

#define DOUBLE_SHIFT    (0.04 * BOND_LENGTH * current->width)
#define TRIPLE_SHIFT    (0.06 * BOND_LENGTH * current->width)
#define STEREO_SHIFT    (0.08 * BOND_LENGTH * current->width)
#define ARROW_SHIFT     (0.12 * BOND_LENGTH * current->width)
#define ARROW2_SHIFT    (0.24 * BOND_LENGTH * current->width)

/* We draw the bonds */
void
draw_bond_list (struct Bond *list, const int recursive, const int selected,
                GtkWidget * area, GdkPixmap *pix, const int mode,
		const double zoom)
{
  PangoLayout *p_layout;
  GdkGC *gc;
  GdkPoint points[4];
  int i, j, dir;
  LLINT dx, dy;
  double x = 0, y = 0, r, cx, cy;
  struct Bond *current;

  if (!recursive)
    gdk_draw_rectangle (pix, area->style->white_gc, TRUE, 0, 0,
			area->allocation.width, area->allocation.height);

  current = list;
  if (current != NULL)
    while (current != NULL)
      {
	if (mode == MODE_ORNAMENT)
	  gc = gc_gray;
	else
	{
	  if ((current->selected & SEL_YES) || (selected & SEL_YES))
	    gc = gc_sel;
	  else
	    {
	      gc = gdk_gc_new (area->window);
	      gdk_gc_set_rgb_fg_color (gc, &(current->color));
	    }
	}

	switch (current->type)
	  {
	  case BOND_SIMPLE:
	    gdk_draw_line (pix, gc,
			   U2S (current->x1), U2S (current->y1),
			   U2S (current->x2), U2S (current->y2));
	    break;

	  case BOND_DOUBLE:
	    r = hypot ((double) current->x2 - (double) current->x1,
		       (double) current->y2 - (double) current->y1);
	    x = ((double) current->x2 - (double) current->x1) / r;
	    y = ((double) current->y2 - (double) current->y1) / r;
	    gdk_draw_line (pix, gc,
			   U2S (current->x1 - DOUBLE_SHIFT * y),
			   U2S (current->y1 + DOUBLE_SHIFT * x),
			   U2S (current->x2 - DOUBLE_SHIFT * y),
			   U2S (current->y2 + DOUBLE_SHIFT * x));
	    gdk_draw_line (pix, gc,
			   U2S (current->x1 + DOUBLE_SHIFT * y),
			   U2S (current->y1 - DOUBLE_SHIFT * x),
			   U2S (current->x2 + DOUBLE_SHIFT * y),
			   U2S (current->y2 - DOUBLE_SHIFT * x));
	    break;

	  case BOND_TRIPLE:
	    r = hypot ((double) current->x2 - (double) current->x1,
		       (double) current->y2 - (double) current->y1);
	    x = ((double) current->x2 - (double) current->x1) / r;
	    y = ((double) current->y2 - (double) current->y1) / r;
	    gdk_draw_line (pix, gc,
			   U2S (current->x1 - TRIPLE_SHIFT * y),
			   U2S (current->y1 + TRIPLE_SHIFT * x),
			   U2S (current->x2 - TRIPLE_SHIFT * y),
			   U2S (current->y2 + TRIPLE_SHIFT * x));
	    gdk_draw_line (pix, gc,
			   U2S (current->x1 + TRIPLE_SHIFT * y),
			   U2S (current->y1 - TRIPLE_SHIFT * x),
			   U2S (current->x2 + TRIPLE_SHIFT * y),
			   U2S (current->y2 - TRIPLE_SHIFT * x));
	    gdk_draw_line (pix, gc,
			   U2S (current->x1), U2S (current->y1),
			   U2S (current->x2), U2S (current->y2));
	    break;

	  case BOND_UP:
	    r = hypot ((double) current->x2 - (double) current->x1,
		       (double) current->y2 - (double) current->y1);
	    x = ((double) current->x2 - (double) current->x1) / r;
	    y = ((double) current->y2 - (double) current->y1) / r;
	    points[0].x = U2S (current->x2 - STEREO_SHIFT * y);
	    points[0].y = U2S (current->y2 + STEREO_SHIFT * x);
	    points[1].x = U2S (current->x2 + STEREO_SHIFT * y);
	    points[1].y = U2S (current->y2 - STEREO_SHIFT * x);
	    points[2].x = U2S (current->x1);
	    points[2].y = U2S (current->y1);

	    gdk_draw_polygon (pix, gc, TRUE, points, 3);
	    break;

	  case BOND_DOWN:
	    r = hypot ((double) current->x2 - (double) current->x1,
		       (double) current->y2 - (double) current->y1);
	    x = ((double) current->x2 - (double) current->x1) / r;
	    y = ((double) current->y2 - (double) current->y1) / r;

	    for (i = 0; i < 9; i++)
	      {
		cx =
		  current->x1 + (2 * i + 1) * (current->x2 -
					       current->x1) / 18;
		cy =
		  current->y1 + (2 * i + 1) * (current->y2 -
					       current->y1) / 18;
		gdk_draw_line (pix, gc,
			       U2S (cx - STEREO_SHIFT * y * (i + 1) / 9),
			       U2S (cy + STEREO_SHIFT * x * (i + 1) / 9),
			       U2S (cx + STEREO_SHIFT * y * (i + 1) / 9),
			       U2S (cy - STEREO_SHIFT * x * (i + 1) / 9));
	      }
	    break;

	  case BOND_DASHED:
	    for (i = 0; i < 4; i++)
	      {
		gdk_draw_line (pix, gc,
			       U2S (current->x1 +
				    2 * i * (current->x2 - current->x1) / 7),
			       U2S (current->y1 +
				    2 * i * (current->y2 - current->y1) / 7),
			       U2S (current->x1 +
				    (2 * i + 1) * (current->x2 -
						   current->x1) / 7),
			       U2S (current->y1 +
				    (2 * i + 1) * (current->y2 -
						   current->y1) / 7));
	      }
	    break;

	  case BOND_ARROW:
	  case BOND_ARC:
	    if (current->type == BOND_ARROW ||
		(current->type == BOND_ARC && current->x3 == -1))
	      {
		r = hypot ((double) current->x2 - (double) current->x1,
			   (double) current->y2 - (double) current->y1);
		x = ((double) current->x2 - (double) current->x1) / r;
		y = ((double) current->y2 - (double) current->y1) / r;
		gdk_draw_line (pix, gc, U2S (current->x1), U2S (current->y1),
			       U2S (current->x2), U2S (current->y2));
	      }
	    else
	      {
		info_arc (&dx, &dy, &i, &j, &dir,
			  (double) current->x1,
			  (double) current->y1,
			  (double) current->x2,
			  (double) current->y2,
			  (double) current->x3, (double) current->y3);
		r = hypot ((double) current->x1 - dx,
			   (double) current->y1 - dy);
		gdk_draw_arc (pix, gc, FALSE, U2S (dx - r), U2S (dy - r),
			      U2S (2 * r), U2S (2 * r), i, j);
	      }

	    if (current->x4 != 0)
	      {
		if (current->type == BOND_ARC)
		  {
		    y = dir * (dx - current->x2) / r;
		    x = -dir * (dy - current->y2) / r;
		  }

		switch (current->x4)
		  {
		  case 1:
		    points[0].x = U2S (current->x2 - ARROW_SHIFT * y
				       - ARROW2_SHIFT * x);
		    points[0].y = U2S (current->y2 + ARROW_SHIFT * x
				       - ARROW2_SHIFT * y);
		    points[1].x = U2S (current->x2);
		    points[1].y = U2S (current->y2);
		    points[2].x = U2S (current->x2 + ARROW_SHIFT * y
				       - ARROW2_SHIFT * x);
		    points[2].y = U2S (current->y2 - ARROW_SHIFT * x
				       - ARROW2_SHIFT * y);
		    points[3].x = U2S (current->x2 - 0.7 * ARROW2_SHIFT * x);
		    points[3].y = U2S (current->y2 - 0.7 * ARROW2_SHIFT * y);
		    gdk_draw_polygon (pix, gc, TRUE, points, 4);
		    break;

		  case 2:
		    points[1].x = U2S (current->x2);
		    points[1].y = U2S (current->y2);
		    points[2].x = U2S (current->x2 + ARROW_SHIFT * y
				       - ARROW2_SHIFT * x);
		    points[2].y = U2S (current->y2 - ARROW_SHIFT * x
				       - ARROW2_SHIFT * y);
		    points[3].x = U2S (current->x2 - 0.7 * ARROW2_SHIFT * x);
		    points[3].y = U2S (current->y2 - 0.7 * ARROW2_SHIFT * y);
		    gdk_draw_polygon (pix, gc, TRUE, points + 1, 3);
		    break;

		  case 3:
		    points[0].x = U2S (current->x2 - ARROW_SHIFT * y
				       - ARROW2_SHIFT * x);
		    points[0].y = U2S (current->y2 + ARROW_SHIFT * x
				       - ARROW2_SHIFT * y);
		    points[1].x = U2S (current->x2);
		    points[1].y = U2S (current->y2);
		    points[2].x = U2S (current->x2 - 0.7 * ARROW2_SHIFT * x);
		    points[2].y = U2S (current->y2 - 0.7 * ARROW2_SHIFT * y);
		    gdk_draw_polygon (pix, gc, TRUE, points, 3);
		    break;
		  }
	      }
	    if (current->y4 != 0)
	      {
		if (current->type == BOND_ARC)
		  {
		    y = dir * (dx - current->x1) / r;
		    x = -dir * (dy - current->y1) / r;
		  }
		switch (current->y4)
		  {
		  case 1:
		    points[0].x = U2S (current->x1 + ARROW_SHIFT * y
				       + ARROW2_SHIFT * x);
		    points[0].y = U2S (current->y1 - ARROW_SHIFT * x
				       + ARROW2_SHIFT * y);
		    points[1].x = U2S (current->x1);
		    points[1].y = U2S (current->y1);
		    points[2].x = U2S (current->x1 - ARROW_SHIFT * y
				       + ARROW2_SHIFT * x);
		    points[2].y = U2S (current->y1 + ARROW_SHIFT * x
				       + ARROW2_SHIFT * y);
		    points[3].x = U2S (current->x1 + 0.7 * ARROW2_SHIFT * x);
		    points[3].y = U2S (current->y1 + 0.7 * ARROW2_SHIFT * y);
		    gdk_draw_polygon (pix, gc, TRUE, points, 4);
		    break;

		  case 3:
		    points[0].x = U2S (current->x1 + ARROW_SHIFT * y
				       + ARROW2_SHIFT * x);
		    points[0].y = U2S (current->y1 - ARROW_SHIFT * x
				       + ARROW2_SHIFT * y);
		    points[1].x = U2S (current->x1);
		    points[1].y = U2S (current->y1);
		    points[2].x = U2S (current->x1 + 0.7 * ARROW2_SHIFT * x);
		    points[2].y = U2S (current->y1 + 0.7 * ARROW2_SHIFT * y);
		    gdk_draw_polygon (pix, gc, TRUE, points, 3);
		    break;

		  case 2:
		    points[1].x = U2S (current->x1);
		    points[1].y = U2S (current->y1);
		    points[2].x = U2S (current->x1 - ARROW_SHIFT * y
				       + ARROW2_SHIFT * x);
		    points[2].y = U2S (current->y1 + ARROW_SHIFT * x
				       + ARROW2_SHIFT * y);
		    points[3].x = U2S (current->x1 + 0.7 * ARROW2_SHIFT * x);
		    points[3].y = U2S (current->y1 + 0.7 * ARROW2_SHIFT * y);
		    gdk_draw_polygon (pix, gc, TRUE, points + 1, 3);
		    break;
		  }
	      }
	    break;

	  case BOND_CIRCLE:
	    x = ((double) current->x1 + (double) current->x2) / 2;
	    y = ((double) current->y1 + (double) current->y2) / 2;
	    r = hypot ((double) current->x1 - (double) current->x2,
		       (double) current->y1 - (double) current->y2);
	    gdk_draw_arc (pix, gc, FALSE, U2S (x - r / 2), U2S (y - r / 2),
		          U2S (r), U2S (r), 0, 23040);
	    break;

	  case BOND_GROUP:
	    draw_bond_list (current->group_members, 1,
		            current->selected | selected, area, pix, mode,
			    zoom);
	    break;

	  case BOND_ATOM:
	  case BOND_GROUP_L:
	  case BOND_GROUP_R:
	    break;

	  default:
	    bug_in ("draw_bond_list");

	  }

	current = current->next;

	if (gc != gc_sel && gc != gc_gray)
	  g_object_unref (gc);
      }

  current = list;
  if (current != NULL)
    while (current != NULL)
      {
	if (BOND_HAS_TEXT (current))
	  {
	    p_layout = gtk_widget_create_pango_layout (area, "");
	    if ((current->selected & SEL_YES) || (selected & SEL_YES))
	      pango_layout_set_rich_text (p_layout, current->pango, NULL,
                                          mode, current->width, zoom);
	    else
	    {
	      pango_layout_set_rich_text (p_layout, current->pango,
		                          &(current->color), mode,
					  current->width, zoom);
	    }
	    
	    gdk_draw_rectangle (pix, area->style->white_gc, TRUE,
				U2S (current->x4) - 1, U2S (current->y4) - 1,
				U2S (current->x2) + 3, U2S (current->y2) + 3);
	    gdk_draw_layout (pix, area->style->black_gc,
			     U2S (current->x1), U2S (current->y1), p_layout);
	    g_object_unref (p_layout);
	  }

	current = current->next;
      }

/* Third loop, for ornaments (must be drawn over everything). */
  current = list;
  while (current != NULL)
  {
    if (mode == MODE_ORNAMENT)
      gc = area->style->black_gc;
    else
      gc = gc_gray;

    if (current->ornaments[0] != NULL)
      {
        if (BOND_HAS_TEXT (current))
	  draw_ornaments (current->ornaments[0], current->x3, current->y3,
	                  zoom, gc, pix);
        else
  	  draw_ornaments (current->ornaments[0], current->x1, current->y1,
	                  zoom, gc, pix);
      }
    if (current->ornaments[1] != NULL)
      draw_ornaments (current->ornaments[1], current->x2, current->y2,
	              zoom, gc, pix);

    current = current->next;
  }
}

/* This routine is used to determine the bouding box of a given pixmap */
void
determine_bbox_from_pixmap (GdkPixmap *pix)
{
  GdkPixbuf * pixbuf;

  pixbuf = gdk_pixbuf_get_from_drawable (NULL, GDK_DRAWABLE (pix), NULL,
                                         0, 0, 0, 0, -1, -1);
  if (pixbuf == NULL)
    bug_in ("determine_bbox_from_pixmap");
}

