/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998 Alexander Larsson
 *
 * Objects for drawing KAOS goal diagrams.
 * This class supports the whole goal specialization hierarchy
 * Copyright (C) 2002 Christophe Ponsard
 *
 * Based on SADT box object
 * Copyright (C) 2000, 2001 Cyrille Chepelov
 *
 * Forked from Flowchart toolbox -- objects for drawing flowcharts.
 * Copyright (C) 1999 James Henstridge.
 *
 * 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 <string.h>
#include <stdio.h>

#include <glib.h>

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

#include "pixmaps/contributes.xpm"

typedef struct _Mbr Mbr;

typedef enum {
    MBR_CONTRIBUTES,
    MBR_OBSTRUCTS,
    MBR_CONFLICTS,
    MBR_RESP,
    MBR_MONITORS,
    MBR_CONTROLS,
    MBR_CAPABLEOF,
    MBR_PERFORMS,
    MBR_INPUT,
    MBR_OUTPUT
} MbrType;

struct _Mbr {
  Connection connection;

  MbrType type;

  Point pm;
  BezPoint line[3];
  Handle pm_handle;

  double text_width,text_ascent;

  int init;
};

#define HANDLE_MOVE_MID_POINT (HANDLE_CUSTOM1)

#define MBR_WIDTH 0.1
#define MBR_DASHLEN 0.5
#define MBR_DECFONTHEIGHT 0.7
#define MBR_ARROWLEN 0.8
#define MBR_ARROWWIDTH 0.5

#define MBR_FG_COLOR color_black
#define MBR_BG_COLOR color_white
#define MBR_RED_COLOR color_red

#define MBR_DEC_SIZE 1.0

static Color color_red = { 1.0f, 0.0f, 0.0f };

static DiaFont *mbr_font = NULL;

static ObjectChange* mbr_move_handle(Mbr *mbr, Handle *handle,
				   Point *to, ConnectionPoint *cp,
				   HandleMoveReason reason, ModifierKeys modifiers);
static ObjectChange* mbr_move(Mbr *mbr, Point *to);
static void mbr_select(Mbr *mbr, Point *clicked_point,
			      DiaRenderer *interactive_renderer);
static void mbr_draw(Mbr *mbr, DiaRenderer *renderer);
static DiaObject *mbr_create(Point *startpoint,
				 void *user_data,
				 Handle **handle1,
				 Handle **handle2);
static real mbr_distance_from(Mbr *mbr, Point *point);
static void mbr_update_data(Mbr *mbr);
static void mbr_destroy(Mbr *mbr);
static DiaObject *mbr_load(ObjectNode obj_node, int version,
			    const char *filename);

static PropDescription *mbr_describe_props(Mbr *mes);
static void mbr_get_props(Mbr * mbr, GPtrArray *props);
static void mbr_set_props(Mbr *mbr, GPtrArray *props);

static void compute_line(Point* p1, Point* p2, Point *pm, BezPoint* line);

static ObjectTypeOps mbr_type_ops =
{
  (CreateFunc) mbr_create,
  (LoadFunc)   mbr_load,/*using_properties*/     /* load */
  (SaveFunc)   object_save_using_properties,      /* save */
  (GetDefaultsFunc)   NULL,
  (ApplyDefaultsFunc) NULL
};

DiaObjectType kaos_mbr_type =
{
  "KAOS - mbr",              /* name */
  0,                         /* version */
  (char **)contributes_xpm,  /* pixmap */
  &mbr_type_ops              /* ops */
};

static ObjectOps mbr_ops = {
  (DestroyFunc)         mbr_destroy,
  (DrawFunc)            mbr_draw,
  (DistanceFunc)        mbr_distance_from,
  (SelectFunc)          mbr_select,
  (CopyFunc)            object_copy_using_properties,
  (MoveFunc)            mbr_move,
  (MoveHandleFunc)      mbr_move_handle,
  (GetPropertiesFunc)   object_create_props_dialog,
  (ApplyPropertiesFunc) object_apply_props_from_dialog,
  (ObjectMenuFunc)      NULL,
  (DescribePropsFunc)   mbr_describe_props,
  (GetPropsFunc)        mbr_get_props,
  (SetPropsFunc)        mbr_set_props
};

