/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998 Alexander Larsson
 *
 * GRAFCET chart support 
 * Copyright(C) 2000 Cyrille Chepelov
 *
 * 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.
 */

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

#include <assert.h>
#include <gtk/gtk.h>
#include <math.h>

#include "intl.h"
#include "object.h"
#include "connection.h"
#include "connectionpoint.h"
#include "render.h"
#include "attributes.h"
#include "widgets.h"
#include "message.h"
#include "color.h"
#include "lazyprops.h"
#include "geometry.h"
#include "text.h"
#include "connpoint_line.h"

#include "grafcet.h"
#include "action_text_draw.h"

#include "pixmaps/action.xpm"

#define ACTION_LINE_WIDTH GRAFCET_GENERAL_LINE_WIDTH
#define ACTION_FONT "Helvetica-Bold"
#define ACTION_FONT_HEIGHT 0.8
#define ACTION_HEIGHT (2.0)

typedef struct _ActionPropertiesDialog ActionPropertiesDialog;
typedef struct _ActionDefaultsDialog ActionDefaultsDialog;
typedef struct _ActionState ActionState;

struct _ActionState {
  ObjectState obj_state;
  
  TextAttributes text_attrib;
  gboolean macro_call;
};

typedef struct _Action {
  Connection connection;
  
  Text *text;
  gboolean macro_call;

  /* computed values : */
  real space_width; /* width of a space in the current font */
  real label_width;
  Rectangle labelbb; /* The bounding box of the label itself */
  Point labelstart;

  ConnPointLine *cps; /* aaahrg ! again one ! */
} Action;

struct _ActionPropertiesDialog {
  AttributeDialog dialog;
  Action *parent;

  BoolAttribute macro_call;
  TextFontAttribute text_font;
  TextFontHeightAttribute text_fontheight;
  TextColorAttribute text_color;
};

typedef struct _ActionDefaults {
  Font *font;
  real font_size;
  Color font_color;
  /* macro_call is not here ; that's voluntary. */
} ActionDefaults;

struct _ActionDefaultsDialog {
  AttributeDialog dialog;
  ActionDefaults *parent;

  FontAttribute font;
  FontHeightAttribute font_size;
  ColorAttribute font_color;
};

static ActionPropertiesDialog *action_properties_dialog;
static ActionDefaultsDialog *action_defaults_dialog;
static ActionDefaults defaults;

static void action_move_handle(Action *action, Handle *handle,
				   Point *to, HandleMoveReason reason, ModifierKeys modifiers);
static void action_move(Action *action, Point *to);
static void action_select(Action *action, Point *clicked_point,
			      Renderer *interactive_renderer);
static void action_draw(Action *action, Renderer *renderer);
static Object *action_create(Point *startpoint,
				 void *user_data,
				 Handle **handle1,
				 Handle **handle2);
static real action_distance_from(Action *action, Point *point);
static void action_update_data(Action *action);
static void action_destroy(Action *action);
static Object *action_copy(Action *action);
static GtkWidget *action_get_properties(Action *action);
static ObjectChange *action_apply_properties(Action *action);

static ActionState *action_get_state(Action *action);
static void action_set_state(Action *action, ActionState *state);

static void action_save(Action *action, ObjectNode obj_node,
			    const char *filename);
static Object *action_load(ObjectNode obj_node, int version,
			       const char *filename);

static GtkWidget *action_get_defaults(void);
static void action_apply_defaults(void);

static ObjectTypeOps action_type_ops =
{
  (CreateFunc)action_create,   /* create */
  (LoadFunc)  action_load,     /* load */
  (SaveFunc)  action_save,      /* save */
  (GetDefaultsFunc)   action_get_defaults, 
  (ApplyDefaultsFunc) action_apply_defaults
};

ObjectType action_type =
{
  "GRAFCET - Action",   /* name */
  0,                         /* version */
  (char **) action_xpm,      /* pixmap */
  
  &action_type_ops       /* ops */
};


static ObjectOps action_ops = {
  (DestroyFunc)         action_destroy,
  (DrawFunc)            action_draw,
  (DistanceFunc)        action_distance_from,
  (SelectFunc)          action_select,
  (CopyFunc)            action_copy,
  (MoveFunc)            action_move,
  (MoveHandleFunc)      action_move_handle,
  (GetPropertiesFunc)   action_get_properties,
  (ApplyPropertiesFunc) action_apply_properties,
  (ObjectMenuFunc)      NULL
};

