/*****
 * runpicture.in
 *
 * Runtime functions for picture operations.
 *
 *****/

pen      => primPen()
pair     => primPair()
triple   => primTriple()
path     => primPath()
path3     => primPath3()
picture* => primPicture()
Intarray*  => IntArray()
Intarray2*  => IntArray2()
realarray* => realArray()
realarray2* => realArray2()
patharray* => pathArray()  
penarray* => penArray()  
penarray2* => penArray2()  
pairarray* => pairArray()
pairarray2* => pairArray2()
triplearray* => tripleArray()
triplearray2* => tripleArray2()
transform => primTransform()
callableTransform* => transformFunction()
callablePen* => penFunction()

#include "picture.h"
#include "drawelement.h"
#include "path.h"
#include "array.h"
#include "arrayop.h"
#include "drawpath.h"
#include "drawfill.h"
#include "drawclipbegin.h"
#include "drawclipend.h"
#include "drawgsave.h"
#include "drawgrestore.h"
#include "drawgroup.h"
#include "drawverbatim.h"
#include "drawlabel.h"
#include "drawlayer.h"
#include "drawimage.h"
#include "drawpath3.h"
#include "drawsurface.h"

using namespace camp;
using namespace settings;
using namespace vm;

typedef array Intarray;
typedef array Intarray2;
typedef array realarray;
typedef array realarray2;
typedef array pairarray;
typedef array pairarray2;
typedef array triplearray;
typedef array triplearray2;
typedef array patharray;
typedef array penarray;
typedef array penarray2;

typedef callable callableTransform;
typedef callable callablePen;

using types::IntArray;
using types::IntArray2;
using types::realArray;
using types::realArray2;
using types::pairArray;
using types::pairArray2;
using types::tripleArray;
using types::tripleArray2;
using types::pathArray;
using types::penArray;
using types::penArray2;

function *transformFunction()
{
  return new function(primTransform());
}

function *penFunction()
{
  return new function(primPen(),primInt(),primInt());
}

// Ignore unclosed begingroups but not spurious endgroups.
const char *nobegin="endgroup without matching begingroup";
  
array *emptyarray=new array(0);

array *nop(array *a) 
{
  return a;
}
  
triple Zero;

// Autogenerated routines:


picture* :newPicture()
{
  return new picture();
}

bool empty(picture *f)
{
  return f->null();
}

void erase(picture *f)
{
  f->nodes.clear();
}

pair min(picture *f)
{
  return f->bounds().Min();
}

pair max(picture *f)
{
  return f->bounds().Max();
}

pair size(picture *f)
{
  bbox b=f->bounds();
  return b.Max()-b.Min();
}

void _draw(picture *f, path g, pen p)
{
  f->append(new drawPath(g,p));
}

void fill(picture *f, patharray *g, pen p=CURRENTPEN, bool copy=true)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  f->append(new drawFill(*copyarray(g),false,p));
}

void latticeshade(picture *f, patharray *g, bool stroke=false,
                  pen fillrule=CURRENTPEN, penarray2 *p, transform t=identity, 
                  bool copy=true)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  f->append(new drawLatticeShade(*copyarray(g),stroke,fillrule,*copyarray(p),
                                 t));
}

void axialshade(picture *f, patharray *g, bool stroke=false, pen pena, pair a,
                bool extenda=true, pen penb, pair b, bool extendb=true,
                bool copy=true)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  f->append(new drawAxialShade(*copyarray(g),stroke,pena,a,extenda,penb,b,
                               extendb));
}

void radialshade(picture *f, patharray *g, bool stroke=false, pen pena,
                 pair a, real ra, bool extenda=true, pen penb, pair b, real rb,
                 bool extendb=true, bool copy=true)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  f->append(new drawRadialShade(*copyarray(g),stroke,pena,a,ra,extenda,
                                penb,b,rb,extendb));
}

