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

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <string.h> /* memcpy() */

#include "polyshape.h"
#include "message.h"
#include "diarenderer.h"

#define NUM_CONNECTIONS(poly) ((poly)->numpoints * 2 + 1)

enum change_type {
  TYPE_ADD_POINT,
  TYPE_REMOVE_POINT
};

struct PointChange {
  ObjectChange obj_change;

  enum change_type type;
  int applied;
  
  Point point;
  int pos;

  Handle *handle; /* owning ref when not applied for ADD_POINT
		     owning ref when applied for REMOVE_POINT */
  ConnectionPoint *cp1, *cp2;
};

static ObjectChange *
polyshape_create_change(PolyShape *poly, enum change_type type,
		       Point *point, int segment, Handle *handle,
		       ConnectionPoint *cp1, ConnectionPoint *cp2);

static void setup_handle(Handle *handle)
{
  handle->id = HANDLE_CORNER;
  handle->type = HANDLE_MAJOR_CONTROL;
  handle->connect_type = HANDLE_NONCONNECTABLE;
  handle->connected_to = NULL;
}


static int get_handle_nr(PolyShape *poly, Handle *handle)
{
  int i = 0;
  for (i=0;i<poly->numpoints;i++) {
    if (poly->object.handles[i] == handle)
      return i;
  }
  return -1;
}

ObjectChange*
polyshape_move_handle(PolyShape *poly, Handle *handle,
		      Point *to, ConnectionPoint *cp,
		      HandleMoveReason reason, ModifierKeys modifiers)
{
  int handle_nr;
  
  handle_nr = get_handle_nr(poly, handle);
  poly->points[handle_nr] = *to;
  
  return NULL;
}

ObjectChange*
polyshape_move(PolyShape *poly, Point *to)
{
  Point p;
  int i;
  
  p = *to;
  point_sub(&p, &poly->points[0]);

  poly->points[0] = *to;
  for (i=1;i<poly->numpoints;i++) {
    point_add(&poly->points[i], &p);
  }

  return NULL;
}

int
polyshape_closest_segment(PolyShape *poly, Point *point, real line_width)
{
  int i;
  real dist;
  int closest;

  dist = distance_line_point( &poly->points[poly->numpoints-1], &poly->points[0],
			      line_width, point);
  closest = poly->numpoints-1;
  for (i=0;i<poly->numpoints-1;i++) {
    real new_dist = 
      distance_line_point( &poly->points[i], &poly->points[i+1],
			   line_width, point);
    if (new_dist < dist) {
      dist = new_dist;
      closest = i;
    }
  }
  return closest;
}

Handle *
polyshape_closest_handle(PolyShape *poly, Point *point)
{
  int i;
  real dist;
  Handle *closest;
  
  closest = poly->object.handles[0];
  dist = distance_point_point( point, &closest->pos);
  for (i=1;i<poly->numpoints;i++) {
    real new_dist;
    new_dist = distance_point_point( point, &poly->points[i]);
    if (new_dist < dist) {
      dist = new_dist;
      closest = poly->object.handles[i];
    }
  }
  return closest;
}

real
polyshape_distance_from(PolyShape *poly, Point *point, real line_width)
{
  return distance_polygon_point(poly->points, poly->numpoints,
				line_width, point);
}

static void
add_handle(PolyShape *poly, int pos, Point *point, Handle *handle,
	   ConnectionPoint *cp1, ConnectionPoint *cp2)
{
  int i;
  DiaObject *obj;
  
  poly->numpoints++;
  poly->points = g_realloc(poly->points, poly->numpoints*sizeof(Point));

  for (i=poly->numpoints-1; i > pos; i--) {
    poly->points[i] = poly->points[i-1];
  }
  poly->points[pos] = *point;
  object_add_handle_at((DiaObject*)poly, handle, pos);
  object_add_connectionpoint_at((DiaObject*)poly, cp1, 2*pos);
  object_add_connectionpoint_at((DiaObject*)poly, cp2, 2*pos+1);

  obj = (DiaObject *)poly;
}

static void
remove_handle(PolyShape *poly, int pos)
{
  int i;
  DiaObject *obj;
  Handle *old_handle;
  ConnectionPoint *old_cp1, *old_cp2;

  obj = (DiaObject *)poly;

  /* delete the points */
  poly->numpoints--;
  for (i=pos; i < poly->numpoints; i++) {
    poly->points[i] = poly->points[i+1];
  }
  poly->points = g_realloc(poly->points, poly->numpoints*sizeof(Point));

  old_handle = obj->handles[pos];
  old_cp1 = obj->connections[2*pos];
  old_cp2 = obj->connections[2*pos+1];
  object_remove_handle(&poly->object, old_handle);
  object_remove_connectionpoint(obj, old_cp1);
  object_remove_connectionpoint(obj, old_cp2);
}