static ObjectChange *
action_apply_properties(Action *action)
{
  ObjectState *old_state;
  ActionPropertiesDialog *dlg = action_properties_dialog;

  PROPDLG_SANITY_CHECK(dlg,action);
  
  old_state = (ObjectState *)action_get_state(action);

  PROPDLG_APPLY_TEXTFONT(dlg,text);
  PROPDLG_APPLY_TEXTFONTHEIGHT(dlg,text);
  PROPDLG_APPLY_TEXTCOLOR(dlg,text);
  PROPDLG_APPLY_BOOL(dlg,macro_call);
  
  action_update_data(action);
  return new_object_state_change(&action->connection.object, old_state, 
				 (GetStateFunc)action_get_state,
				 (SetStateFunc)action_set_state);
}

static PROPDLG_TYPE
action_get_properties(Action *action)
{
  ActionPropertiesDialog *dlg = action_properties_dialog;
  
  PROPDLG_CREATE(dlg,action);
  PROPDLG_SHOW_TEXTFONT(dlg,text,_("Font:"));
  PROPDLG_SHOW_TEXTFONTHEIGHT(dlg,text,_("Font size:"));
  PROPDLG_SHOW_TEXTCOLOR(dlg,text,_("Text color:"));
  PROPDLG_SHOW_BOOL(dlg,macro_call,_("Macro call"));
  PROPDLG_READY(dlg);
  
  action_properties_dialog = dlg;

  PROPDLG_RETURN(dlg);
}

static void 
action_apply_defaults(void)
{
  ActionDefaultsDialog *dlg = action_defaults_dialog;  

  PROPDLG_APPLY_FONT(dlg,font);
  PROPDLG_APPLY_FONTHEIGHT(dlg,font_size);
  PROPDLG_APPLY_COLOR(dlg,font_color);
}

static void
init_default_values(void) {
  static int defaults_initialized = 0;
  
  if (!defaults_initialized) {
    defaults.font = font_getfont(ACTION_FONT);
    defaults.font_size = ACTION_FONT_HEIGHT;
    defaults.font_color = color_black;

    defaults_initialized = 1;
  }
}

static PROPDLG_TYPE
action_get_defaults(void)
{
  ActionDefaultsDialog *dlg = action_defaults_dialog;
  init_default_values();
  PROPDLG_CREATE(dlg, &defaults);

  PROPDLG_SHOW_FONT(dlg,font,_("Font:"));
  PROPDLG_SHOW_FONTHEIGHT(dlg,font_size,_("Font size:"));
  PROPDLG_SHOW_COLOR(dlg,font_color,_("Text color:"));
  PROPDLG_READY(dlg);

  action_defaults_dialog = dlg;

  PROPDLG_RETURN(dlg);
}


static real
action_distance_from(Action *action, Point *point)
{
  Connection *conn = &action->connection;
  real dist; Point p1,p2;
  dist = distance_rectangle_point(&action->labelbb,point);
  p1.x = p2.x = (conn->endpoints[0].x+conn->endpoints[1].x)/2;
  p1.y = conn->endpoints[0].y; p2.y = conn->endpoints[0].y;
  dist = MIN(dist,distance_line_point(&conn->endpoints[0],&p1,
				      ACTION_LINE_WIDTH,point));
  dist = MIN(dist,distance_line_point(&conn->endpoints[1],&p2,
				      ACTION_LINE_WIDTH,point));
  dist = MIN(dist,distance_line_point(&p2,&p1,ACTION_LINE_WIDTH,point));
  return dist;
}

static void
action_select(Action *action, Point *clicked_point,
		  Renderer *interactive_renderer)
{
  action_update_data(action);
  text_grab_focus(action->text, &action->connection.object);
}

static void
action_move_handle(Action *action, Handle *handle,
		       Point *to, HandleMoveReason reason, ModifierKeys modifiers)
{
  g_assert(action!=NULL);
  g_assert(handle!=NULL);
  g_assert(to!=NULL);

#if 0
  if (handle->id == HANDLE_MOVE_STARTPOINT) {
    Point to2;
    /* move also the second point */
    to2 = *to;
    point_sub(&to2,&action->connection.endpoints[0]);
    point_add(&to2,&action->connection.endpoints[1]);
    connection_move_handle(&action->connection, HANDLE_MOVE_ENDPOINT, 
			   to, reason);
  }
#endif
  connection_move_handle(&action->connection, handle->id, to, reason);
  action_update_data(action);
}


static void
action_move(Action *action, Point *to)
{
  Point start_to_end;
  Point *endpoints = &action->connection.endpoints[0]; 

  start_to_end = endpoints[1];
  point_sub(&start_to_end, &endpoints[0]);

  endpoints[1] = endpoints[0] = *to;
  point_add(&endpoints[1], &start_to_end);

  action_update_data(action);
}

