/*

    GEO, Postgres GIS (Geografic Information System) frontend

    Copyright (C) 1991  TNO Institute for Perception, The Netherlands
			Written by: Tom Vijlbrief

    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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.


Note that parts of this distribution are taken from the ET++
distribution. See the et2.2/ subdirectories for details.

===============================================================================
Tom Vijlbrief
TNO Institute for Perception		Phone: +31 34 63 562 11
P.O. Box 23				Fax:   +31 34 63 539 77
3769 ZG  Soesterberg			E-mail: tom@izf.tno.nl
The Netherlands				or: uunet!hp4nl.nluug.nl!tnosoes!tom
===============================================================================

*/

static char *sccs_id= (sccs_id, "@(#)IconShape.c	3.9 7/29/92");

#include "Class.h"
#include "OverView.h"
#include "PolyShape.h"
#include "IconShape.h"
#include "ObjList.h"

extern bool	map_show_points;

static const int NODE_DIM= 4;


static const int LabelVSpacing= 3;

static Font *icon_f;

MetaImpl(QueryShape,(
T(oid),T(ink),TVP(attr,n_attr),T(label_h),T(label_w),
T(bbox),T(font_h),T(labelpos),0));

QueryShape::QueryShape(MapView *d): dv(d)
{
  if (!icon_f)
    icon_f= new_Font(eFontHelvetica, 10, eFaceBold);
}

void QueryShape::SetBBox(Rectangle r)
{
  if (n_attr) {
    // Merge Label BBox with r: 
    bbox= r.Merge(Rectangle(labelpos-Point(2,font_h), Point(label_w,label_h)));
  } else
    bbox= r;
}

QueryShape::~QueryShape()
{
  if (attr) {
	for (int i= 0; i < n_attr; i++)
	  delete attr[i];
	delete attr;
  }
}

void QueryShape::SetAttr(char **a, bool transp)
{
  n_attr= 0;
  is_trans= transp;

  if (!a[0])
	return;

  char	**p= a;
  while (*p++)
	n_attr++;
  attr= new char*[n_attr];
  char **p2= attr;
  label_w= label_h= 0;
  for (p= a; *p; p++) {
	Metric	m;
	*p2++= strsave(*p);
	if ((m= icon_f->GetMetric(*p)).Width() > label_w)
	  label_w= m.Width();
	label_h+= (font_h= m.Height())+LabelVSpacing;
  }
  label_w+= 4;
  label_h+= 2;
}


static ObjList	*labels;

void QueryShape::Draw()
{
  if (n_attr == 0)
	return;

  if (!labels)
	labels= new ObjList;
  
  labels->Add(this);
}


void DrawAllLabels()
{
  if (labels) {
    labels->ForEach(QueryShape,DrawLabel)();
    labels->Empty(0);
  }
}


void QueryShape::DrawLabel()
{
  if (n_attr == 0)
	return;

  GrSetPenNormal();
  GrSetTextNormal();
  GrSetFont(icon_f);
  GrSetPenInk(ePatBlack);

  Point pos= labelpos;
  for (int i= 0; i < n_attr; i++) {

	if (!is_trans) {
	  Rectangle rec= icon_f->BBox(attr[i]);
	  rec.origin.y= -rec.origin.y;
	  rec.Moveby(pos-2);
	  rec.extent+= 4;
	  GrSetInk(ePatWhite);
	  GrFillRect(rec);
	  GrSetPenInk(ePatBlack);
	  GrStrokeRect(rec);
	}

	GrTextMoveto(pos);
	GrDrawString(attr[i]);
	pos.y+= LabelVSpacing + font_h;
  }
}


class Bitmap *NewIconBM(Point size, short *bm)
{
  if (size == Point(64,64))
    return new Bitmap(size, bm);

  int   xdim= (size.x+15)/16;
  short *nbm= new short[size.y * xdim];
  char *p, *q;
  int	x,y;

  for (y= 0; y < size.y; y++)
	for (x= 0,
	  q= ((char*) bm) + y*8 + (64-size.y)*4 + (32-size.x/2)/8,
	  p= (char*) &nbm[y*xdim] ; x < xdim; x++) {
		*p++= *q++; *p++= *q++;
	}

  Bitmap *rb= new Bitmap(size, nbm);