/* Add a point by splitting segment into two, putting the new point at
 'point' or, if NULL, in the middle */
ObjectChange *
polyshape_add_point(PolyShape *poly, int segment, Point *point)
{
  Point realpoint;
  Handle *new_handle;
  ConnectionPoint *new_cp1, *new_cp2;

  if (point == NULL) {
    realpoint.x = (poly->points[segment].x+poly->points[segment+1].x)/2;
    realpoint.y = (poly->points[segment].y+poly->points[segment+1].y)/2;
  } else {
    realpoint = *point;
  }

  new_handle = g_new(Handle, 1);
  new_cp1 = g_new0(ConnectionPoint, 1);
  new_cp1->object = &poly->object;
  new_cp2 = g_new0(ConnectionPoint, 1);
  new_cp2->object = &poly->object;
  setup_handle(new_handle);
  add_handle(poly, segment+1, &realpoint, new_handle, new_cp1, new_cp2);
  return polyshape_create_change(poly, TYPE_ADD_POINT,
				&realpoint, segment+1, new_handle,
				new_cp1, new_cp2);
}

ObjectChange *
polyshape_remove_point(PolyShape *poly, int pos)
{
  Handle *old_handle;
  ConnectionPoint *old_cp1, *old_cp2;
  Point old_point;
  
  old_handle = poly->object.handles[pos];
  old_point = poly->points[pos];
  old_cp1 = poly->object.connections[2*pos];
  old_cp2 = poly->object.connections[2*pos+1];
  
  object_unconnect((DiaObject *)poly, old_handle);

  remove_handle(poly, pos);

  polyshape_update_data(poly);
  
  return polyshape_create_change(poly, TYPE_REMOVE_POINT,
				&old_point, pos, old_handle,
				old_cp1, old_cp2);
}

/** Returns the first clockwise direction in dirs 
 * (as returned from find_slope_directions) */
static gint
first_direction(gint dirs) {
  switch (dirs) {
  case DIR_NORTHEAST: return DIR_NORTH;
  case DIR_SOUTHEAST: return DIR_EAST;
  case DIR_NORTHWEST: return DIR_WEST;
  case DIR_SOUTHWEST: return DIR_SOUTH;
  default: return dirs;
  }
}

/** Returns the last clockwise direction in dirs 
 * (as returned from find_slope_directions) */
static gint
last_direction(gint dirs) {
  switch (dirs) {
  case DIR_NORTHEAST: return DIR_EAST;
  case DIR_SOUTHEAST: return DIR_SOUTH;
  case DIR_NORTHWEST: return DIR_NORTH;
  case DIR_SOUTHWEST: return DIR_WEST;
  default: return dirs;
  }
}

/** Returns the available directions for a corner */
static gint 
find_tip_directions(Point prev, Point this, Point next)
{
  gint startdirs = find_slope_directions(prev, this);
  gint enddirs = find_slope_directions(this, next);
  gint firstdir = first_direction(startdirs);
  gint lastdir = last_direction(enddirs);
  gint dir, dirs = 0;

  dir = firstdir;
  while (dir != lastdir) {
    dirs |= dir;
    dir = dir * 2;
    if (dir == 16) dir = 1;
  }
  dirs |= dir;

  return dirs;
}

void
polyshape_update_data(PolyShape *poly)
{
  Point next;
  int i;
  DiaObject *obj = &poly->object;
  Point minp, maxp;
  
  /* handle the case of whole points array update (via set_prop) */
  if (poly->numpoints != obj->num_handles ||
      NUM_CONNECTIONS(poly) != obj->num_connections) {
    object_unconnect_all(obj); /* too drastic ? */

    obj->handles = g_realloc(obj->handles, 
                             poly->numpoints*sizeof(Handle *));
    obj->num_handles = poly->numpoints;
    for (i=0;i<poly->numpoints;i++) {
      obj->handles[i] = g_new(Handle, 1);
      setup_handle(obj->handles[i]);
    }

    obj->connections = g_realloc(obj->connections,
                                 NUM_CONNECTIONS(poly) * sizeof(ConnectionPoint *));
    for (i = 0; i < NUM_CONNECTIONS(poly); i++) {
      obj->connections[i] = g_new0(ConnectionPoint, 1);
      obj->connections[i]->object = obj;
    }
    obj->num_connections = NUM_CONNECTIONS(poly);
  }

  /* Update handles: */
  minp = maxp = poly->points[0];
  for (i = 0; i < poly->numpoints; i++) {
    gint thisdir, nextdir;
    Point prev;
    obj->handles[i]->pos = poly->points[i];

    if (i == 0)
      prev = poly->points[poly->numpoints-1];
    else
      prev = poly->points[i-1];
    if (i == poly->numpoints - 1)
      next = poly->points[0];
    else
      next = poly->points[i+1];
    point_add(&next, &poly->points[i]);
    point_scale(&next, 0.5);

    thisdir = find_tip_directions(prev, poly->points[i], next);
    nextdir = find_slope_directions(poly->points[i], next);

    obj->connections[2*i]->pos = poly->points[i];
    obj->connections[2*i]->directions = thisdir;
    obj->connections[2*i+1]->pos = next;
    obj->connections[2*i+1]->directions = nextdir;

    if (poly->points[i].x < minp.x) minp.x = poly->points[i].x;
    if (poly->points[i].x > maxp.x) maxp.x = poly->points[i].x;
    if (poly->points[i].y < minp.y) minp.y = poly->points[i].y;
    if (poly->points[i].y > maxp.y) maxp.y = poly->points[i].y;
  }

  obj->connections[obj->num_connections-1]->pos.x = (minp.x + maxp.x) / 2;
  obj->connections[obj->num_connections-1]->pos.y = (minp.y + maxp.y) / 2;
  obj->connections[obj->num_connections-1]->directions = DIR_ALL;
}