static void
action_update_data(Action *action)
{
  Point p1,p2;
  real x,x1;
  real left,right;
  int i;
  real chunksize;
  Connection *conn = &action->connection;
  Object *obj = &conn->object;

  obj->position = conn->endpoints[0];
  connection_update_boundingbox(conn);

  /* compute the label's width and bounding box */
  action->space_width = action_text_spacewidth(action->text);

  action->labelstart = conn->endpoints[1];
  action->labelbb.left = action->labelstart.x;
  action->labelstart.y += .3 * action->text->height; 
  action->labelstart.x = action->labelbb.left + action->space_width;
  if (action->macro_call) {
    action->labelstart.x += 2.0 * action->space_width;
  }
  text_set_position(action->text,&action->labelstart);

  action_text_calc_boundingbox(action->text,&action->labelbb);

  if (action->macro_call) {
    action->labelbb.right += 2.0 * action->space_width;
  }
  action->labelbb.top = conn->endpoints[1].y - .5*ACTION_HEIGHT;
  action->labelbb.bottom = action->labelstart.y + .5*ACTION_HEIGHT;
  
  action->label_width = action->labelbb.right - 
    action->labelbb.left;


  /* Adjust the count and positions of the condition connection points. */
  
  
  left = x = conn->endpoints[1].x;
  right = left + action->label_width;
  p1.y = conn->endpoints[1].y - .5 * ACTION_HEIGHT;
  p2.y = p1.y + ACTION_HEIGHT;
  connpointline_adjust_count(action->cps,2+(2 * action->text->numlines), &p1);
  
  for (i=0; i<action->text->numlines; i++) {
    chunksize = font_string_width(action->text->line[i],
				  action->text->font,
				  action->text->height);
    x1 = x + 1.0;
    if (x1 >= right) {
      x1 = right - ACTION_LINE_WIDTH;
    }
    p1.x = p2.x = x1;

    obj->connections[(2*i) + 2]->pos = p1;
    obj->connections[(2*i) + 3]->pos = p2;

    x = x + chunksize + 2 * action->space_width; 
  }
  p1.y = p2.y = conn->endpoints[1].y;
  p1.x = left; p2.x = right;
  obj->connections[0]->pos = p1;
  obj->connections[1]->pos = p2;

  /* fix boundingbox for line_width: */
  action->labelbb.top -= ACTION_LINE_WIDTH/2;
  action->labelbb.left -= ACTION_LINE_WIDTH/2;
  action->labelbb.bottom += ACTION_LINE_WIDTH/2;
  action->labelbb.right += ACTION_LINE_WIDTH/2;

  rectangle_union(&obj->bounding_box,&action->labelbb);
  connection_update_handles(conn);
}


static void 
action_draw(Action *action, Renderer *renderer)
{
  Connection *conn = &action->connection;
  Point ul,br,p1,p2;
  int i;
  real chunksize;
  Color cl;

  renderer->ops->set_linewidth(renderer, ACTION_LINE_WIDTH);
  renderer->ops->set_linestyle(renderer, LINESTYLE_SOLID);
  renderer->ops->set_linecaps(renderer, LINECAPS_BUTT);

  /* first, draw the line or polyline from the step to the action label */
  if (conn->endpoints[0].y == conn->endpoints[1].y) {
    /* simpler case */
    renderer->ops->draw_line(renderer,
			     &conn->endpoints[0],&conn->endpoints[1],
			     &color_black);
  } else {
    Point pts[4];
    pts[0] = conn->endpoints[0];
    pts[3] = conn->endpoints[1];
    pts[1].y = pts[0].y;
    pts[2].y = pts[3].y;
    pts[1].x = pts[2].x = .5 * (pts[0].x + pts[3].x);
    
    renderer->ops->draw_polyline(renderer,
				 pts,sizeof(pts)/sizeof(pts[0]),
				 &color_black);
  }

  /* Now, draw the action label. */
  ul.x = conn->endpoints[1].x;
  ul.y = conn->endpoints[1].y - .5 * ACTION_HEIGHT;
  br.x = ul.x + action->label_width;
  br.y = ul.y + ACTION_HEIGHT;

  renderer->ops->fill_rect(renderer,&ul,&br,&color_white);

  action_text_draw(action->text,renderer);

  p1.x = p2.x = ul.x; 
  p1.y = ul.y; p2.y = br.y;

  for (i=0; i<action->text->numlines-1; i++) {
    chunksize = font_string_width(action->text->line[i],
				      action->text->font,
				      action->text->height);
    p1.x = p2.x = p1.x + chunksize + 2 * action->space_width;
    renderer->ops->draw_line(renderer,&p1,&p2,&color_black);
  }

  if (action->macro_call) {
    p1.x = p2.x = ul.x + 2.0 * action->space_width;
    renderer->ops->draw_line(renderer,&p1,&p2,&color_black);
    p1.x = p2.x = br.x - 2.0 * action->space_width;
    renderer->ops->draw_line(renderer,&p1,&p2,&color_black);
  }

  cl.red = 1.0; cl.blue = cl.green = .2; 
  renderer->ops->draw_rect(renderer,&ul,&br,&color_black);
}

