/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998, 1999 Alexander Larsson
 *
 * 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.
 */

/* DO NOT USE THIS OBJECT AS A BASIS FOR A NEW OBJECT. */

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

#include <assert.h>
#include <math.h>
#include <string.h>
#include <stdio.h>

#include "intl.h"
#include "object.h"
#include "connection.h"
#include "diarenderer.h"
#include "handle.h"
#include "arrows.h"
#include "diamenu.h"
#include "text.h"
#include "connection.h"
#include "properties.h"

#include "pixmaps/flow.xpm"

Color flow_color_energy   = { 1.0f, 0.0f, 0.0f };
Color flow_color_material = { 0.8f, 0.0f, 0.8f };
Color flow_color_signal   = { 0.0f, 0.0f, 1.0f };

typedef struct _Flow Flow;
typedef enum {
  FLOW_ENERGY,
  FLOW_MATERIAL,
  FLOW_SIGNAL
} FlowType;

struct _Flow {
  Connection connection;

  Handle text_handle;

  Text* text;
  TextAttributes attrs;
  FlowType type;
  Point textpos; /* This is the master position, but overridden in load */
};
  
#define FLOW_WIDTH 0.1
#define FLOW_MATERIAL_WIDTH 0.2
#define FLOW_DASHLEN 0.4
#define FLOW_FONTHEIGHT 0.6
#define FLOW_ARROWLEN 0.8
#define FLOW_ARROWWIDTH 0.5
#define HANDLE_MOVE_TEXT (HANDLE_CUSTOM1)

static ObjectChange* flow_move_handle(Flow *flow, Handle *handle,
				      Point *to, ConnectionPoint *cp,
				      HandleMoveReason reason, 
				      ModifierKeys modifiers);
static ObjectChange* flow_move(Flow *flow, Point *to);
static void flow_select(Flow *flow, Point *clicked_point,
			DiaRenderer *interactive_renderer);
static void flow_draw(Flow *flow, DiaRenderer *renderer);
static DiaObject *flow_create(Point *startpoint,
			   void *user_data,
			   Handle **handle1,
			   Handle **handle2);
static real flow_distance_from(Flow *flow, Point *point);
static void flow_update_data(Flow *flow);
static void flow_destroy(Flow *flow);
static DiaObject *flow_copy(Flow *flow);
static void flow_save(Flow *flow, ObjectNode obj_node,
		      const char *filename);
static DiaObject *flow_load(ObjectNode obj_node, int version,
			 const char *filename);
static PropDescription *flow_describe_props(Flow *mes);
static void
flow_get_props(Flow * flow, GPtrArray *props);
static void
flow_set_props(Flow * flow, GPtrArray *props);
static DiaMenu *flow_get_object_menu(Flow *flow, Point *clickedpoint) ;


static ObjectTypeOps flow_type_ops =
{
  (CreateFunc)		flow_create,
  (LoadFunc)		flow_load,
  (SaveFunc)		flow_save,
  (GetDefaultsFunc)	NULL,
  (ApplyDefaultsFunc)	NULL,
} ;

DiaObjectType flow_type =
{
  "FS - Flow",         /* name */
  0,                   /* version */
  (char **) flow_xpm,  /* pixmap */
  &flow_type_ops       /* ops */
};

static ObjectOps flow_ops = {
  (DestroyFunc)         flow_destroy,
  (DrawFunc)            flow_draw,
  (DistanceFunc)        flow_distance_from,
  (SelectFunc)          flow_select,
  (CopyFunc)            flow_copy,
  (MoveFunc)            flow_move,
  (MoveHandleFunc)      flow_move_handle,
  (GetPropertiesFunc)   object_create_props_dialog,
  (ApplyPropertiesDialogFunc) object_apply_props_from_dialog,
  (ObjectMenuFunc)      flow_get_object_menu,
  (DescribePropsFunc)   flow_describe_props,
  (GetPropsFunc)        flow_get_props,
  (SetPropsFunc)        flow_set_props,
  (TextEditFunc) 0,
  (ApplyPropertiesListFunc) object_apply_props,
};

