// --------------------------------------------------------------------
// Snapping
// --------------------------------------------------------------------
/*

    This file is part of the extensible drawing editor Ipe.
    Copyright (C) 1993-2004  Otfried Cheong

    Ipe 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.

    As a special exception, you have permission to link Ipe with the
    CGAL library and distribute executables, as long as you follow the
    requirements of the Gnu General Public License in regard to all of
    the software in the executable aside from CGAL.

    Ipe 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 Ipe; if not, you can find it at
    "http://www.gnu.org/copyleft/gpl.html", or write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "ipesnap.h"
#include "ipepage.h"
#include "ipevisitor.h"
#include "ipegroup.h"
#include "iperef.h"
#include "ipepath.h"

/*! \defgroup high Ipe Management
  \brief Classes to manage Ipe documents and objects.

  This module contains classes used in the implementation of the Ipe
  program itself.  They are in Ipelib because they are independant of
  the UI toolkit, and so are more easily accessible for a port of Ipe
  to another toolkit.

  The only classes from this module you may be interested in are
  IpeVisitor (which is essential to traverse an Ipe object structure),
  and perhaps IpeSnapData (if you are writing an Ipelet whose behavior
  depends on the current snap setting in the Ipe program).

*/

/*! \class IpeSnapData
  \ingroup high
  \brief Performs snapping operations, and stores snapping state.

*/

// --------------------------------------------------------------------

class CollectSegs : public IpeVisitor {
public:
  CollectSegs(const IpeVector &mouse, double snapDist,
	      const IpePage *page);

  virtual void VisitGroup(const IpeGroup *obj);
  virtual void VisitPath(const IpePath *obj);
  virtual void VisitReference(const IpeReference *obj);

public:
  std::vector<IpeSegment> iSegs;
  std::vector<IpeBezier> iBeziers;
  std::vector<IpeArc> iArcs;

private:
  std::vector<IpeMatrix> iMatrices;
  IpeVector iMouse;
  double iDist;
};

CollectSegs::CollectSegs(const IpeVector &mouse, double snapDist,
			 const IpePage *page)
  : iMouse(mouse), iDist(snapDist)
{
  iMatrices.push_back(IpeMatrix()); // identity matrix
  for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) {
    if (page->Layer(it->Layer()).IsSnapping())
      it->Object()->Accept(*this);
  }
}

void CollectSegs::VisitGroup(const IpeGroup *obj)
{
  iMatrices.push_back(iMatrices.back() * obj->Matrix());
  for (IpeGroup::const_iterator it = obj->begin(); it != obj->end(); ++it)
    (*it)->Accept(*this);
  iMatrices.pop_back();
}

void CollectSegs::VisitReference(const IpeReference *obj)
{
  if (obj->Object()) {
    iMatrices.push_back(iMatrices.back() * obj->Matrix());
    obj->Object()->Accept(*this);
    iMatrices.pop_back();
  }
}

// XXX use bounding boxes for subsegs/beziers to speed up ?
void CollectSegs::VisitPath(const IpePath *obj)
{
  IpeBezier b;
  IpeArc arc;
  IpeMatrix m = obj->Matrix();
  for (int i = 0; i < obj->NumSubPaths(); ++i) {
    const IpeSubPath *sp = obj->SubPath(i);
    switch (sp->Type()) {
    case IpeSubPath::EEllipse:
      if (sp->Distance(iMouse, m, iDist) < iDist)
	iArcs.push_back(m * IpeArc(sp->AsEllipse()->Matrix()));
      break;
    case IpeSubPath::EClosedSpline: {
      std::vector<IpeBezier> bez;
      sp->AsClosedSpline()->Beziers(bez);
      for (uint i = 0; i < bez.size(); ++i) {
	b = m * bez[i];
	if (b.Distance(iMouse, iDist) < iDist)
	  iBeziers.push_back(b);
      }
      break; }
    case IpeSubPath::ESegments: {
      const IpeSegmentSubPath *ssp = sp->AsSegs();
      int ns = ssp->Closed() ? -1 : 0;
      IpeVector u[2];
      for (int j = ns; j < ssp->NumSegments(); ++j) {
	IpePathSegment seg = j < 0 ? ssp->ClosingSegment(u) : ssp->Segment(j);
	switch (seg.Type()) {
	case IpePathSegment::ESegment:
	  if (seg.Distance(iMouse, m, iDist) < iDist)
	    iSegs.push_back(IpeSegment(m * seg.CP(0), m * seg.CP(1)));
	  break;
	case IpePathSegment::EBezier:
	case IpePathSegment::EQuad:
	  b = m * seg.Bezier();
	  if (b.Distance(iMouse, iDist) < iDist)
	    iBeziers.push_back(b);
	  break;
	case IpePathSegment::EArc:
	  arc = m * seg.Arc();
	  if (arc.Distance(iMouse, iDist) < iDist)
	    iArcs.push_back(arc);
	  break;
	case IpePathSegment::ESpline: {
	  std::vector<IpeBezier> bez;
	  seg.Beziers(bez);
	  for (uint i = 0; i < bez.size(); ++i) {
	    b = m * bez[i];
	    if (b.Distance(iMouse, iDist) < iDist)
	      iBeziers.push_back(b);
	  }
	  break; }
	}
      }
      break; }
    }
  }
}

// --------------------------------------------------------------------

/*! Find line through \a base with slope determined by angular snap
  size and direction. */
IpeLine IpeSnapData::GetLine(const IpeVector &mouse,
			     const IpeVector &base) const
{
  IpeAngle alpha = iDir;
  IpeVector d = mouse - base;

  if (d.Len() > 2.0) {
    alpha = d.Angle() - iDir;
    alpha.Normalize(0.0);
    alpha = iAngleSize * int(alpha / iAngleSize + 0.5) + iDir;
  }
  return IpeLine(base, IpeVector(alpha));
}

