// --------------------------------------------------------------------
// Ipelet  for several cute little functions, like
// precise scale and rotate, precise boxes, marking circle centers
// --------------------------------------------------------------------
/*

    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 "ipelib.h"

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

class GoodiesIpelet : public Ipelet {
public:
  virtual int IpelibVersion() const { return IPELIB_VERSION; }
  virtual int NumFunctions() const { return 12; }
  virtual const char *Label() const { return "Goodies"; }
  virtual const char *SubLabel(int function) const;
  virtual void Run(int function, IpePage *page, IpeletHelper *helper);
};

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

const char * const sublabel[] = {
  "Mirror horizontal",
  "Mirror vertical",
  "Turn 90 degrees",
  "Turn 180 degrees",
  "Turn 270 degrees",
  "Precise rotate",
  "Precise stretch",
  "Insert precise box",
  "Insert bounding box",
  "Insert media box",
  "Mark circle center",
  "Make parabolas" };

const char *GoodiesIpelet::SubLabel(int function) const
{
  return sublabel[function];
}

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

void MarkCircleCenter(IpePage *page, IpeletHelper *helper)
{
  IpePage::iterator it = page->PrimarySelection();
  if (it == page->end()) {
    helper->Message("Nothing selected");
    return;
  }
  IpePath *obj = it->Object()->AsPath();
  if (!obj || obj->NumSubPaths() > 1 ||
      obj->SubPath(0)->Type() != IpeSubPath::EEllipse) {
    helper->Message("Primary selection is not a circle");
    return;
  }
  IpeMatrix m = obj->Matrix() * obj->SubPath(0)->AsEllipse()->Matrix();
  IpeMark *mark = new IpeMark(helper->Attributes(), m.Translation());
  page->push_back(IpePgObject(IpePgObject::ESecondary,
			      helper->CurrentLayer(),
			      mark));
}

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

void BoundingBox(IpePage *page, IpeletHelper *helper)
{
  IpeRect box;
  for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) {
    if (it->Select())
      box.AddRect(it->BBox());
  }
  IpePath *obj = new IpePath(helper->Attributes(), box);
  page->push_back(IpePgObject(IpePgObject::ESecondary,
			      helper->CurrentLayer(),
			      obj));
}

void PreciseTransform(IpePage *page, IpeletHelper *helper, int mode)
{
  if (!page->HasSelection()) {
    helper->Message("Nothing selected");
    return;
  }

  IpeString str;
  if (mode > 4 && !helper->GetString((mode == 6) ? "Enter stretch factors" :
				     "Enter angle in degrees", str))
    return;

  IpeMatrix tfm;
  switch (mode) {
  case 0: // Mirror horizontal
    tfm = IpeLinear(-1, 0, 0, 1);
    break;
  case 1: // Mirror vertical
    tfm = IpeLinear(1, 0, 0, -1);
    break;
  case 2: // Turn 90 degrees
    tfm = IpeLinear(0, -1, 1, 0);
    break;
  case 3: // Turn 180 degrees
    tfm = IpeLinear(-1, 0, 0, -1);
    break;
  case 4: // Turn 270 degrees
    tfm = IpeLinear(0, 1, -1, 0);
    break;
  case 5: // Precise rotate
    tfm = IpeLinear(IpeAngle::Degrees(IpeLex(str).GetDouble()));
    break;
  case 6: { // Precise stretch
    IpeLex lex(str);
    double sx, sy;
    lex >> sx >> sy;
    tfm = IpeLinear(sx, 0, 0, sy);
  } break;
  }

  IpeVector origin;
  const IpeSnapData &sd = helper->SnapData();
  if (sd.iWithAxes) {
    origin = sd.iOrigin;
  } else {
    IpeRect box;
    for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) {
      if (it->Select())
	box.AddRect(it->BBox());
    }
    origin = 0.5 * (box.Min() + box.Max());
  }

  tfm = IpeMatrix(origin) * tfm * IpeMatrix(-origin);
  for (IpePage::iterator it = page->begin(); it != page->end(); ++it) {
    if (it->Select())
      it->Transform(tfm);
  }
}

const double dpmm = 72.0 / 25.4;

void PreciseBox(IpePage *page, IpeletHelper *helper)
{
  IpeString str;
  if (!helper->GetString("Enter size in mm (width height)", str))
    return;
  IpeLex lex(str);
  double sx, sy;
  lex >> sx >> sy;
  IpeRect box(IpeVector::Zero, IpeVector(sx * dpmm, sy * dpmm));

  IpePath *obj = new IpePath(helper->Attributes(), box);
  page->push_back(IpePgObject(IpePgObject::ESecondary,
			      helper->CurrentLayer(),
			      obj));
}

void MediaBox(IpePage *page, IpeletHelper *helper)
{
  IpeRect box = helper->Document()->Properties().iMedia;
  IpePath *obj = new IpePath(helper->Attributes(), box);
  page->push_back(IpePgObject(IpePgObject::ESecondary,
			      helper->CurrentLayer(),
			      obj));
}

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

class ParabolaVisitor : public IpeVisitor {
public:
  virtual void VisitMark(const IpeMark *obj);
public:
  std::vector<IpeVector> iPos;
};

void ParabolaVisitor::VisitMark(const IpeMark *obj)
{
  iPos.push_back(obj->Matrix() * obj->Position());
}

void Parabola(IpePage *page, IpeletHelper *helper)
{
  IpePage::iterator it = page->PrimarySelection();
  if (it == page->end()) {
    helper->Message("Nothing selected");
    return;
  }
  const IpePath *p = it->Object()->AsPath();
  if (!p || p->NumSubPaths() > 1 ||
      p->SubPath(0)->Type() != IpeSubPath::ESegments ||
      p->SubPath(0)->AsSegs()->NumSegments() > 1 ||
      p->SubPath(0)->AsSegs()->Segment(0).Type() != IpePathSegment::ESegment) {
    helper->Message("Primary selection is not a line segment");
    return;
  }

  ParabolaVisitor visitor;
  for (IpePage::iterator it = page->begin(); it != page->end(); ++it) {
    if (it->Select() == IpePgObject::ESecondary) {
      visitor(*it);
    }
  }

  IpePathSegment ps = p->SubPath(0)->AsSegs()->Segment(0);
  IpeVector p0 = p->Matrix() * ps.CP(0);
  IpeVector p1 = p->Matrix() * ps.CP(1);

  // tfm maps the positive x-axis to p0..p1
  IpeMatrix tfm = IpeMatrix(p0) * IpeLinear((p1 - p0).Angle());
  IpeMatrix inv = tfm.Inverse();
  // treat x-interval from 0 to xmax
  double xmax = (p1 - p0).Len();

  for (uint i = 0; i < visitor.iPos.size(); ++i) {
    IpeVector mrk = inv * visitor.iPos[i];
    double a = -mrk.iX;
    double b = xmax - mrk.iX;

    // the following are the three control points for the unit parabola
    // between x = a and x = b
    IpeVector q0(a, a*a);
    IpeVector q1(0.5*(a + b), a*b);
    IpeVector q2(b, b*b);

    // create parabola
    IpeSegmentSubPath *sp = new IpeSegmentSubPath;
    sp->AppendQuad(q0, q1, q2);
    IpePath *obj = new IpePath(helper->Attributes());
    obj->AddSubPath(sp);

    // now stretch in y-direction and move
    double stretch = 2.0 * mrk.iY;
    IpeVector offs(mrk.iX, mrk.iY / 2.0);
    IpeMatrix m = tfm * IpeMatrix(offs) * IpeLinear(1, 0, 0, 1.0/stretch);
    obj->SetMatrix(m);
    page->push_back(IpePgObject(IpePgObject::ESecondary,
				helper->CurrentLayer(),
				obj));
  }
}

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

void GoodiesIpelet::Run(int function, IpePage *page, IpeletHelper *helper)
{
  switch (function) {
  case 0:
  case 1:
  case 2:
  case 3:
  case 4:
  case 5:
  case 6:
    PreciseTransform(page, helper, function);
    break;
  case 7:
    PreciseBox(page, helper);
    break;
  case 8:
    BoundingBox(page, helper);
    break;
  case 9:
    MediaBox(page, helper);
    break;
  case 10:
    MarkCircleCenter(page, helper);
    break;
  case 11:
    Parabola(page, helper);
    break;
  default:
    break;
  }
}

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

IPELET_DECLARE Ipelet *NewIpelet()
{
  return new GoodiesIpelet;
}

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