/* 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 <math.h>

#include "intl.h"
#include "object.h"
#include "connection.h"
#include "connectionpoint.h"
#include "diarenderer.h"
#include "attributes.h"
#include "widgets.h"
#include "message.h"
#include "color.h"
#include "properties.h"
#include "geometry.h"
#include "connpoint_line.h"

#include "grafcet.h"

#include "pixmaps/vergent.xpm"

#define VERGENT_LINE_WIDTH (GRAFCET_GENERAL_LINE_WIDTH * 1.5)

typedef enum { VERGENT_OR, VERGENT_AND } VergentType;

typedef struct _Vergent {
  Connection connection;
 
  ConnectionPoint northeast,northwest,southwest,southeast;
  ConnPointLine *north,*south;

  VergentType type;
} Vergent;

static ObjectChange* vergent_move_handle(Vergent *vergent, Handle *handle,
					 Point *to, ConnectionPoint *cp,
					 HandleMoveReason reason, 
                                ModifierKeys modifiers);
static ObjectChange* vergent_move(Vergent *vergent, Point *to);
static void vergent_select(Vergent *vergent, Point *clicked_point,
			      DiaRenderer *interactive_renderer);
static void vergent_draw(Vergent *vergent, DiaRenderer *renderer);
static DiaObject *vergent_create(Point *startpoint,
				 void *user_data,
				 Handle **handle1,
				 Handle **handle2);
static real vergent_distance_from(Vergent *vergent, Point *point);
static void vergent_update_data(Vergent *vergent);
static void vergent_destroy(Vergent *vergent);
static DiaObject *vergent_load(ObjectNode obj_node, int version,
                            const char *filename);
static PropDescription *vergent_describe_props(Vergent *vergent);
static void vergent_get_props(Vergent *vergent, 
                              GPtrArray *props);
static void vergent_set_props(Vergent *vergent, 
                              GPtrArray *props);

static DiaMenu *vergent_get_object_menu(Vergent *vergent,
					Point *clickedpoint);

static ObjectTypeOps vergent_type_ops =
{
  (CreateFunc)vergent_create,   /* create */
  (LoadFunc)  vergent_load,/*using properties*/     /* load */
  (SaveFunc)  object_save_using_properties,      /* save */
  (GetDefaultsFunc)   NULL, 
  (ApplyDefaultsFunc) NULL  
};

DiaObjectType vergent_type =
{
  "GRAFCET - Vergent",   /* name */
  0,                         /* version */
  (char **) vergent_xpm,      /* pixmap */
  
  &vergent_type_ops       /* ops */
};


static ObjectOps vergent_ops = {
  (DestroyFunc)         vergent_destroy,
  (DrawFunc)            vergent_draw,
  (DistanceFunc)        vergent_distance_from,
  (SelectFunc)          vergent_select,
  (CopyFunc)            object_copy_using_properties,
  (MoveFunc)            vergent_move,
  (MoveHandleFunc)      vergent_move_handle,
  (GetPropertiesFunc)   object_create_props_dialog,
  (ApplyPropertiesDialogFunc) object_apply_props_from_dialog,
  (ObjectMenuFunc)      vergent_get_object_menu,
  (DescribePropsFunc)   vergent_describe_props,
  (GetPropsFunc)        vergent_get_props,
  (SetPropsFunc)        vergent_set_props,
  (TextEditFunc) 0,
  (ApplyPropertiesListFunc) object_apply_props,

};

static PropEnumData prop_vtype_data[] = {
  { N_("OR"), VERGENT_OR },
  { N_("AND"), VERGENT_AND },
  { NULL, 0 }
};

static PropDescription vergent_props[] = {
  CONNECTION_COMMON_PROPERTIES,
  { "cpl_north",PROP_TYPE_CONNPOINT_LINE, 0,
    "cpl_north","cpl_north"},
  { "cpl_south",PROP_TYPE_CONNPOINT_LINE, 0,
    "cpl_south","cpl_south"},
  { "vtype", PROP_TYPE_ENUM, PROP_FLAG_VISIBLE|PROP_FLAG_NO_DEFAULTS,
    N_("Vergent type:"),NULL, prop_vtype_data},
  PROP_DESC_END
};

