static char *sccs_id= (sccs_id, "@(#)PolyShape.c	3.10 7/28/92");

//$PolyShape,PolySketcher,SplineStretcher$
#include "Class.h"
#ifdef ET25
#include "Math.h"
#endif
#include "PolyShape.h"
#include "DrawView.h"
#include "DrawDocument.h"

static short PolyImage[]= {
#   include  "images/PolyShape.im"
};

//---- PolyShape ---------------------------------------------------------------

MetaImpl(PolyShape, (T(npts), TV(pts, npts), 0));

PolyShape::PolyShape()
{
    SetFlag(eShapeSplit);
    type= ePolyDefault;
    size= npts= 0;
    pts= 0;
    ink= 0;
}
	
PolyShape::~PolyShape()
{
    SafeDelete(pts);
    SafeDelete(gpts);
}

short *PolyShape::GetImage()
{
    return PolyImage;
}

float PolyShape::GetLength() const
{
    double tot= 0;

    for (int i= 0; i < npts-1; i++)
	tot+= GeoDist(gpts[i], gpts[i+1]);
    return tot;
}


void PolyShape::Moveby(Point delta)
{
    Shape::Moveby(delta);
    for (int i= 0; i < npts; i++) {
	gpts[i]= GeoPos(this, pts[i]+bbox.origin);        
    }
}

void PolyShape::AreaChanged()
{
    Shape::AreaChanged();
    for (int i= 0; i < npts; i++) {
	pts[i]= gpts[i]-bbox.origin;
    }
}

int PolyShape::AddPt(Point p, GeoPos *gp)
{
    if (npts >= size) {
#ifdef ET25
	size= Math::Max(10, npts*2);
#else
	size= max(10, npts*2);
#endif
	pts= (Point*) Realloc(pts, size * sizeof(Point));
    }
    p-= bbox.origin;
    pts[npts++]= p;

    GeoPos old_pos;
    if (npts > 1)
      old_pos= gpts[0];

    SafeDelete(gpts);
    gpts= new GeoPos[npts];

    // Leave first and last point unchanged...
    if (npts > 1)
      gpts[0]= old_pos;
    for (int i= 1; i < npts-1; i++) {
      gpts[i]= GeoPos(this, pts[i]+bbox.origin);
    }
    gpts[npts-1]= gp ? GeoPos(this, gp->GetLong(), gp->GetLat()) :
		  GeoPos(this, pts[npts-1]+bbox.origin);
    
    CalcBBox();
    Invalidate();
    Shape::Init(bbox.NW(), bbox.SE());

    return npts;
}


Point PolyShape::GetPt(int i)
{
    if (pts[0] == pts[npts-1]) {
	if (i < 0)
	    i= npts-1+i;
	i %= npts-1;
    } else
#ifdef ET25
	i= Math::Range(0, npts-1, i);
#else
	i= range(0, npts-1, i);
#endif
    return pts[i] + bbox.origin;
}

void PolyShape::SetSpan(Rectangle r)
{
    Shape::SetSpan(r);
}

void PolyShape::CalcBBox()
{
    Point oldorigin= bbox.origin;
    bbox= BoundingBox(npts, pts);
    bbox.origin+= oldorigin;
}

void PolyShape::MovePoint(int handle, Point delta, bool redraw, GeoPos *gpp)
{
    if (redraw)
	Invalidate();
    if (pts[0] == pts[npts-1] && handle == 0) {
	pts[npts-1]+= delta;
	gpts[npts-1]= gpp ?
		GeoPos(this, gpp->GetLong(), gpp->GetLat()):
		GeoPos(this, pts[npts-1]+bbox.origin);
    }
    pts[handle]+= delta;
    gpts[handle]= gpp ?
		GeoPos(this, gpp->GetLong(), gpp->GetLat()):
		GeoPos(this, pts[handle]+bbox.origin);
    if (redraw) {
	CalcBBox();
	Invalidate();
	Changed();
    }
    // ((DrawDocument*)GetView()->GetDocument())->ShowInfo(this);
}


bool PolyShape::CanSplit()
{
    return !GetSplit();
}

Point *PolyShape::MakeHandles(int *n)
{
    if (GetSplit()) {
	if (npts > 1 && pts[0] == pts[npts-1])
	    *n= npts-1;
	else
	    *n= npts;
	return pts;
    }
    return Shape::MakeHandles(n);
}

void PolyShape::Outline(Point p1, Point p2)
{
    GrStrokePolygon(NormRect(p1, p2).origin, pts, npts, type);
}