void
polyshape_update_boundingbox(PolyShape *poly)
{
  ElementBBExtras *extra;
  PolyBBExtras pextra;

  assert(poly != NULL);

  extra = &poly->extra_spacing;
  pextra.start_trans = pextra.end_trans = 
    pextra.start_long = pextra.end_long = 0;
  pextra.middle_trans = extra->border_trans;

  polyline_bbox(&poly->points[0],
                poly->numpoints,
                &pextra, TRUE,
                &poly->object.bounding_box);
}

void
polyshape_simple_draw(PolyShape *poly, DiaRenderer *renderer, real width)
{
  Point *points;
  
  assert(poly != NULL);
  assert(renderer != NULL);

  points = &poly->points[0];
  
  DIA_RENDERER_GET_CLASS(renderer)->set_linewidth(renderer, width);
  DIA_RENDERER_GET_CLASS(renderer)->set_linestyle(renderer, LINESTYLE_SOLID);
  DIA_RENDERER_GET_CLASS(renderer)->set_linejoin(renderer, LINEJOIN_ROUND);
  DIA_RENDERER_GET_CLASS(renderer)->set_linecaps(renderer, LINECAPS_BUTT);

  DIA_RENDERER_GET_CLASS(renderer)->draw_polygon(renderer, points, poly->numpoints,
			       &color_black);
}


void
polyshape_init(PolyShape *poly, int num_points)
{
  DiaObject *obj;
  int i;

  obj = &poly->object;

  object_init(obj, num_points, 2 * num_points + 1);
  
  poly->numpoints = num_points;

  poly->points = g_malloc(num_points*sizeof(Point));

  for (i = 0; i < num_points; i++) {
    obj->handles[i] = g_new(Handle, 1);

    obj->handles[i]->connect_type = HANDLE_NONCONNECTABLE;
    obj->handles[i]->connected_to = NULL;
    obj->handles[i]->type = HANDLE_MAJOR_CONTROL;
    obj->handles[i]->id = HANDLE_CORNER;
  }

  for (i = 0; i < NUM_CONNECTIONS(poly); i++) {
    obj->connections[i] = g_new0(ConnectionPoint, 1);
    obj->connections[i]->object = &poly->object;
    obj->connections[i]->flags = 0;
  }
  obj->connections[obj->num_connections - 1]->flags = CP_FLAGS_MAIN;
  

  /* Since the points aren't set yet, and update_data only deals with
     the points, don't call it (Thanks, valgrind!) */
  /*  polyshape_update_data(poly);*/
}

void
polyshape_set_points(PolyShape *poly, int num_points, Point *points)
{
  int i;

  poly->numpoints = num_points;

  if (poly->points) g_free(poly->points);
  poly->points = g_new(Point, num_points);

  for (i = 0; i < num_points; i++) {
    poly->points[i] = points[i];
  }
}

void
polyshape_copy(PolyShape *from, PolyShape *to)
{
  int i;
  DiaObject *toobj, *fromobj;

  toobj = &to->object;
  fromobj = &from->object;

  object_copy(fromobj, toobj);

  polyshape_set_points(to, from->numpoints, from->points);

  for (i=0;i<to->numpoints;i++) {
    toobj->handles[i] = g_new(Handle, 1);
    setup_handle(toobj->handles[i]);
    toobj->connections[2*i] = g_new0(ConnectionPoint, 1);
    toobj->connections[2*i]->object = &to->object;
    toobj->connections[2*i+1] = g_new0(ConnectionPoint, 1);
    toobj->connections[2*i+1]->object = &to->object;
  }
  toobj->connections[toobj->num_connections - 1] = g_new0(ConnectionPoint, 1);
  toobj->connections[toobj->num_connections - 1]->object = &to->object;

  memcpy(&to->extra_spacing,&from->extra_spacing,sizeof(to->extra_spacing));
  polyshape_update_data(to);
}

