/*****
 * drawpath.cc
 * Andy Hammerlindl 2002/06/06
 *
 * Stores a path that has been added to a picture.
 *****/

#include <sstream>
#include <vector>
#include <cfloat>

#include "drawpath.h"
#include "psfile.h"
#include "util.h"

using vm::array;
using vm::read;

namespace camp {

double PatternLength(double arclength, const array& pat,
                     bool cyclic, double penwidth)
{
  double sum=0.0;

  size_t n=pat.size();
  for(unsigned i=0; i < n; i ++)
    sum += read<double>(pat,i)*penwidth;

  if(sum == 0.0) return 0.0;

  if(n % 2 == 1) sum *= 2.0; // On/off pattern repeats after 2 cycles.

  double pat0=read<double>(pat,0);
  // Fix bounding box resolution problem. Example:
  // asy -f pdf testlinetype; gv -scale -2 testlinetype.pdf
  if(!cyclic && pat0 == 0) sum += 1.0e-3*penwidth;

  double terminator=(cyclic && arclength >= 0.5*sum) ? 0.0 : pat0*penwidth;
  int ncycle=(int)((arclength-terminator)/sum+0.5);

  return (ncycle >= 1 || terminator >= 0.75*arclength) ?
    ncycle*sum+terminator : 0.0;
}

bool isdashed(pen& p) {
  const LineType *linetype=p.linetype();
  size_t n=linetype->pattern.size();
  return n > 0;
}

pen adjustdash(pen& p, double arclength, bool cyclic)
{
  pen q=p;
  // Adjust dash sizes to fit arclength; also compensate for linewidth.
  const LineType *linetype=q.linetype();
  size_t n=linetype->pattern.size();

  if(n > 0) {
    double penwidth=linetype->scale ? q.width() : 1.0;
    double factor=penwidth;

    if(linetype->adjust && arclength) {
      double denom=PatternLength(arclength,linetype->pattern,cyclic,penwidth);
      if(denom != 0.0) factor *= arclength/denom;
    }

    if(factor != 1.0)
      q.adjust(max(factor,0.1));
  }
  return q;
}

// Account for square or extended pen cap contributions to bounding box.
void cap(bbox& b, double t, path p, pen pentype)
{
  transform T=pentype.getTransform();

  double h=0.5*pentype.width();
  pair v=p.dir(t);
  transform S=rotate(conj(v))*shiftless(T);
  double xx=S.getxx(), xy=S.getxy();
  double yx=S.getyx(), yy=S.getyy();
  double y=hypot(yx,yy);
  if(y == 0.0) return;
  double numer=xx*yx+xy*yy;
  double x=numer/y;
  pair z=shift(T)*p.point(t);

  switch(pentype.cap()) {
    case SquareCap:
    {
      pair d=rotate(v)*pair(x,y)*h;
      b += z+d;
      b += z-d;
      break;
    }
    case RoundCap:
    {
      b += pad(z,pentype.bounds());
      break;
    }
    case ExtendedCap:
    {
      transform R=rotate(v);
      double w=(xx*yy-xy*yx)/y;
      pair dp=R*pair(x+w,y)*h;
      pair dm=R*pair(x-w,y)*h;
      b += z+dp;
      b += z+dm;
      b += z-dp;
      b += z-dm;
      break;
    }
  }
}

// Account for square or extended pen cap contributions to bounding box.
void join(bbox& b, double t, path p, pen pentype)
{
  transform T=pentype.getTransform();

  double h=0.5*pentype.width();
  pair v=p.dir(t);
  transform S=rotate(conj(v))*shiftless(T);
  double xx=S.getxx(), xy=S.getxy();
  double yx=S.getyx(), yy=S.getyy();
  double y=hypot(yx,yy);
  if(y == 0.0) return;
  double numer=xx*yx+xy*yy;
  double x=numer/y;
  pair z=shift(T)*p.point(t);

  switch(pentype.join()) {
    case MiterJoin:
    {
      double phi=angle(-p.predir(t),false)-angle(p.postdir(t),false);
      static const double twopi=2.0*pi;
      if(phi > pi) phi -= twopi;
      if(phi < -pi) phi += twopi;
      double s=sin(0.5*phi);
      if(s != 0.0) {
        double factor=1.0/s;
        if(abs(factor) < pentype.miter()) {
          b += z-rotate(v)*pair(x,y)*h*factor;
          break;
        }
      }
      // Fall through
    }
    case BevelJoin:
    {
      pair Z=pair(x,y)*h;
      pair d=rotate(p.predir(t))*Z;
      b += z+d;
      b += z-d;
      pair D=rotate(p.postdir(t))*Z;
      b += z+D;
      b += z-D;
      break;
    }
    case RoundJoin:
    {
      b += pad(z,pentype.bounds());
      break;
    }
  }
}

void drawPathPenBase::strokebounds(bbox& b, const path& p)
{
  Int l=p.length();
  if(l < 0) return;

  b += p.internalbounds(pentype.bounds());

  unsigned int n=p.size();
  if(p.cyclic())
    join(b,0,p,pentype);

  for(unsigned int i=1; i < n-1; ++i)
    join(b,i,p,pentype);

  if(p.cyclic()) {
    join(b,n-1,p,pentype);
  } else {
    cap(b,0,p,pentype);
    cap(b,l,p,pentype);
  }
}

bool drawPath::draw(psfile *out)
{
  Int n = p.size();
  if (n == 0 || pentype.invisible())
    return true;

  pen q=isdashed(pentype) ?
    adjustdash(pentype,
               p.transformed(inverse(pentype.getTransform())).arclength(),
               p.cyclic()) :  pentype;

  penSave(out);
  penTranslate(out);

  if(n > 1)
    out->write(p);
  else
    out->dot(p,q);

  penConcat(out);

  out->setpen(q);

  out->stroke(q,n == 1);

  penRestore(out);

  return true;
}

drawElement *drawPath::transformed(const transform& t)
{
  return new drawPath(transpath(t),transpen(t),KEY);
}

} //namespace camp