void PolyShape::StrokePartial(int s, int e)
{
    Point p[10];
    for (int i= 0; i < e-s+1; i++)
	p[i]= GetPt(s+i);

    GrSetPenSize(3);
    // GrSetInk(GetInk(NrInks()-1));
    GrStrokePolygon(gPoint0, p, e-s+1, type);
}

void PolyShape::Draw(Rectangle)
{
    GrFillPolygon(bbox.origin, pts, npts, type);
    GrStrokePolygon(bbox.origin, pts, npts, type);
}


ShapeSketcher *PolyShape::NewSketcher(DrawView *dv, SketchModes m)
{
    return new PolySketcher(dv, this, m);
}

ShapeStretcher *PolyShape::NewStretcher(DrawView *dv, int handle)
{
    if (GetSplit())
	return new SplineStretcher(dv, this, handle);
    return ((LineShape*)this)->LineShape::NewStretcher(dv, handle);
}

int Inside_2D_Polygon(Point p, int numpoints, Point *data)
{
    int intersect= 0, i, j;
    Point p1, p2;
    int x3, y4;

    for (i= 0; i < numpoints; i++) {
	p1= data[i];
	p2= data[(i+1) % numpoints];
	if (p.y != p2.y) {
	    if (p2.y > p1.y)
		Swap(p1, p2);
	    if (p.y > p2.y && p.y < p1.y) {
		x3= p1.x;
		if (p1.y != p2.y)
		    x3-= (p1.x-p2.x) * (p1.y-p.y) / (p1.y-p2.y);
		if (x3 < p.x)
		    intersect++;
	    }
	} else {
	    if (p2.x < p.x) {
		if (p1.y == p2.y) {
		    if (((p1.x > p.x) && (p2.x < p.x))
					|| ((p1.x < p.x) && (p2.x > p.x)))
			intersect++;
		} else {
		    for (j= i; j < numpoints; j++) {
			y4= data[(j+1) % numpoints].y;
			if (y4 != p.y) {
			    if (((p1.y < p.y) && (y4 > p.y))
						|| ((p1.y > p.y) && (p.y > y4)))
				intersect++;
			    break;
			}
		    }
		}
	    }
	}
    }
    return intersect % 2;
}

bool PolyShape::ContainsPoint(Point p)
{
    p-= bbox.origin;
    if (penink != 0) {
	for (int i= 1; i < npts; i++)
	    if (((LineShape*)this)->LineShape::PointAtLine(p, pts[i-1], pts[i]))
		return TRUE;
    }
    if (ink != 0)
	return Inside_2D_Polygon(p, npts, pts);
    return FALSE;
}

void PolyShape::DelPoint(int handle, Point *del_point, GeoPos *del_gpoint)
{
    Invalidate();
    *del_point= pts[handle];
    *del_gpoint= gpts[handle];
    for (int i= handle; i < npts-1; i++) {
	pts[i]= pts[i+1];
	gpts[i]= gpts[i+1];
    }
    npts--;
    CalcBBox();
    Invalidate();
    Changed();
}

void PolyShape::InsPoint(int handle, Point del_point, GeoPos *del_gpoint)
{
    Invalidate();
    if (size == npts) {
      size+= 10;
      pts= (Point*) Realloc(pts, size * sizeof(Point));
      gpts= (GeoPos*) Realloc(gpts, size * sizeof(GeoPos));
    }
    for (int i= npts; i > handle; i--) {
	pts[i]= pts[i-1];
	gpts[i]= gpts[i-1];
    }
    pts[i]= del_point;
    gpts[i]= *del_gpoint;
    npts++;
    CalcBBox();
    Invalidate();
    Changed();
}


bool PolyShape::InjectPoint(Point p)
{
    Point orgp= p;
    p-= bbox.origin;
    if (penink != 0) {
	for (int i= 1; i < npts; i++)
	  if (((LineShape*)this)->LineShape::PointAtLine(p, pts[i-1], pts[i])) {
	    GeoPos ngp(this, orgp);
	    InsPoint(i, p, &ngp);
	    return TRUE;
	  }
    }
    return FALSE;
}

#ifdef ET25
OStream& PolyShape::PrintOn(OStream& s)
#else
ostream& PolyShape::PrintOn(ostream& s)
#endif
{
    Shape::PrintOn(s);
    s << type SP;
    s << npts SP;
    for (int i= 0; i < npts; i++) {
	s << pts[i] SP;
	gpts[i].Save(s);
    }
    return s;
}