static PropDescription *
vergent_describe_props(Vergent *vergent) 
{
  if (vergent_props[0].quark == 0) {
    prop_desc_list_calculate_quarks(vergent_props);
  }
  return vergent_props;
}

static PropOffset vergent_offsets[] = {
  CONNECTION_COMMON_PROPERTIES_OFFSETS,
  {"cpl_north",PROP_TYPE_CONNPOINT_LINE,offsetof(Vergent,north)},
  {"cpl_south",PROP_TYPE_CONNPOINT_LINE,offsetof(Vergent,south)},
  {"vtype",PROP_TYPE_ENUM,offsetof(Vergent,type)},
  { NULL,0,0 }
};

static void
vergent_get_props(Vergent *vergent, GPtrArray *props)
{  
  object_get_props_from_offsets(&vergent->connection.object,
                                vergent_offsets,props);
}

static void
vergent_set_props(Vergent *vergent, GPtrArray *props)
{
  object_set_props_from_offsets(&vergent->connection.object,
                                vergent_offsets,props);  
  vergent_update_data(vergent);
}


static real
vergent_distance_from(Vergent *vergent, Point *point)
{
  Connection *conn = &vergent->connection;
  Rectangle rectangle;

  rectangle.left = conn->endpoints[0].x;
  rectangle.right = conn->endpoints[1].x;
  rectangle.top = conn->endpoints[0].y;
  switch (vergent->type) {
  case VERGENT_OR:
    rectangle.top -= .5 * VERGENT_LINE_WIDTH;
    rectangle.bottom = rectangle.top + VERGENT_LINE_WIDTH;
    break;
  case VERGENT_AND:
    rectangle.top -= 1.5 * VERGENT_LINE_WIDTH;
    rectangle.bottom = rectangle.top + (3.0 * VERGENT_LINE_WIDTH);
    break;
  }
  return distance_rectangle_point(&rectangle,point);
}

static void
vergent_select(Vergent *vergent, Point *clicked_point,
		  DiaRenderer *interactive_renderer)
{
  vergent_update_data(vergent);
}

static ObjectChange*
vergent_move_handle(Vergent *vergent, Handle *handle,
		    Point *to, ConnectionPoint *cp,
		    HandleMoveReason reason, ModifierKeys modifiers)
{
  g_assert(vergent!=NULL);
  g_assert(handle!=NULL);
  g_assert(to!=NULL);

  if (handle->id == HANDLE_MOVE_ENDPOINT) {
    Point to2;

    to2.x = to->x;
    to2.y = vergent->connection.endpoints[0].y;
    connection_move_handle(&vergent->connection, HANDLE_MOVE_ENDPOINT, 
			   &to2, NULL, reason, 0);
  }
  connection_move_handle(&vergent->connection, handle->id, to, cp,
			 reason, modifiers);
  vergent_update_data(vergent);

  return NULL;
}


static ObjectChange*
vergent_move(Vergent *vergent, Point *to)
{
  Point start_to_end;
  Point *endpoints = &vergent->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);

  vergent_update_data(vergent);

  return NULL;
}


static void 
vergent_draw(Vergent *vergent, DiaRenderer *renderer)
{
  DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
  Connection *conn = &vergent->connection;
  Point p1,p2;

  renderer_ops->set_linestyle(renderer, LINESTYLE_SOLID);
  
  switch(vergent->type) {
  case VERGENT_OR:
    renderer_ops->set_linewidth(renderer, VERGENT_LINE_WIDTH);
    renderer_ops->draw_line(renderer,
			     &conn->endpoints[0],&conn->endpoints[1],
			     &color_black);
    break;
  case VERGENT_AND:
    renderer_ops->set_linewidth(renderer, 2.0 * VERGENT_LINE_WIDTH);
    renderer_ops->draw_line(renderer,
			     &conn->endpoints[0],&conn->endpoints[1],
			     &color_white);
    renderer_ops->set_linewidth(renderer, VERGENT_LINE_WIDTH);
    p1.x = conn->endpoints[0].x;
    p2.x = conn->endpoints[1].x;
    p1.y = p2.y = conn->endpoints[0].y - VERGENT_LINE_WIDTH;
    renderer_ops->draw_line(renderer,&p1,&p2,&color_black);
    p1.y = p2.y = conn->endpoints[0].y + VERGENT_LINE_WIDTH;
    renderer_ops->draw_line(renderer,&p1,&p2,&color_black);
    break;
  }
}