static PropEnumData prop_mbr_type_data[] = {
  { N_("Contributes"), MBR_CONTRIBUTES },
  { N_("Obstructs"),   MBR_OBSTRUCTS },
  { N_("Conflicts"),   MBR_CONFLICTS },
  { N_("Responsibility"),   MBR_RESP },
  { N_("Monitors"),    MBR_MONITORS },
  { N_("Controls"),    MBR_CONTROLS },
  { N_("CapableOf"),   MBR_CAPABLEOF },
  { N_("Performs"),    MBR_PERFORMS },
  { N_("Input"),       MBR_INPUT },
  { N_("Output"),      MBR_OUTPUT },
  { NULL, 0}
};

static PropDescription mbr_props[] = {
  CONNECTION_COMMON_PROPERTIES,
  { "type", PROP_TYPE_ENUM, PROP_FLAG_VISIBLE,
    N_("Type:"), NULL, prop_mbr_type_data },
  { "pm", PROP_TYPE_POINT, 0, "pm", NULL, NULL},
  PROP_DESC_END
};

static PropDescription *
mbr_describe_props(Mbr *mbr)
{
  if (mbr_props[0].quark == 0)
    prop_desc_list_calculate_quarks(mbr_props);
  return mbr_props;

}

static PropOffset mbr_offsets[] = {
  CONNECTION_COMMON_PROPERTIES_OFFSETS,
  { "type", PROP_TYPE_ENUM, offsetof(Mbr,type)},
  { "pm", PROP_TYPE_POINT, offsetof(Mbr,pm) },
  { NULL, 0, 0 }
};

static void
mbr_get_props(Mbr * mbr, GPtrArray *props)
{
  object_get_props_from_offsets(&mbr->connection.object,
                                mbr_offsets, props);
}

static void
mbr_set_props(Mbr *mbr, GPtrArray *props)
{
 if (mbr->init==-1) { mbr->init++; return; }

  object_set_props_from_offsets(&mbr->connection.object,
                                mbr_offsets, props);
  mbr_update_data(mbr);
}


static real
mbr_distance_from(Mbr *mbr, Point *point)
{
  return distance_bez_line_point(mbr->line, 3, 2*MBR_WIDTH, point);
}

static void
mbr_select(Mbr *mbr, Point *clicked_point,
	    DiaRenderer *interactive_renderer)
{
  connection_update_handles(&mbr->connection);
}

static ObjectChange*
mbr_move_handle(Mbr *mbr, Handle *handle,
		 Point *to, ConnectionPoint *cp,
		 HandleMoveReason reason, ModifierKeys modifiers)
{
  Point p1, p2;
  Point *endpoints;

  assert(mbr!=NULL);
  assert(handle!=NULL);
  assert(to!=NULL);

  if (handle->id == HANDLE_MOVE_MID_POINT) {
    mbr->pm = *to;
  } else  {
    endpoints = &mbr->connection.endpoints[0];
    p1.x = 0.5*(endpoints[0].x + endpoints[1].x);
    p1.y = 0.5*(endpoints[0].y + endpoints[1].y);
    connection_move_handle(&mbr->connection, handle->id, to, cp, reason, modifiers);
    p2.x = 0.5*(endpoints[0].x + endpoints[1].x);
    p2.y = 0.5*(endpoints[0].y + endpoints[1].y);
    point_sub(&p2, &p1);
    point_add(&mbr->pm, &p2);
  }

  mbr_update_data(mbr);
  return NULL;
}

static ObjectChange*
mbr_move(Mbr *mbr, Point *to)
{
  Point start_to_end;
  Point *endpoints = &mbr->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(&mbr->pm, &delta);

  mbr_update_data(mbr);
  return NULL;
}