//! Perform intersection snapping.
bool IpeSnapData::IntersectionSnap(IpeVector &pos, const IpePage *page,
				   double snapDist) const
{
  CollectSegs segs(pos, snapDist, page);

  double d = snapDist;
  IpeVector pos1 = pos;
  IpeVector v;
  double d1;

  for (uint i = 0; i < segs.iSegs.size(); i++) {
    for (uint j = i+1; j < segs.iSegs.size(); j++) {
      if (segs.iSegs[i].Intersects(segs.iSegs[j], v) &&
	  (d1 = (pos - v).Len()) < d) {
	d = d1;
	pos1 = v;
      }
    }
  }
  if (d < snapDist) {
    pos = pos1;
    return true;
  }
  return false;
}

//! Perform snapping to intersection of angular line and pos.
bool IpeSnapData::SnapAngularIntersection(IpeVector &pos, const IpeLine &l,
					  const IpePage *page,
					  double snapDist) const
{
  CollectSegs segs(pos, snapDist, page);

  double d = snapDist;
  IpeVector pos1 = pos;
  IpeVector v;
  double d1;

  for (std::vector<IpeSegment>::const_iterator it = segs.iSegs.begin();
       it != segs.iSegs.end(); ++it) {
    if (it->Intersects(l, v) && (d1 = (pos - v).Len()) < d) {
      d = d1;
      pos1 = v;
    }
  }

  for (std::vector<IpeArc>::const_iterator it = segs.iArcs.begin();
       it != segs.iArcs.end(); ++it) {
    if (it->Intersects(l, v) && (d1 = (pos - v).Len()) < d) {
      d = d1;
      pos1 = v;
    }
  }

  for (std::vector<IpeBezier>::const_iterator it = segs.iBeziers.begin();
       it != segs.iBeziers.end(); ++it) {
    if (it->Intersects(l, v) && (d1 = (pos - v).Len()) < d) {
      d = d1;
      pos1 = v;
    }
  }

  if (d < snapDist) {
    pos = pos1;
    return true;
  }
  return false;
}

//! Tries vertex, intersection, boundary, and grid snapping.
/*! If snapping occurred, \a pos is set to the new user space position. */
bool IpeSnapData::SimpleSnap(IpeVector &pos, const IpePage *page,
			     double snapDist) const
{
  double d = snapDist;
  IpeVector fifi = pos;

  // highest priority: vertex snapping
  if (iSnap & ESnapVtx) {
    for (IpePage::const_iterator it = page->begin();
	 it != page->end(); ++it) {
      if (page->Layer(it->Layer()).IsSnapping())
	it->SnapVtx(pos, fifi, d);
    }
  }
  if (iSnap & ESnapInt) {
    IntersectionSnap(pos, page, d);
  }

  // Return if snapping has occurred
  if (d < snapDist) {
    pos = fifi;
    return true;
  }

  // boundary snapping
  if (iSnap & ESnapBd) {
    for (IpePage::const_iterator it = page->begin();
	 it != page->end(); ++it) {
      if (page->Layer(it->Layer()).IsSnapping())
	it->SnapBnd(pos, fifi, d);
    }
    if (d < snapDist) {
      pos = fifi;
      return true;
    }
  }

  // grid snapping: alway occurs
  if (iSnap & ESnapGrid) {
    int grid = iGridSize;
    fifi.iX = grid * int(pos.iX / grid + 0.5);
    fifi.iY = grid * int(pos.iY / grid + 0.5);
    pos = fifi;
    return true;
  }
  return false;
}

//! Performs snapping of position \a pos.
/*! Returns \c true if snapping occurred. In that case \a pos is set
  to the new user space position.

  Automatic angular snapping occurs if \a autoOrg is not null --- the
  value is then used as the origin for automatic angular snapping.
*/
bool IpeSnapData::Snap(IpeVector &pos, const IpePage *page, double snapDist,
		       IpeVector *autoOrg) const
{
  // automatic angular snapping and angular snapping both on?
  if (autoOrg && (iSnap & ESnapAuto) && (iSnap & ESnapAngle)) {
    // only one possible point!
    IpeLine angular = GetLine(pos, iOrigin);
    IpeLine automat = GetLine(pos, *autoOrg);
    IpeVector v;
    if (angular.Intersects(automat, v)) {
      pos = v;
      return true;
    }
    // if the two lines do not intersect, use following case
  }

  // case of only one angular snapping mode
  if ((iSnap & ESnapAngle) || (autoOrg && (iSnap & ESnapAuto))) {
    IpeVector org;
    if (iSnap & ESnapAngle)
      org = iOrigin;
    else
      org = *autoOrg;
    IpeLine l = GetLine(pos, org);
    pos = l.Project(pos);
    if (iSnap & ESnapBd)
      SnapAngularIntersection(pos, l, page, snapDist);
    return true;
  }

  // we are not in any angular snapping mode
  return SimpleSnap(pos, page, snapDist);
}

//! Set axis origin and direction from edge near mouse.
/*! Returns \c true if successful. */
bool IpeSnapData::SetEdge(const IpeVector &pos, const IpePage *page)
{
  CollectSegs segs(pos, 0.001, page);

  if (!segs.iSegs.empty()) {
    IpeSegment seg = segs.iSegs.front();
    IpeLine l = seg.Line();
    iOrigin = l.Project(pos);
    IpeVector dir = l.Dir();
    if ((iOrigin - seg.iP).Len() > (iOrigin - seg.iQ).Len())
      dir = -dir;
    iDir = dir.Angle();
    return true;
  }

  // should also support IpeArc and IpeBezier
  return false;
}

// --------------------------------------------------------------------