  // delete nbm; // not done because Storage is used by Bitmap class !!!
  return rb;
}


MetaImpl(IconShape,(T(origin),TP(bm),T(pickrec),0));

IconShape::IconShape(MapView *d, Bitmap *b): (d)
{
  bm= b;
}

void IconShape::CompBBox()
{
  Rectangle isbbox(origin, bm->Size());
  SetBBox(isbbox);
}

bool IconShape::ContainsPoint1(Point p)
{
  return pickrec.ContainsPoint(p);
}

inline int PointDistance(Point p1, Point p2)
{
  int x= abs(p1.x - p2.x);
  int y= abs(p1.y - p2.y);
  return x > y ? x: y;
}

int IconShape::Distance(Point p)
{
  return PointDistance(p, pickrec.Center());
}

void IconShape::Init(float lng, float lat)
{
  center= dv->GeoToPoint(lng,lat);
  origin= center - bm->Size()/2;
  pickrec= Rectangle(center-Point(8), Point(16,16));
  // labelpos= origin + Point(bm->Size().x, 2*font_h);
  labelpos= origin + Point(bm->Size().x, (3*font_h)/2);
}

void IconShape::Draw()
{
  QueryShape::Draw();
  
  Rectangle b(origin, bm->Size());
  GrPaintBitMap(b, bm, GetInk(ink));
  if (map_show_points) {
    GrSetPenInk(GetHlInk());
    GrStrokeRect(Rectangle(center.x-NODE_DIM/2,center.y-NODE_DIM/2,NODE_DIM,NODE_DIM));
  }
}


static char *get_pl_field(char **pp)
{
    static char		nstr[100];
    register char	*p= *pp;
    register char	*n;

    for (n= nstr; *p != ',' && *p != ')' && *p; p++)
	*n++= *p;
    if (*p)
	p++;
    *pp= p;
    *n= '\0';
    return nstr;
}


MetaImpl(PlineShape,(T(npoints),T(closed),T(width),0));

PlineShape::PlineShape(MapView *d, char *s): (d)
{
    if (!s)
      return;

    char	*p= ++s;

    closed= get_pl_field(&p)[0] == '1';
    npoints= atoi(get_pl_field(&p));

    if (!npoints) {
	pts= 0;
	return;
    }

    register int	i;
    register int	np= 0;
    Point		lastp, newp;
    // Prevent useless constructor call:
    char		pts_data[10000 * sizeof(Point)];
    register Point	*tpts= (Point*) pts_data;

    for (i= 0; i < npoints && np < 10000; i++) {
      float Long= atof(get_pl_field(&p));
      float Lat= atof(get_pl_field(&p));
      newp= dv->GeoToPoint(Long,Lat);
      if (newp == BADPOINT) {
	np= 1;
	break;
      }
      if (np == 0 || newp != lastp) {
        tpts[np]= lastp= newp;
	np++;
      }

      if (i == 0) {
	start_long= Long;
	start_lat= Lat;
      } else if (i == npoints - 1) {
	end_long= Long;
	end_lat= Lat;
      }
    }

    Rectangle bbox;
    if (np == 1) {
      npoints= 0;
      pts= 0;
      bbox= Rectangle(tpts[0], gPoint0);
    } else {
      npoints= np;
      // Prevent useless constructor call:
      pts= (Point*) new char[sizeof(Point) * npoints];
      bcopy(tpts, pts, npoints * sizeof(Point));
      bbox= NormRect(pts[0],pts[npoints-1]);
    }

    labelpos= bbox.Center();
    width= 1;
}


void PlineShape::CompBBox()
{
  if (npoints == 0)
    return;
    
  int xmin= pts[0].x;
  int ymin= pts[0].y;
  int xmax= xmin;
  int ymax= ymin;
  
  for (int i= 1 ; i < npoints; i++) {
    if (pts[i].x < xmin)
      xmin= pts[i].x;
    else if (pts[i].x > xmax)
      xmax= pts[i].x;
    if (pts[i].y < ymin)
      ymin= pts[i].y;
    else if (pts[i].y > ymax)
      ymax= pts[i].y;
  }
  int exp= width-1;
  SetBBox(NormRect(Point(xmin,ymin),Point(xmax,ymax)).Expand(Point(exp,exp)));
}