void
polyshape_destroy(PolyShape *poly)
{
  int i;
  Handle **temp_handles;
  ConnectionPoint **temp_cps;

  /* Need to store these temporary since object.handles is
     freed by object_destroy() */
  temp_handles = g_new(Handle *, poly->numpoints);
  for (i=0;i<poly->numpoints;i++)
    temp_handles[i] = poly->object.handles[i];

  temp_cps = g_new(ConnectionPoint *, NUM_CONNECTIONS(poly));
  for (i = 0; i < NUM_CONNECTIONS(poly); i++)
    temp_cps[i] = poly->object.connections[i];

  object_destroy(&poly->object);

  for (i=0;i<poly->numpoints;i++)
    g_free(temp_handles[i]);
  g_free(temp_handles);
  for (i = 0; i < NUM_CONNECTIONS(poly); i++)
    g_free(temp_cps[i]);
  g_free(temp_cps);
  
  g_free(poly->points);
}


void
polyshape_save(PolyShape *poly, ObjectNode obj_node)
{
  int i;
  AttributeNode attr;

  object_save(&poly->object, obj_node);

  attr = new_attribute(obj_node, "poly_points");
  
  for (i=0;i<poly->numpoints;i++) {
    data_add_point(attr, &poly->points[i]);
  }
}

void
polyshape_load(PolyShape *poly, ObjectNode obj_node) /* NOTE: Does object_init() */
{
  int i;
  AttributeNode attr;
  DataNode data;
  
  DiaObject *obj = &poly->object;

  object_load(obj, obj_node);

  attr = object_find_attribute(obj_node, "poly_points");

  if (attr != NULL)
    poly->numpoints = attribute_num_data(attr);
  else
    poly->numpoints = 0;

  object_init(obj, poly->numpoints, NUM_CONNECTIONS(poly));

  data = attribute_first_data(attr);
  poly->points = g_new(Point, poly->numpoints);
  for (i=0;i<poly->numpoints;i++) {
    data_point(data, &poly->points[i]);
    data = data_next(data);
  }

  for (i=0;i<poly->numpoints;i++) {
    obj->handles[i] = g_new(Handle, 1);
    setup_handle(obj->handles[i]);
  }
  for (i = 0; i < NUM_CONNECTIONS(poly); i++) {
    obj->connections[i] = g_new0(ConnectionPoint, 1);
    obj->connections[i]->object = obj;
  }
  obj->connections[obj->num_connections - 1]->flags = CP_FLAGS_MAIN;

  polyshape_update_data(poly);
}

static void
polyshape_change_free(struct PointChange *change)
{
  if ( (change->type==TYPE_ADD_POINT && !change->applied) ||
       (change->type==TYPE_REMOVE_POINT && change->applied) ){
    g_free(change->handle);
    g_free(change->cp1);
    g_free(change->cp2);
    change->handle = NULL;
    change->cp1 = NULL;
    change->cp2 = NULL;
  }
}

static void
polyshape_change_apply(struct PointChange *change, DiaObject *obj)
{
  change->applied = 1;
  switch (change->type) {
  case TYPE_ADD_POINT:
    add_handle((PolyShape *)obj, change->pos, &change->point,
	       change->handle, change->cp1, change->cp2);
    break;
  case TYPE_REMOVE_POINT:
    object_unconnect(obj, change->handle);
    remove_handle((PolyShape *)obj, change->pos);
    break;
  }
}

static void
polyshape_change_revert(struct PointChange *change, DiaObject *obj)
{
  switch (change->type) {
  case TYPE_ADD_POINT:
    remove_handle((PolyShape *)obj, change->pos);
    break;
  case TYPE_REMOVE_POINT:
    add_handle((PolyShape *)obj, change->pos, &change->point,
	       change->handle, change->cp1, change->cp2);
      
    break;
  }
  change->applied = 0;
}

static ObjectChange *
polyshape_create_change(PolyShape *poly, enum change_type type,
		       Point *point, int pos, Handle *handle,
		       ConnectionPoint *cp1, ConnectionPoint *cp2)
{
  struct PointChange *change;

  change = g_new(struct PointChange, 1);

  change->obj_change.apply = (ObjectChangeApplyFunc) polyshape_change_apply;
  change->obj_change.revert = (ObjectChangeRevertFunc) polyshape_change_revert;
  change->obj_change.free = (ObjectChangeFreeFunc) polyshape_change_free;

  change->type = type;
  change->applied = 1;
  change->point = *point;
  change->pos = pos;
  change->handle = handle;
  change->cp1 = cp1;
  change->cp2 = cp2;

  return (ObjectChange *)change;
}