static PropEnumData prop_flow_type_data[] = {
  { N_("Energy"),FLOW_ENERGY },
  { N_("Material"),FLOW_MATERIAL },
  { N_("Signal"),FLOW_SIGNAL },
  { NULL, 0 } 
};

static PropDescription flow_props[] = {
  OBJECT_COMMON_PROPERTIES,
  { "type", PROP_TYPE_ENUM, PROP_FLAG_VISIBLE,
    N_("Type:"), NULL, prop_flow_type_data },
  { "text", PROP_TYPE_TEXT, 0, NULL, NULL },
  PROP_STD_TEXT_ALIGNMENT,
  PROP_STD_TEXT_FONT,
  PROP_STD_TEXT_HEIGHT,
  /* Colour determined from type, don't show */
  { "text_colour", PROP_TYPE_COLOUR, PROP_FLAG_DONT_SAVE, },
  PROP_DESC_END
};

static PropDescription *
flow_describe_props(Flow *mes)
{
  if (flow_props[0].quark == 0)
    prop_desc_list_calculate_quarks(flow_props);
  return flow_props;

}

static PropOffset flow_offsets[] = {
  OBJECT_COMMON_PROPERTIES_OFFSETS,
  { "type", PROP_TYPE_ENUM, offsetof(Flow, type) },
  { "text", PROP_TYPE_TEXT, offsetof (Flow, text) },
  { "text_alignment", PROP_TYPE_ENUM, offsetof (Flow, attrs.alignment) },
  { "text_font", PROP_TYPE_FONT, offsetof (Flow, attrs.font) },
  { PROP_STDNAME_TEXT_HEIGHT, PROP_STDTYPE_TEXT_HEIGHT, offsetof (Flow, attrs.height) },
  { "text_colour", PROP_TYPE_COLOUR, offsetof (Flow, attrs.color) },
  { NULL, 0, 0 }
};

static void
flow_get_props(Flow * flow, GPtrArray *props)
{
  text_get_attributes (flow->text, &flow->attrs);
  object_get_props_from_offsets(&flow->connection.object, 
                                flow_offsets, props);
}

static void
flow_set_props(Flow *flow, GPtrArray *props)
{
  object_set_props_from_offsets(&flow->connection.object, 
                                flow_offsets, props);
  apply_textattr_properties (props, flow->text, "text", &flow->attrs);
  flow_update_data(flow);
}


static real
flow_distance_from(Flow *flow, Point *point)
{
  Point *endpoints;
  real linedist;
  real textdist;
  
  endpoints = &flow->connection.endpoints[0];
  
  linedist = distance_line_point(&endpoints[0], &endpoints[1], 
				 flow->type == FLOW_MATERIAL ? FLOW_MATERIAL_WIDTH : FLOW_WIDTH, 
				 point);
  textdist = text_distance_from( flow->text, point ) ;
  
  return linedist > textdist ? textdist : linedist ;
}

static void
flow_select(Flow *flow, Point *clicked_point,
	    DiaRenderer *interactive_renderer)
{
  text_set_cursor(flow->text, clicked_point, interactive_renderer);
  text_grab_focus(flow->text, &flow->connection.object);

  connection_update_handles(&flow->connection);
}