static void
compute_line(Point* p1, Point* p2, Point *pm, BezPoint* line) {
  double dx,dy,k;
  double dx1,dy1,k1;
  double dx2,dy2,k2;
  double OFF=1.0;

  dx=p2->x-p1->x;
  dy=p2->y-p1->y;
  k=sqrt(dx*dx+dy*dy);
  if (k!=0) {
    dx=dx/k;
    dy=dy/k;
  } else {
    dx=0;
    dy=1;
  }

  dx1=pm->x-p1->x;
  dy1=pm->y-p1->y;
  k1=sqrt(dx*dx+dy*dy);
  if (k1!=0) {
    dx1=dx1/k;
    dy1=dy1/k;
  } else {
    dx1=0;
    dy1=1;
  }

  dx2=p2->x-pm->x;
  dy2=p2->y-pm->y;
  k2=sqrt(dx*dx+dy*dy);
  if (k2!=0) {
    dx2=dx2/k;
    dy2=dy2/k;
  } else {
    dx2=0;
    dy2=1;
  }

  line[0].type=BEZ_MOVE_TO;
  line[0].p1.x=p1->x;
  line[0].p1.y=p1->y;

  line[1].type=BEZ_CURVE_TO;
  line[1].p3.x=pm->x;
  line[1].p3.y=pm->y;
  line[1].p1.x=p1->x+dx1*OFF;
  line[1].p1.y=p1->y+dy1*OFF;
  line[1].p2.x=pm->x-dx*OFF;
  line[1].p2.y=pm->y-dy*OFF;

  line[2].type=BEZ_CURVE_TO;
  line[2].p3.x=p2->x;
  line[2].p3.y=p2->y;
  line[2].p1.x=pm->x+dx*OFF;
  line[2].p1.y=pm->y+dy*OFF;
  line[2].p2.x=p2->x-dx2*OFF;
  line[2].p2.y=p2->y-dy2*OFF;
}

static gchar* compute_text(Mbr *mbr) {
  gchar* annot;
  switch (mbr->type) {
    case MBR_RESP:      annot = g_strdup("Resp"); break;
    case MBR_MONITORS:  annot = g_strdup("Mon"); break;
    case MBR_CONTROLS:  annot = g_strdup("Ctrl"); break;
    case MBR_CAPABLEOF: annot = g_strdup("CapOf"); break;
    case MBR_PERFORMS:  annot = g_strdup("Perf"); break;
    case MBR_INPUT:     annot = g_strdup("In"); break;
    case MBR_OUTPUT:    annot = g_strdup("Out"); break;
    default:            annot = g_strdup("");
  }
  return annot;
}