void gouraudshade(picture *f, patharray *g, bool stroke=false,
                  pen fillrule=CURRENTPEN, penarray *p, pairarray *z,
                  Intarray *edges, bool copy=true)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  checkArrays(p,z);
  checkArrays(z,edges);
  f->append(new drawGouraudShade(*copyarray(g),stroke,fillrule,*copyarray(p),
                                 *copyarray(z),*copyarray(edges)));
}

void gouraudshade(picture *f, patharray *g, bool stroke=false,
                  pen fillrule=CURRENTPEN, penarray *p, Intarray *edges,
                  bool copy=true)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  size_t n=checkArrays(p,edges);
  size_t m=checkArray(g);
  array *z=new array(n);
  Int k=0;
  Int in=(Int) n;
  for(size_t j=0; j < m; ++j) {
    path *P=read<path *>(g,j);
    assert(P);
    Int stop=Min(P->size(),in-k);
    mem::vector<solvedKnot>& nodes=P->Nodes();
    for(Int i=0; i < stop; ++i)
      (*z)[k++]=nodes[i].point;
  }
  checkArrays(p,z);
  
  f->append(new drawGouraudShade(*copyarray(g),stroke,fillrule,*copyarray(p),
                                 *z,*copyarray(edges)));
}

void tensorshade(picture *f, patharray *g, bool stroke=false,
                 pen fillrule=CURRENTPEN, penarray2 *p, patharray *b=NULL,
                 pairarray2 *z=emptyarray, bool copy=true)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  array *(*copyarray2)(array *a)=copy ? copyArray2: nop;
  if(b == NULL) b=g;
  size_t n=checkArrays(p,b);
  size_t nz=checkArray(z);
  if(nz != 0)
    checkEqual(nz,n);
  f->append(new drawTensorShade(*copyarray(g),stroke,fillrule,*copyarray2(p),
                                *copyarray(b),*copyarray2(z)));
}

void functionshade(picture *f, patharray *g, bool stroke=false,
                   pen fillrule=CURRENTPEN, string shader=emptystring,
                   bool copy=true)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  f->append(new drawFunctionShade(*copyarray(g),stroke,fillrule,shader));
}

// Clip a picture to a superpath using the given fill rule.
// Subsequent additions to the picture will not be affected by the clipping.
void clip(picture *f, patharray *g, bool stroke=false,
          pen fillrule=CURRENTPEN, bool copy=true)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  drawClipBegin *begin=new drawClipBegin(*copyarray(g),stroke,fillrule,true);
  f->enclose(begin,new drawClipEnd(true,begin));
}

void beginclip(picture *f, patharray *g, bool stroke=false,
               pen fillrule=CURRENTPEN, bool copy=true)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  f->append(new drawClipBegin(*copyarray(g),stroke,fillrule,false));
}

void endclip(picture *f)
{
  f->append(new drawClipEnd(false));
}

void gsave(picture *f)
{
  f->append(new drawGsave());
}

void grestore(picture *f)
{
  f->append(new drawGrestore());
}

void begingroup(picture *f)
{
  f->append(new drawBegin());
}

void endgroup(picture *f)
{
  f->append(new drawEnd());
}

void _begingroup3(picture *f, string name, real compression,
                 real granularity, bool closed, bool tessellate,
                 bool dobreak, bool nobreak, triple center, Int interaction)
{
  f->append(new drawBegin3(name,compression,granularity,
                           closed,tessellate,dobreak,nobreak,
                           center,(Interaction) intcast(interaction)));
}

void endgroup3(picture *f)
{
  f->append(new drawEnd3());
}

void add(picture *dest, picture *src)
{
  dest->add(*src);
}

void prepend(picture *dest, picture *src)
{
  dest->prepend(*src);
}

void postscript(picture *f, string s)
{
  f->append(new drawVerbatim(PostScript,s));
}

void tex(picture *f, string s)
{
  f->append(new drawVerbatim(TeX,s));
}

void postscript(picture *f, string s, pair min, pair max)
{
  f->append(new drawVerbatim(PostScript,s,min,max));
}

void tex(picture *f, string s, pair min, pair max)
{
  f->append(new drawVerbatim(TeX,s,min,max));
}