static Object *
action_create(Point *startpoint,
		  void *user_data,
		  Handle **handle1,
		  Handle **handle2)
{
  Action *action;
  Connection *conn;
  Object *obj;
  ConnectionBBExtras *extra;
  Point defaultlen  = {1.0,0.0}, pos;

  init_default_values();
  action = g_malloc0(sizeof(Action));
  conn = &action->connection;
  obj = &conn->object;
  extra = &conn->extra_spacing;
  
  obj->type = &action_type;
  obj->ops = &action_ops;
  
  conn->endpoints[0] = *startpoint;
  conn->endpoints[1] = *startpoint;
  point_add(&conn->endpoints[1], &defaultlen);

  connection_init(conn, 2,0);
  action->cps = connpointline_create(obj,0);

  pos = conn->endpoints[1];
  action->text = new_text("",defaults.font,defaults.font_size,
			  &pos, /* never used */
			  &defaults.font_color, ALIGN_LEFT);
  action->macro_call = FALSE;

  extra->start_long =
    extra->start_trans = 
    extra->end_trans =
    extra->end_long = ACTION_LINE_WIDTH/2.0;

  action_update_data(action);

  conn->endpoint_handles[1].connect_type = HANDLE_NONCONNECTABLE;

  *handle1 = &conn->endpoint_handles[0];
  *handle2 = &conn->endpoint_handles[1];

  return &action->connection.object;
}

static void
action_destroy(Action *action)
{
  text_destroy(action->text);
  connpointline_destroy(action->cps);
  connection_destroy(&action->connection);
}

static Object *
action_copy(Action *action)
{
  Action *newaction;
  Connection *conn, *newconn;
  Object *newobj;
  int rcc;

  conn = &action->connection;
 
  newaction = g_malloc0(sizeof(Action));
  newconn = &newaction->connection;
  newobj = &newconn->object;

  connection_copy(conn, newconn);

  rcc = newobj->num_connections - action->cps->num_connections;
  g_assert(rcc == 0);
  newaction->cps = connpointline_copy(newobj,action->cps,&rcc);
  /* g_assert(rcc == newaction->cps->num_connections); */
  newaction->text = text_copy(action->text);
  newaction->macro_call = action->macro_call;

  return &newaction->connection.object;
}

static ActionState *
action_get_state(Action *action)
{
  ActionState *state = g_new0(ActionState, 1);

  state->obj_state.free = NULL;
  text_get_attributes(action->text, &state->text_attrib);
  state->macro_call = action->macro_call;

  return state;
}

static void
action_set_state(Action *action, ActionState *state)
{
  text_set_attributes(action->text, &state->text_attrib);
  action->macro_call = state->macro_call;

  g_free(state);
  
  action_update_data(action);
}


static void
action_save(Action *action, ObjectNode obj_node,
		const char *filename)
{
  connection_save(&action->connection, obj_node);

  save_text(obj_node,"text",action->text);
  save_boolean(obj_node,"macro_call",action->macro_call);
}

static Object *
action_load(ObjectNode obj_node, int version, const char *filename)
{
  Action *action;
  Connection *conn;
  Object *obj;
  ConnectionBBExtras *extra;

  init_default_values();
  action = g_malloc0(sizeof(Action));

  conn = &action->connection;
  obj = &conn->object;
  extra = &conn->extra_spacing;

  obj->type = &action_type;
  obj->ops = &action_ops;

  connection_load(conn, obj_node);
  connection_init(conn, 2,0);

  action->cps = connpointline_create(obj,0);
  action->text = load_text(obj_node,"text",NULL);
  if (!action->text) {
    action->text = new_text("",defaults.font,defaults.font_size,
			    &conn->endpoints[1], /* never used */
			    &defaults.font_color, ALIGN_LEFT);
  }
  action->macro_call = load_boolean(obj_node,"macro_call",FALSE);

  extra->start_long =
    extra->start_trans = 
    extra->end_trans =
    extra->end_long = ACTION_LINE_WIDTH/2.0;

  action_update_data(action);

  return &action->connection.object;
}