static ObjectChange*
flow_move_handle(Flow *flow, Handle *handle,
		 Point *to, ConnectionPoint *cp,
		 HandleMoveReason reason, ModifierKeys modifiers)
{
  Point p1, p2;
  Point *endpoints;
  
  assert(flow!=NULL);
  assert(handle!=NULL);
  assert(to!=NULL);

  if (handle->id == HANDLE_MOVE_TEXT) {
    flow->textpos = *to;
  } else  {
    real dest_length ;
    real orig_length2 ;
    real along_mag, norm_mag ;
    Point along ;

    endpoints = &flow->connection.endpoints[0]; 
    p1 = flow->textpos ;
    point_sub( &p1, &endpoints[0] ) ;

    p2 = endpoints[1] ;
    point_sub( &p2, &endpoints[0] ) ;
    orig_length2= point_dot( &p2, &p2 ) ;
    along = p2 ;
    if ( orig_length2 > 1e-5 ) {
      along_mag = point_dot( &p2, &p1 )/sqrt(orig_length2) ;
      along_mag *= along_mag ;
      norm_mag = point_dot( &p1, &p1 ) ;
      norm_mag = (real)sqrt( (double)( norm_mag - along_mag ) ) ;
      along_mag = (real)sqrt( along_mag/orig_length2 ) ;
      if ( p1.x*p2.y - p1.y*p2.x > 0.0 )
	norm_mag = -norm_mag ;
    } else {
      along_mag = 0.5 ;
      norm_mag = (real)sqrt( (double) point_dot( &p1, &p1 ) ) ;
    }

    connection_move_handle(&flow->connection, handle->id, to, cp, 
			   reason, modifiers);

    p2 = endpoints[1] ;
    point_sub( &p2, &endpoints[0] ) ;
    flow->textpos = endpoints[0] ;
    along = p2 ;
    p2.x = -along.y ;
    p2.y = along.x ;
    dest_length = point_dot( &p2, &p2 ) ;
    if ( dest_length > 1e-5 ) {
      point_normalize( &p2 ) ;
    } else {
      p2.x = 0.0 ; p2.y = -1.0 ; 
    }
    point_scale( &p2, norm_mag ) ;
    point_scale( &along, along_mag ) ;
    point_add( &flow->textpos, &p2 ) ;
    point_add( &flow->textpos, &along ) ;
  }

  flow_update_data(flow);

  return NULL;
}

static ObjectChange*
flow_move(Flow *flow, Point *to)
{
  Point start_to_end;
  Point *endpoints = &flow->connection.endpoints[0]; 
  Point delta;

  delta = *to;
  point_sub(&delta, &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);

  point_add(&flow->textpos, &delta);

  flow_update_data(flow);

  return NULL;
}

static void
flow_draw(Flow *flow, DiaRenderer *renderer)
{
  DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
  Point *endpoints, p1, p2;
  Arrow arrow;
  int n1 = 1, n2 = 0;
  Color* render_color = NULL;

  assert(flow != NULL);
  assert(renderer != NULL);

  arrow.type = ARROW_FILLED_TRIANGLE;
  arrow.width = FLOW_ARROWWIDTH;
  arrow.length = FLOW_ARROWLEN;
 
  endpoints = &flow->connection.endpoints[0];
  
  renderer_ops->set_linewidth(renderer, FLOW_WIDTH);

  renderer_ops->set_linecaps(renderer, LINECAPS_BUTT);

  switch (flow->type) {
  case FLOW_SIGNAL:
    renderer_ops->set_dashlength(renderer, FLOW_DASHLEN);
    renderer_ops->set_linestyle(renderer, LINESTYLE_DASHED);
    render_color = &flow_color_signal ;
    break ;
  case FLOW_MATERIAL:
    renderer_ops->set_linewidth(renderer, FLOW_MATERIAL_WIDTH ) ;
    renderer_ops->set_linestyle(renderer, LINESTYLE_SOLID);
    render_color = &flow_color_material ;
    break ;
  case FLOW_ENERGY:
    render_color = &flow_color_energy ;
    renderer_ops->set_linestyle(renderer, LINESTYLE_SOLID);
  }

  p1 = endpoints[n1];
  p2 = endpoints[n2];

  renderer_ops->draw_line_with_arrows(renderer,
				       &p1, &p2,
				       FLOW_WIDTH,
				       render_color,
				       &arrow, NULL); 

  text_draw(flow->text, renderer);
}