void texpreamble(string s)
{
  string t=s+"\n";
  processDataStruct &pd=processData();
  pd.TeXpipepreamble.push_back(t);
  pd.TeXpreamble.push_back(t);
}

void deletepreamble() 
{
  if(getSetting<bool>("inlinetex")) {
    unlink(buildname(outname(),"pre").c_str());
  }
}

void _labelpath(picture *f, string s, string size, path g, string justify,
                pair offset, pen p)
{
  f->append(new drawLabelPath(s,size,g,justify,offset,p));
}

void texreset()
{
  processDataStruct &pd=processData();
  pd.TeXpipepreamble.clear();
  pd.TeXpreamble.clear();
  pd.tex.pipeclose();
}

void layer(picture *f)
{
  f->append(new drawLayer());
}

void newpage(picture *f)
{
  f->append(new drawNewPage());
}

void _image(picture *f, realarray2 *data, pair initial, pair final,
            penarray *palette=NULL, transform t=identity, bool copy=true,
            bool antialias=false)
{
  array *(*copyarray)(array *a)=copy ? copyArray: nop;
  array *(*copyarray2)(array *a)=copy ? copyArray2: nop;
  f->append(new drawPaletteImage(*copyarray2(data),*copyarray(palette),
                            t*matrix(initial,final),antialias));
}

void _image(picture *f, penarray2 *data, pair initial, pair final,
            transform t=identity, bool copy=true, bool antialias=false)
{
  array *(*copyarray2)(array *a)=copy ? copyArray2: nop;
  f->append(new drawNoPaletteImage(*copyarray2(data),t*matrix(initial,final),
                                   antialias));
}

void _image(picture *f, callablePen *F, Int width, Int height,
            pair initial, pair final, transform t=identity, bool antialias=false)
{
  f->append(new drawFunctionImage(Stack,F,width,height,
                                  t*matrix(initial,final),antialias));
}

string nativeformat()
{
  return nativeformat();
}

bool latex()
{
  return latex(getSetting<string>("tex"));
}

bool pdf()
{
  return pdf(getSetting<string>("tex"));
}

void shipout(string prefix=emptystring, picture *f, picture *preamble=NULL,
             string format=emptystring, bool wait=false, bool view=true,
             callableTransform *xform)
{
  if(prefix.empty()) prefix=outname();

  picture *result=new picture;
  unsigned level=0;
  picture::nodelist::iterator p;

  // If null is given as an xform, just use the identity transformation.
  bool xformIsNull = xform == nullfunc::instance();

  for(p = f->nodes.begin(); p != f->nodes.end(); ++p) {
    if (!xformIsNull)
      xform->call(Stack);
    transform t=xformIsNull ? camp::identity : pop<transform>(Stack);

    static transform Zero=transform(0.0,0.0,0.0,0.0,0.0,0.0);
    bool Delete=(t == Zero);
    picture *group=new picture;
    assert(*p);
    if((*p)->endgroup()) error(nobegin);
    if((*p)->begingroup()) {
      ++level;
      while(p != f->nodes.end() && level) {
        if(!Delete) {
          drawElement *e=t.isIdentity() ? *p : (*p)->transformed(t);
          group->append(e);
        }
        ++p;
        if(p == f->nodes.end()) break;
        assert(*p);
        if((*p)->begingroup()) ++level;
        if((*p)->endgroup()) {
          if(level) --level;
          else error(nobegin);
        }
      }
    }
    if(p == f->nodes.end()) break;
    assert(*p);
    if(!Delete) {
      drawElement *e=t.isIdentity() ? *p : (*p)->transformed(t);
      group->append(e);
      result->add(*group);
    }
  }
    
  result->shipout(preamble,prefix,format,0.0,wait,view);
}