/* drawing here -- TBD inverse flow ??  */
static void
mbr_draw(Mbr *mbr, DiaRenderer *renderer)
{
  DiaRendererClass *renderer_ops = DIA_RENDERER_GET_CLASS (renderer);
  Point *endpoints;
  Point p1,p2,pm1,pm2;
  Point pa1,pa2;
  Arrow arrow;
  DiaImage img;
  gchar *annot;
  double k,dx,dy,dxn,dyn,dxp,dyp;

  /* some asserts */
  assert(mbr != NULL);
  assert(renderer != NULL);

  /* arrow type */
  if (mbr->type!=MBR_CONFLICTS)
    arrow.type = ARROW_FILLED_TRIANGLE;
  else
    arrow.type = ARROW_NONE;
  arrow.length = MBR_ARROWLEN;
  arrow.width = MBR_ARROWWIDTH;

  endpoints = &mbr->connection.endpoints[0];

  /* assigning a mid-range bitmap for some metarelations */
  img=NULL; /* no image */

  /* some computations */
  p1 = endpoints[0];     /* could reverse direction here */
  p2 = endpoints[1];

  /** drawing directed line **/
  renderer_ops->set_linewidth(renderer, MBR_WIDTH);
  renderer_ops->set_linecaps(renderer, LINECAPS_BUTT);
  renderer_ops->set_linestyle(renderer, LINESTYLE_SOLID);

  dx=p1.x-p2.x;
  dy=p1.y-p2.y;
  k=sqrt(dx*dx+dy*dy)*2/MBR_DEC_SIZE;

  if (k<0.05) {  /* bug fix for closed bezier */
    renderer_ops->draw_line_with_arrows(renderer,&p1,&p2,MBR_WIDTH,&MBR_FG_COLOR,NULL, &arrow);
  } else {
    renderer_ops->draw_bezier_with_arrows(renderer, mbr->line, 3, MBR_WIDTH, &MBR_FG_COLOR, NULL, &arrow);
  }

  /** drawing vector decoration  **/

  /* red line for obstruction */
  k=k*2/MBR_DEC_SIZE;
  dxn=dx/k;
  dyn=dy/k;
  dxp=-dyn;
  dyp=dxn;

  if (mbr->type==MBR_OBSTRUCTS) {
    pm1.x=mbr->pm.x-dxp;
    pm1.y=mbr->pm.y-dyp;
    pm2.x=mbr->pm.x+dxp;
    pm2.y=mbr->pm.y+dyp;

    renderer_ops->set_linewidth(renderer, MBR_WIDTH*2);
    renderer_ops->draw_line_with_arrows(renderer,&pm1,&pm2,MBR_WIDTH,&MBR_RED_COLOR,NULL,NULL);
  }

  if (mbr->type==MBR_CONFLICTS) {
    pm1.x=mbr->pm.x-dxn-dxp;
    pm1.y=mbr->pm.y-dyn-dyp;
    pm2.x=mbr->pm.x+dxn+dxp;
    pm2.y=mbr->pm.y+dyn+dyp;

    renderer_ops->set_linewidth(renderer, MBR_WIDTH*2);
    renderer_ops->draw_line_with_arrows(renderer,&pm1,&pm2,MBR_WIDTH,&MBR_RED_COLOR,NULL,NULL);

    pm1.x=mbr->pm.x-dxn+dxp;
    pm1.y=mbr->pm.y-dyn+dyp;
    pm2.x=mbr->pm.x+dxn-dxp;
    pm2.y=mbr->pm.y+dyn-dyp;

    renderer_ops->draw_line_with_arrows(renderer,&pm1,&pm2,MBR_WIDTH,&MBR_RED_COLOR,NULL,NULL);
  }


  /** writing decoration text **/
  annot=compute_text(mbr);
  renderer_ops->set_font(renderer, mbr_font, MBR_DECFONTHEIGHT);

  if (annot && strlen(annot) != 0) {
      pa1.x=mbr->pm.x-mbr->text_width/2;
      pa1.y=mbr->pm.y-mbr->text_ascent +0.1;  /* with some fix... */
      pa2.x=pa1.x+mbr->text_width;
      pa2.y=pa1.y+MBR_DECFONTHEIGHT    +0.1;  /* with some fix... */
      renderer_ops->fill_rect(renderer,&pa1,&pa2,&color_white);
      renderer_ops->draw_string(renderer,annot,&mbr->pm,ALIGN_CENTER,&MBR_FG_COLOR);
  }

  g_free(annot);
}