PlineShape::~PlineShape()
{
  // SafeDelete(pts);
  // Must use delete[] !!!
  if (pts)
    // delete [] pts;
    delete (char*) pts;
}

void PlineShape::Draw()
{
    QueryShape::Draw();
    if (npoints == 0)
      return;

    GrSetPenNormal();
    GrSetPenSize(width);
    if (closed) {
	GrSetInk(GetInk(ink));
	GrFillPolygon(gPoint0, pts, npoints, ePolyDefault);
    } else {
	GrSetPenInk(GetInk(ink));
	GrStrokePolygon(gPoint0, pts, npoints, ePolyDefault);
    }
    if (map_show_points) {
	Point pt;

	GrSetPenInk(GetHlInk());

	pt= pts[0];
	GrStrokeRect(Rectangle(
	    pt.x-NODE_DIM/2-1,pt.y-NODE_DIM/2-1,NODE_DIM+2,NODE_DIM+2));
	pt= pts[npoints-1];
	GrStrokeRect(Rectangle(
	    pt.x-NODE_DIM/2-1,pt.y-NODE_DIM/2-1,NODE_DIM+2,NODE_DIM+2));

	for (int i= 1; i < npoints-1; i++) {
	  pt= pts[i];
	  GrStrokeRect(Rectangle(
	    pt.x-NODE_DIM/2,pt.y-NODE_DIM/2,NODE_DIM,NODE_DIM));
	}
    }
}


static int PointDistLine(Point p, Point p1, Point p2)
{
    register int	dx= p2.x - p1.x, dy= p2.y - p1.y;
    Rectangle		r= NormRect(p1, p2);

    if (dx == 0) {
      if (dy == 0)
	return PointDistance(p, p1);
      r= r.Expand(Point(abs(dy),0));
    }
    if (dy == 0) {
      r= r.Expand(Point(0,abs(dx)));
    }

    if (! r.ContainsPoint(p)) {
	int d1= PointDistance(p, p1);
	int d2= PointDistance(p, p2);
	return d1 < d2 ? d1: d2;
    } else if (dx == 0) {
	dx= p1.x - p.x;
	return abs(dx);
    } else if (dy == 0) {
	dy= p1.y - p.y;
	return abs(dy);
    }

    int ax, ay;
    
    ax= (dy*(p.x-p1.x)/dx)+p1.y-p.y;
    ay= (dx*(p.y-p1.y)/dy)+p1.x-p.x;
    
    ax= abs(ax); ay= abs(ay);
    return ax < ay ? ax: ay;
}


static bool PointOnLine(Point p, Point delta, Point p1, Point p2)
{
    register int dx= p2.x - p1.x, dy= p2.y - p1.y, ax, ay;
    
    if (! NormRect(p1, p2).Expand(delta).ContainsPoint(p))
	return FALSE;

    if (dx == 0 || dy == 0)
	return TRUE;
	
    ax= (dy*(p.x-p1.x)/dx)+p1.y-p.y;
    ay= (dx*(p.y-p1.y)/dy)+p1.x-p.x;
    
    return abs(ax) <= delta.x || abs(ay) <= delta.y;
}

bool PlineShape::ContainsPoint1(Point p)
{
  if (!closed) {
    for (register int i= 0; i < npoints-1; i++) {
      if (PointOnLine(p, Point(width+1), pts[i], pts[i+1]))
        return TRUE;
    }
    return FALSE;
  } else {
    return Inside_2D_Polygon(p, npoints, pts);
  }
}

int PlineShape::Distance(Point p)
{
  if (!closed || !Inside_2D_Polygon(p, npoints, pts)) {
    register int md= 10000;
    register int nd;
    for (int i= 0; i < npoints-1; i++) {
      if ((nd= PointDistLine(p, pts[i], pts[i+1])) == 0)
        return 0;
      if (nd != -1 && nd < md)
	md= nd;
    }
    if (closed) {
      nd= PointDistLine(p, pts[0], pts[npoints-1]);
      if (nd != -1 && nd < md)
        md= nd;
    } else if ((md -= (width+1)/2) < 0) {
	md= 0;
    }
    return md == 10000 ? -1: md;
  } else {
    return bbox.extent.x > 20 && bbox.extent.y > 20 ? 2: 0;
    // always returning 0 would make it hard to select a small
    // shape which is situated on a large closed polygon !!!
  }
}