static DiaObject *
flow_create(Point *startpoint,
	    void *user_data,
	    Handle **handle1,
	    Handle **handle2)
{
  Flow *flow;
  Connection *conn;
  DiaObject *obj;
  LineBBExtras *extra;
  Point p ;
  Point n ;
  DiaFont *font;

  flow = g_malloc0(sizeof(Flow));

  conn = &flow->connection;
  conn->endpoints[0] = *startpoint;
  conn->endpoints[1] = *startpoint;
  conn->endpoints[1].x += 1.5;
 
  obj = &conn->object;
  extra = &conn->extra_spacing;

  obj->type = &flow_type;
  obj->ops = &flow_ops;
  
  connection_init(conn, 3, 0);

  p = conn->endpoints[1] ;
  point_sub( &p, &conn->endpoints[0] ) ;
  point_scale( &p, 0.5 ) ;
  n.x = p.y ;
  n.y = -p.x ;
  if ( fabs(n.x) < 1.e-5 && fabs(n.y) < 1.e-5 ) {
    n.x = 0. ;
    n.y = -1. ;
  } else {
    point_normalize( &n ) ;
  }
  point_scale( &n, 0.5*FLOW_FONTHEIGHT ) ;

  point_add( &p, &n ) ;
  point_add( &p, &conn->endpoints[0] ) ;
  flow->textpos = p;

  font = dia_font_new_from_style(DIA_FONT_SANS, 0.8);

  flow->text = new_text("", font, 0.8, &p, &color_black, ALIGN_CENTER);
  dia_font_unref(font);  
  text_get_attributes(flow->text, &flow->attrs);

  flow->text_handle.id = HANDLE_MOVE_TEXT;
  flow->text_handle.type = HANDLE_MINOR_CONTROL;
  flow->text_handle.connect_type = HANDLE_NONCONNECTABLE;
  flow->text_handle.connected_to = NULL;
  flow->text_handle.pos = p;
  obj->handles[2] = &flow->text_handle;

  extra->start_long = 
    extra->end_long =
    extra->start_trans = FLOW_WIDTH/2.0;
  extra->end_trans = MAX(FLOW_WIDTH, FLOW_ARROWLEN) / 2.0;
  flow_update_data(flow);
  *handle1 = obj->handles[0];
  *handle2 = obj->handles[1];
  
  return &flow->connection.object;
}


static void
flow_destroy(Flow *flow)
{
  connection_destroy(&flow->connection);
  text_destroy(flow->text) ;
}

static DiaObject *
flow_copy(Flow *flow)
{
  Flow *newflow;
  Connection *conn, *newconn;
  DiaObject *newobj;
  
  conn = &flow->connection;
  
  newflow = g_malloc0(sizeof(Flow));
  newconn = &newflow->connection;
  newobj = &newconn->object;

  connection_copy(conn, newconn);

  newflow->text_handle = flow->text_handle;
  newflow->text_handle.connected_to = NULL;
  newobj->handles[2] = &newflow->text_handle;
  newflow->textpos = flow->textpos;
  newflow->text = text_copy(flow->text);
  newflow->type = flow->type;

  flow_update_data(newflow);
  return &newflow->connection.object;
}


static void
flow_update_data(Flow *flow)
{
  Connection *conn = &flow->connection;
  DiaObject *obj = &conn->object;
  Rectangle rect;
  Color* color = NULL;
  
  if (connpoint_is_autogap(flow->connection.endpoint_handles[0].connected_to) ||
      connpoint_is_autogap(flow->connection.endpoint_handles[1].connected_to)) {
    connection_adjust_for_autogap(conn);
  }
  obj->position = conn->endpoints[0];

  switch (flow->type) {
  case FLOW_ENERGY:
    color = &flow_color_energy ;
    break ;
  case FLOW_MATERIAL:
    color = &flow_color_material ;
    break ;
  case FLOW_SIGNAL:
    color = &flow_color_signal ;
    break ;
  }
  text_set_color( flow->text, color ) ;

  flow->text->position = flow->textpos;
  flow->text_handle.pos = flow->textpos;

  connection_update_handles(conn);

  /* Boundingbox: */
  connection_update_boundingbox(conn);

  /* Add boundingbox for text: */
  text_calc_boundingbox(flow->text, &rect) ;
  rectangle_union(&obj->bounding_box, &rect);
}