/* creation here */
static DiaObject *
mbr_create(Point *startpoint, void *user_data, Handle **handle1, Handle **handle2)
{
  Mbr *mbr;
  Connection *conn;
  LineBBExtras *extra;
  DiaObject *obj;

  if (mbr_font == NULL) {
    mbr_font = dia_font_new_from_style(DIA_FONT_SANS, MBR_DECFONTHEIGHT);
  }

  mbr = g_malloc0(sizeof(Mbr));

  conn = &mbr->connection;
  conn->endpoints[0] = *startpoint;
  conn->endpoints[1] = *startpoint;
  conn->endpoints[1].y -= 2;

  obj = &conn->object;
  extra = &conn->extra_spacing;
  switch (GPOINTER_TO_INT(user_data)) {
    case 1: mbr->type=MBR_CONTRIBUTES; break;
    case 2: mbr->type=MBR_OBSTRUCTS; break;
    case 3: mbr->type=MBR_CONFLICTS; break;
    case 4: mbr->type=MBR_RESP; break;
    case 5: mbr->type=MBR_MONITORS; break;
    case 6: mbr->type=MBR_CONTROLS; break;
    case 7: mbr->type=MBR_CAPABLEOF; break;
    case 8: mbr->type=MBR_PERFORMS; break;
    case 9: mbr->type=MBR_INPUT; break;
    case 10: mbr->type=MBR_OUTPUT; break;
    default: mbr->type=MBR_CONTRIBUTES; break;
  }

  obj->type = &kaos_mbr_type;
  obj->ops = &mbr_ops;

  /* connectionpoint init */
  connection_init(conn, 3, 0);

  mbr->text_width = 0.0;
  mbr->text_ascent = 0.0;

  mbr->pm.x=0.5*(conn->endpoints[0].x + conn->endpoints[1].x);
  mbr->pm.y=0.5*(conn->endpoints[0].y + conn->endpoints[1].y);
  mbr->pm_handle.id = HANDLE_MOVE_MID_POINT;
  mbr->pm_handle.type = HANDLE_MINOR_CONTROL;
  mbr->pm_handle.connect_type = HANDLE_NONCONNECTABLE;
  mbr->pm_handle.connected_to = NULL;
  obj->handles[2] = &mbr->pm_handle;

  compute_line(&conn->endpoints[0],&conn->endpoints[1],&mbr->pm,mbr->line);

  extra->start_long =
  extra->start_trans =
  extra->end_long = MBR_WIDTH/2.0;
  extra->end_trans = MAX(MBR_WIDTH,MBR_ARROWLEN)/2.0;

  mbr_update_data(mbr);

  *handle1 = obj->handles[0];
  *handle2 = obj->handles[1];

  /* bug workaround */
  if (GPOINTER_TO_INT(user_data)!=0) mbr->init=-1; else mbr->init=0;

  return &mbr->connection.object;
}

static void
mbr_destroy(Mbr *mbr)
{
  connection_destroy(&mbr->connection);
}

static void
mbr_update_data(Mbr *mbr)
{
  Connection *conn = &mbr->connection;
  DiaObject *obj = &conn->object;
  Rectangle rect;
  Point p1,p2;
  Point p3,p4;
  gchar *text;

/* Too complex to easily decide */
/*
  if (connpoint_is_autogap(conn->endpoint_handles[0].connected_to) ||
      connpoint_is_autogap(conn->endpoint_handles[1].connected_to)) {
    connection_adjust_for_autogap(conn);
  }
*/
  obj->position = conn->endpoints[0];

  mbr->pm_handle.pos = mbr->pm;

  connection_update_handles(conn);
  connection_update_boundingbox(conn);

  /* text width */
  text=compute_text(mbr);
  mbr->text_width = dia_font_string_width(text, mbr_font, MBR_DECFONTHEIGHT);
  mbr->text_ascent = dia_font_ascent(text, mbr_font, MBR_DECFONTHEIGHT);

  /* endpoint */
  p1 = conn->endpoints[0];
  p2 = conn->endpoints[1];

 /* bezier */
  compute_line(&p1,&p2,&mbr->pm,mbr->line);

  /* Add boundingbox for mid decoration (slightly overestimated) : */
  p3.x=mbr->pm.x-MBR_DEC_SIZE;
  p3.y=mbr->pm.y-MBR_DEC_SIZE;
  p4.x=p3.x+MBR_DEC_SIZE*2;
  p4.y=p3.y+MBR_DEC_SIZE*2;
  rect.left=p3.x;
  rect.right=p4.x;
  rect.top=p3.y;
  rect.bottom=p4.y;
  rectangle_union(&obj->bounding_box, &rect);

  /* Add boundingbox for text: */
  rect.left = mbr->pm.x-mbr->text_width/2;
  rect.right = rect.left + mbr->text_width;
  rect.top = mbr->pm.y - mbr->text_ascent;
  rect.bottom = rect.top + MBR_DECFONTHEIGHT;
  rectangle_union(&obj->bounding_box, &rect);

  g_free(text);   /* free auxilliary text */
}

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