/*****
* 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