#ifdef ET25
IStream& PolyShape::ReadFrom(IStream& s)
#else
istream& PolyShape::ReadFrom(istream& s)
#endif
{
    Shape::ReadFrom(s);
    s >> Enum(type);
    s >> npts;
    size= npts;
    SafeDelete(pts);
    pts= new Point[npts];
    SafeDelete(gpts);
    gpts= new GeoPos[npts];
    for (int i= 0; i<npts; i++) {
	s >> pts[i];
	gpts[i].Load(s);
    }
    return s;
}


//---- Stretcher Methods -------------------------------------------------------

SplineStretcher::SplineStretcher(DrawView *dv, Shape *sp, int h)
					: ShapeStretcher(dv, sp, h) 
{
    lsp= (PolyShape*) sp;
}
    
void SplineStretcher::TrackFeedback(Point, Point, bool)
{
    lsp->MovePoint(handle, delta, FALSE);
    lsp->StrokePartial(handle-1, handle+1);
    lsp->MovePoint(handle, -delta, FALSE);
}

void SplineStretcher::TrackConstrain(Point ap, Point pp, Point* np)
{
}

Command *SplineStretcher::TrackMouse(TrackPhase tp, Point ap, Point, Point np)
{
    extern bool	path_edit;
    Point	pnt;
    GeoPos	tgp;

    if (view->MatchPoint() && view->FindPoint(np, &pnt, &tgp)) {
      np= pnt;
      gp= tgp;
      matched_point= TRUE;
    } else {
      matched_point= FALSE;
    }

    view->ShowPos(np);
    delta= np-ap;
    path_edit= TRUE;
    return this;
}

void SplineStretcher::DoIt()
{
    if (delta == gPoint0) {
      lsp->DelPoint(handle, &del_point, &del_gpoint);
    } else
      lsp->MovePoint(handle, delta, TRUE, matched_point ? &gp: 0);
}

void SplineStretcher::UndoIt()
{
    if (delta == gPoint0) {
      lsp->InsPoint(handle, del_point, &del_gpoint);
    } else
      lsp->MovePoint(handle, -delta);
}

//---- Sketcher Methods --------------------------------------------------------

PolySketcher::PolySketcher(DrawView *dv, Shape *sp, SketchModes m)
							: (dv, sp, m) 
{
    SetFlag(eCmdMoveEvents);
    newshape= (Shape*) proto->Clone();
    Poly()->SetSimpleProperty(eShapePensize, 3);
    Poly()->SetSimpleProperty(eShapePenPattern, NrInks()-1);
    view->Insert(newshape);
}

void PolySketcher::TrackFeedback(Point ap, Point np, bool)
{
    GrSetPenSize(3);
    GrLine(ap, np);
}
 
Command *PolySketcher::TrackMouse(TrackPhase tp, Point ap, Point pp, Point np)
{
    Point	pnt;
    extern int	GEOMARGIN;
    // Point	margin(GEOMARGIN ? GEOMARGIN: 5);
    Point	margin(2);

    // if (tp != eTrackRelease)
    DrawCommand::TrackMouse(tp, ap, pp, np);
    view->ShowPos(np);
    view->ShowInfo(tp, "x: %4d y: %4d", np.x, np.y);
    if (tp == eTrackRelease) {
	int	n= Poly()->GetPtCnt();
	GeoPos	gp;

	if (n >= 2 && Abs(ap - Poly()->GetPt(n-1)) <= margin ) {
	    view->Remove(newshape);
	    ResetFlag(eCmdMoveEvents);
	} else if (n > 2 && Abs(ap - Poly()->GetPt(0)) <= margin) {
	    Poly()->AddPt(Poly()->GetPt(0), & Poly()->gpts[0]);
	    view->UpdateEvent();
	    view->Remove(newshape);
	    ResetFlag(eCmdMoveEvents);
	} else if (view->MatchPoint() && n >= 1
	  && view->FindPoint(ap, &pnt, &gp)) {
	    Poly()->AddPt(pnt, &gp);
	    view->UpdateEvent();
	    view->Remove(newshape);
	    ResetFlag(eCmdMoveEvents);
	} else {
	    if (n == 0 && view->MatchPoint() && view->FindPoint(ap, &pnt, &gp)) {
	      ap= pnt;
	      Poly()->AddPt(ap, &gp);
	    } else
	      Poly()->AddPt(ap);
	    view->UpdateEvent();
	}
    }
    return this;
}