static void
vergent_update_data(Vergent *vergent)
{
  Connection *conn = &vergent->connection;
  LineBBExtras *extra = &conn->extra_spacing;
  DiaObject *obj = &conn->object;
  Point p0,p1;
  
  conn->endpoints[1].y = conn->endpoints[0].y;
  if (ABS(conn->endpoints[1].x-conn->endpoints[0].x) < 3.0) 
    conn->endpoints[1].x = conn->endpoints[0].x + 3.0;

  obj->position = conn->endpoints[0];

  p0.x = conn->endpoints[0].x + 1.0;
  p1.x = conn->endpoints[1].x - 1.0;
  p0.y = p1.y = conn->endpoints[0].y;

  switch(vergent->type) {
  case VERGENT_OR:
    extra->start_trans = 
      extra->start_long = 
      extra->end_trans =
      extra->end_long = VERGENT_LINE_WIDTH/2.0;
    connection_update_boundingbox(conn);

    /* place the connection point lines */
    connpointline_update(vergent->north);
    connpointline_putonaline(vergent->north,&p0,&p1);
    vergent->northwest.pos = p0;
    vergent->northwest.directions = DIR_NORTH;
    vergent->northeast.pos = p1;
    vergent->northeast.directions = DIR_NORTH;
    connpointline_update(vergent->south);
    connpointline_putonaline(vergent->south,&p0,&p1);
    vergent->southwest.pos = p0;
    vergent->southwest.directions = DIR_SOUTH;
    vergent->southeast.pos = p1;    
    vergent->southeast.directions = DIR_SOUTH;
    break;
  case VERGENT_AND:
    extra->start_trans = 
      extra->end_trans = (3 * VERGENT_LINE_WIDTH)/2.0;
    extra->start_long = 
      extra->end_long = VERGENT_LINE_WIDTH/2.0;
    connection_update_boundingbox(conn);
    connection_update_boundingbox(conn);
    
    /* place the connection point lines */
    p0.y = p1.y = p0.y - VERGENT_LINE_WIDTH;
    connpointline_update(vergent->north);
    connpointline_putonaline(vergent->north,&p0,&p1);
    vergent->northwest.pos = p0;
    vergent->northwest.directions = DIR_NORTH;
    vergent->northeast.pos = p1;
    vergent->northeast.directions = DIR_NORTH;
    p0.y = p1.y = p0.y + 2.0 *VERGENT_LINE_WIDTH;
    connpointline_update(vergent->south);
    connpointline_putonaline(vergent->south,&p0,&p1);
    vergent->southwest.pos = p0;
    vergent->southwest.directions = DIR_SOUTH;
    vergent->southeast.pos = p1;
    vergent->southeast.directions = DIR_SOUTH;
    break;
  }
  connection_update_handles(conn);
}

/* DiaObject menu handling */

typedef struct {
  ObjectChange obj_change;
  
  ObjectChange *north,*south;
} VergentChange;

static void vergent_change_apply(VergentChange *change, DiaObject *obj)
{
  change->north->apply(change->north,obj);
  change->south->apply(change->south,obj);
}

static void vergent_change_revert(VergentChange *change, DiaObject *obj)
{
  change->north->revert(change->north,obj);
  change->south->revert(change->south,obj);
}

static void vergent_change_free(VergentChange *change) 
{
  if (change->north->free) change->north->free(change->north);
  g_free(change->north);
  if (change->south->free) change->south->free(change->south);
  g_free(change->south);
}