void shipout3(string prefix, picture *f, string format=emptystring,
              real width, real height, real angle, real zoom,
              triple m, triple M, pair shift, realarray2 *t,
              realarray *background, triplearray *lights, realarray2 *diffuse,
              realarray2 *ambient, realarray2 *specular,
              bool viewportlighting, bool view=true)
{
  size_t n=checkArrays(lights,diffuse);
  checkEqual(n,checkArray(ambient));
  checkEqual(n,checkArray(specular));
  
  real *T,*Background,*Diffuse,*Ambient,*Specular;
  triple *Lights;

  copyArray2C(T,t,true,4);
  copyArrayC(Background,background);
  copyArrayC(Lights,lights);
  copyArray2C(Diffuse,diffuse,false,4,UseGC);
  copyArray2C(Ambient,ambient,false,4,UseGC);
  copyArray2C(Specular,specular,false,4,UseGC);
    
  f->shipout3(prefix,format,width,height,angle,zoom,m,M,shift,T,Background,n,
              Lights,Diffuse,Ambient,Specular,viewportlighting,view);
  
  delete[] Background;
  delete[] T;
}

void shipout3(string prefix, picture *f)
{
  f->shipout3(prefix);
}

void deconstruct(picture *f, picture *preamble=NULL, real magnification=1, 
                 callableTransform *xform)
{
  unsigned level=0;
  unsigned n=0;

  string prefix=outname();
  const string xformat="png";

  static long arg_max=sysconf(_SC_ARG_MAX);
  const unsigned maxargs=::min(arg_max/(prefix.size()+xformat.size()+25ul),
                               256ul);
  openpipeout();

  fprintf(pipeout,"%d\n",maxargs);
  fflush(pipeout);
  
  string preformat=nativeformat();
  const string Done="Done";
  const string Error="Error";
  
  mem::vector<string> cmd;
  
  // Enforce ghostscript limitations.
  magnification=::max(magnification,0.0001);
  real res=::min(::max(magnification*72.0,2.0),8192.0);
  
  const char *converter=NULL, *hint=NULL;
  
  if(magnification > 0.0) {
    mem::list<string> nameStack;
    string outname;
    unsigned arg=0;
    unsigned batch=0;
    for(picture::nodelist::iterator p=f->nodes.begin();;) {
      if(p == f->nodes.end()) break;
      if(arg == 0) {
        cmd.clear();
        ostringstream buf;
        buf << batch << "_";
        outname=buildname(prefix+buf.str()+"%d",xformat,"");
        converter="gs";
        hint="Ghostscript";
        cmd.push_back(getSetting<string>(converter));
        cmd.push_back("-q");
        cmd.push_back("-dNOPAUSE");
        cmd.push_back("-dBATCH");
        cmd.push_back("-sDEVICE=pngalpha");
        cmd.push_back("-dEPSCrop");
        if(safe)
          cmd.push_back("-dSAFER");
        cmd.push_back("-r"+String(res)+"x"+String(res));
        cmd.push_back("-sOutputFile="+outname);
      }
      
      picture *group=new picture;
      xform->call(Stack);
      transform t=pop<transform>(Stack);
      assert(*p);
      if((*p)->endgroup()) {
        fprintf(pipeout,"%s\n",Error.c_str());
        fflush(pipeout);
        error(nobegin);
      }
      if((*p)->begingroup()) {
        ++level;
        while(p != f->nodes.end() && level) {
          drawElement *e=t.isIdentity() ? *p : (*p)->transformed(t);
          group->append(e);
          ++p;
          if(p == f->nodes.end()) break;
          assert(*p);
          if((*p)->begingroup()) ++level;
          if((*p)->endgroup()) {
            if(level) --level;
            else {
              fprintf(pipeout,"%s\n",Error.c_str());
              fflush(pipeout);
              error(nobegin);
            }
          }
        }
      }
      if(p != f->nodes.end()) {
        assert(*p);
        drawElement *e=t.isIdentity() ? *p : (*p)->transformed(t);
        group->append(e);
        bbox b;
        ostringstream buf;
        buf << prefix << "_" << n;
        group->shipout(preamble,buf.str(),preformat,magnification,false,false);
        string Preformat=group->Transparency() ? "pdf" : preformat;
        string name=buildname(buf.str(),Preformat);
        nameStack.push_back(name);
        cmd.push_back(name);
        b=group->bounds();
        b *= magnification;
      
        fprintf(pipeout,"%g %g %g %g\n",b.left,b.right,b.bottom,b.top);
        fflush(pipeout);
        ++n;
        ++p;
        ++arg;
      }
      
      if(p == f->nodes.end() || arg >= maxargs) {
        arg=0;
        ++batch;
        fflush(pipeout);
  
        int status=System(cmd,0,true,converter,hint);
        if(status) {
          fprintf(pipeout,"%s\n",Error.c_str());
          fflush(pipeout);
          error("deconstruct failed");
        }
      }
    }
    
    if(!getSetting<bool>("keep")) {
      for(mem::list<string>::iterator p=nameStack.begin();
          p != nameStack.end(); ++p)
        unlink(p->c_str());
    }
    
    fprintf(pipeout,"%s\n",Done.c_str());
    fflush(pipeout);
  }
}