static void
flow_save(Flow *flow, ObjectNode obj_node, const char *filename)
{
  connection_save(&flow->connection, obj_node);

  data_add_text(new_attribute(obj_node, "text"),
		flow->text) ;
  data_add_int(new_attribute(obj_node, "type"),
	       flow->type);
}

static DiaObject *
flow_load(ObjectNode obj_node, int version, const char *filename)
{
  Flow *flow;
  AttributeNode attr;
  Connection *conn;
  DiaObject *obj;
  LineBBExtras *extra;

  flow = g_malloc0(sizeof(Flow));

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

  obj->type = &flow_type;
  obj->ops = &flow_ops;

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

  flow->text = NULL;
  attr = object_find_attribute(obj_node, "text");
  if (attr != NULL)
    flow->text = data_text(attribute_first_data(attr));

  attr = object_find_attribute(obj_node, "type");
  if (attr != NULL)
    flow->type = (FlowType)data_int(attribute_first_data(attr));

  flow->text_handle.id = HANDLE_MOVE_TEXT;
  flow->text_handle.type = HANDLE_MINOR_CONTROL;
  flow->text_handle.connect_type = HANDLE_NONCONNECTABLE;
  flow->text_handle.connected_to = NULL;
  flow->text_handle.pos = flow->text->position;
  obj->handles[2] = &flow->text_handle;

  extra->start_long = 
    extra->end_long =
    extra->start_trans = FLOW_WIDTH/2.0;
  extra->end_trans = MAX(FLOW_WIDTH, FLOW_ARROWLEN) / 2.0;
  
  flow->textpos = flow->text->position;
  flow_update_data(flow);
  
  return &flow->connection.object;
}

struct TypeChange {
  ObjectChange obj_change;
  int old_type, new_type;
};

static void
type_change_free(struct TypeChange *change)
{
}

static void
type_change_apply(struct TypeChange *change, DiaObject *obj)
{
  Flow *flow = (Flow*)obj;

  flow->type = change->new_type;
  flow_update_data(flow);
}

static void
type_change_revert(struct TypeChange *change, DiaObject *obj)
{
  Flow *flow = (Flow*)obj;

  flow->type = change->old_type;
  flow_update_data(flow);
}

static ObjectChange *
type_create_change(Flow *flow, int type)
{
  struct TypeChange *change;

  change = g_new0(struct TypeChange, 1);

  change->obj_change.apply = (ObjectChangeApplyFunc) type_change_apply;
  change->obj_change.revert = (ObjectChangeRevertFunc) type_change_revert;
  change->obj_change.free = (ObjectChangeFreeFunc) type_change_free;

  change->old_type = flow->type;
  change->new_type = type;

  return (ObjectChange *)change;
}


static ObjectChange *
flow_set_type_callback (DiaObject* obj, Point* clicked, gpointer data)
{
  ObjectChange *change;

  change = type_create_change((Flow *)obj, GPOINTER_TO_INT(data));
  change->apply(change, obj);

  return change;
}

static DiaMenuItem flow_menu_items[] = {
  { N_("Energy"), flow_set_type_callback, (void*)FLOW_ENERGY, 1 },
  { N_("Material"), flow_set_type_callback, (void*)FLOW_MATERIAL, 1 },
  { N_("Signal"), flow_set_type_callback, (void*)FLOW_SIGNAL, 1 },
};

static DiaMenu flow_menu = {
  "Flow",
  sizeof(flow_menu_items)/sizeof(DiaMenuItem),
  flow_menu_items,
  NULL
};

static DiaMenu *
flow_get_object_menu(Flow *flow, Point *clickedpoint)
{
  /* Set entries sensitive/selected etc here */
  flow_menu_items[0].active = 1;
  return &flow_menu;
}