static ObjectChange *
vergent_create_change(Vergent *vergent, int add, Point *clicked) 
{
  VergentChange *vc;
 
  vc = g_new0(VergentChange,1);
  vc->obj_change.apply = (ObjectChangeApplyFunc)vergent_change_apply;
  vc->obj_change.revert = (ObjectChangeRevertFunc)vergent_change_revert;
  vc->obj_change.free = (ObjectChangeFreeFunc)vergent_change_free;
  
  if (add) {
    vc->north = connpointline_add_point(vergent->north,clicked);
    vc->south = connpointline_add_point(vergent->south,clicked);
  } else {
    vc->north = connpointline_remove_point(vergent->north,clicked);
    vc->south = connpointline_remove_point(vergent->south,clicked);
  }
  vergent_update_data(vergent);
  return (ObjectChange *)vc;
}

static ObjectChange *
vergent_add_cp_callback(DiaObject *obj, Point *clicked, gpointer data)
{
  return vergent_create_change((Vergent *)obj,1,clicked);
}

static ObjectChange *
vergent_delete_cp_callback(DiaObject *obj, Point *clicked, gpointer data)
{ 
  return vergent_create_change((Vergent *)obj,0,clicked); 
}

static DiaMenuItem object_menu_items[] = {
  { N_("Add connection point"), vergent_add_cp_callback, NULL, 1 },
  { N_("Delete connection point"), vergent_delete_cp_callback, NULL, 1 },
};

static DiaMenu object_menu = {
  N_("GRAFCET OR/AND vergent"),
  sizeof(object_menu_items)/sizeof(DiaMenuItem),
  object_menu_items,
  NULL
};

static DiaMenu *
vergent_get_object_menu(Vergent *vergent, Point *clickedpoint)
{
  /* Set entries sensitive/selected etc here */
  g_assert(vergent->north->num_connections == vergent->south->num_connections);

  object_menu_items[0].active = 1;
  object_menu_items[1].active = (vergent->north->num_connections > 1);

  return &object_menu;
}

static DiaObject *
vergent_create(Point *startpoint,
		  void *user_data,
		  Handle **handle1,
		  Handle **handle2)
{
  Vergent *vergent;
  Connection *conn;
  DiaObject *obj;
  int i;
  Point defaultlen  = {6.0,0.0};

  vergent = g_malloc0(sizeof(Vergent));
  conn = &vergent->connection;
  obj = &conn->object;
  
  obj->type = &vergent_type;
  obj->ops = &vergent_ops;
  
  conn->endpoints[0] = *startpoint;
  conn->endpoints[1] = *startpoint;
  point_add(&conn->endpoints[1], &defaultlen);

  connection_init(conn, 2, 4);

  obj->connections[0] = &vergent->northeast;
  obj->connections[1] = &vergent->northwest;
  obj->connections[2] = &vergent->southwest;
  obj->connections[3] = &vergent->southeast;
  for (i=0; i<4; i++) {
    obj->connections[i]->object = obj;
    obj->connections[i]->connected = NULL;
  }
  
  vergent->north = connpointline_create(obj,1);
  vergent->south = connpointline_create(obj,1);
  
  switch(GPOINTER_TO_INT(user_data)) {
  case VERGENT_OR:
  case VERGENT_AND:
    vergent->type = GPOINTER_TO_INT(user_data);
    break;
  default:
    g_warning("in vergent_create(): incorrect user_data %p",user_data);
    vergent->type = VERGENT_OR;
  }
  
  vergent_update_data(vergent);

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

  return &vergent->connection.object;
}

static void
vergent_destroy(Vergent *vergent)
{
  connpointline_destroy(vergent->south);
  connpointline_destroy(vergent->north);
  connection_destroy(&vergent->connection);
}

static DiaObject *
vergent_load(ObjectNode obj_node, int version, const char *filename)
{
  return object_load_using_properties(&vergent_type,
                                      obj_node,version,filename);
}