// Three-dimensional picture and surface operations

// Bezier curve
void _draw(picture *f, path3 g, triple center=Zero, pen p, Int interaction=0)
{
  if(g.size() > 0)
    f->append(new drawPath3(g,center,p,(Interaction) intcast(interaction)));
}

// Bezier patch
void draw(picture *f, triplearray2 *P, triple center, bool straight,
          penarray *p, real opacity, real shininess, real PRCshininess,
          triple normal, penarray *colors, Int interaction, bool prc=true)
{
  f->append(new drawSurface(*P,center,straight,*p,opacity,shininess,
                            PRCshininess,normal,*colors,
                            (Interaction) intcast(interaction),prc));
}

// General NURBS curve
void draw(picture *f, triplearray *P, realarray *knot,
          realarray *weights=emptyarray, pen p)
{
  f->append(new drawNurbsPath3(*P,knot,weights,p));
}

// General NURBS surface
void draw(picture *f, triplearray2 *P, realarray *uknot, realarray *vknot,
          realarray2 *weights=emptyarray, penarray *p, real opacity,
          real shininess, real PRCshininess, penarray *colors)
{
  f->append(new drawNurbs(*P,uknot,vknot,weights,*p,opacity,shininess,
                          PRCshininess,*colors));
}

// PRC unit sphere
void drawPRCsphere(picture *f, realarray2 *t, bool half=false, penarray *p,
                   real opacity, real shininess, Int type)
{
  f->append(new drawSphere(*t,half,*p,opacity,shininess,intcast(type)));
}

// PRC unit cylinder
void drawPRCcylinder(picture *f, realarray2 *t, penarray *p, real opacity,
                     real shininess)
{
  f->append(new drawCylinder(*t,*p,opacity,shininess));
}

// PRC unit disk
void drawPRCdisk(picture *f, realarray2 *t, penarray *p, real opacity,
                 real shininess)
{
  f->append(new drawDisk(*t,*p,opacity,shininess));
}

// General PRC tube
void drawPRCtube(picture *f, path3 center, path3 g, penarray *p, real opacity,
                 real shininess)
{
  f->append(new drawTube(center,g,*p,opacity,shininess));
}

// Draw pixel
void drawpixel(picture *f, triple v, pen p, real width=1.0)
{
  f->append(new drawPixel(v,p,width));
}

// Draw triangles
void draw(picture *f, triplearray *v, Intarray2 *vi,
          triplearray *n, Intarray2 *ni,
          penarray *p, real opacity, real shininess, real PRCshininess,
          penarray *c=emptyarray, Intarray2 *ci=emptyarray)
{
  f->append(new drawTriangles(*v,*vi,*n,*ni,*p,opacity,shininess,PRCshininess,
                              *c,*ci));
}

triple min3(picture *f)
{
  return f->bounds3().Min();
}

triple max3(picture *f)
{
  return f->bounds3().Max();
}

triple size3(picture *f)
{
  bbox3 b=f->bounds3();
  return b.Max()-b.Min();
}

pair minratio(picture *f)
{
  return f->ratio(::min);
}

pair maxratio(picture *f)
{
  return f->ratio(::max);
}

bool is3D(picture *f)
{
  return f->have3D();
}
