/*****
 * runlabel.in
 *
 * Runtime functions for label operations.
 *
 *****/

pen      => primPen()
  pair     => primPair()
  path     => primPath()
  picture* => primPicture()
  transform => primTransform()
  realarray* => realArray()
  stringarray* => stringArray()
  penarray* => penArray()
  patharray* => pathArray()
  patharray2* => pathArray2()

#include "picture.h"
#include "drawlabel.h"
#include "locate.h"

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

typedef array realarray;
typedef array stringarray;
typedef array penarray;
typedef array patharray;
typedef array patharray2;

using types::realArray;
using types::stringArray;
using types::penArray;
using types::pathArray;
using types::pathArray2;

void cannotread(const string& s)
{
  ostringstream buf;
  buf << "Cannot read from " << s;
  error(buf);
}

void cannotwrite(const string& s)
{
  ostringstream buf;
  buf << "Cannot write to " << s;
  error(buf);
}

pair readpair(stringstream& s, double hscale=1.0, double vscale=1.0)
{
  double x,y;
  s >> y;
  s >> x;
  return pair(hscale*x,vscale*y);
}

string ASYx="/ASYx {( ) print ASYX sub 12 string cvs print} bind def";
string ASYy="/ASYy {( ) print ASYY sub 12 string cvs print} bind def";
string pathforall="{(M) print ASYy ASYx} {(L) print ASYy ASYx} {(C) print ASYy ASYx ASYy ASYx ASYy ASYx} {(c) print} pathforall";
string currentpoint="print currentpoint ASYy ASYx ";
string ASYinit="/ASYX currentpoint pop def /ASYY currentpoint exch pop def ";
string ASY1="ASY1 {"+ASYinit+"/ASY1 false def} if ";

void endpath(std::ostream& ps)
{
  ps << ASY1 << pathforall << " (M) " << currentpoint
     << "currentpoint newpath moveto} bind def" << endl;
}

void fillpath(std::ostream& ps)
{
  ps << "/fill {closepath ";
  endpath(ps);
}

void showpath(std::ostream& ps)
{
  ps << ASYx << newl
     << ASYy << newl
     << "/ASY1 true def" << newl
     << "/stroke {strokepath ";
  endpath(ps);
  fillpath(ps);
}

array *readpath(const string& psname, bool keep,
                double hscale=1.0, double vsign=1.0)
{
  double vscale=vsign*hscale;
  array *PP=new array(0);

  char *oldPath=NULL;
  string dir=stripFile(outname());
  if(!dir.empty()) {
    oldPath=getPath();
    setPath(dir.c_str());
  }

  mem::vector<string> cmd;
  cmd.push_back(getSetting<string>("gs"));
  cmd.push_back("-q");
  cmd.push_back("-dBATCH");
  cmd.push_back("-P");
  if(safe) cmd.push_back("-dSAFER");
#ifdef __MSDOS__
  const string null="NUL";
#else
  const string null="/dev/null";
#endif
  string psdriver=getSetting<string>("psdriver");
  cmd.push_back("-sDEVICE="+psdriver);
  cmd.push_back("-sOutputFile="+null);
  cmd.push_back(stripDir(psname));
  iopipestream gs(cmd,"gs","Ghostscript");
  while(gs.running()) {
    stringstream buf;
    string s=gs.readline();
    if(s.empty()) break;
    gs << newl;

// Workaround broken stringstream container in libc++.
#ifdef _LIBCPP_VERSION
    for(string::iterator i=s.begin(); i != s.end(); ++i) {
      if(isalpha(*i) && *i != 'e') {buf << " ";}
      buf << *i;
    }
#else
    buf << s;
#endif

    if(verbose > 2) cout << endl;

    mem::vector<solvedKnot> nodes;
    solvedKnot node;
    bool active=false;

    array *P=new array(0);
    PP->push(P);

    while(!buf.eof()) {
      char c='>';
      buf >> c;
      if(c == '>') break;

      switch(c) {
        case 'M':
        {
          node.pre=node.point=readpair(buf,hscale,vscale);
          node.straight=false;
          break;
        }
        case 'L':
        {
          pair point=readpair(buf,hscale,vscale);
          pair delta=(point-node.point)*third;
          node.post=node.point+delta;
          node.straight=true;
          nodes.push_back(node);
          active=true;
          node.pre=point-delta;
          node.point=point;
          break;
        }
        case 'C':
        {
          pair point=readpair(buf,hscale,vscale);
          pair pre=readpair(buf,hscale,vscale);
          node.post=readpair(buf,hscale,vscale);
          node.straight=false;
          nodes.push_back(node);
          active=true;
          node.pre=pre;
          node.point=point;
          break;
        }
        case 'c':
        {
          if(active) {
            if(node.point == nodes[0].point)
              nodes[0].pre=node.pre;
            else {
              pair delta=(nodes[0].point-node.point)*third;
              node.post=node.point+delta;
              nodes[0].pre=nodes[0].point-delta;
              node.straight=true;
              nodes.push_back(node);
            }
            P->push(path(nodes,nodes.size(),true)); // Discard noncyclic paths
            nodes.clear();
          }
          active=false;
          node.straight=false;
          break;
        }
      }
    }
  }

  if(oldPath != NULL)
    setPath(oldPath);

  if(!keep)
    unlink(psname.c_str());
  return PP;
}

// Autogenerated routines:


void label(picture *f, string *s, string *size, transform t, pair position,
           pair align, pen p)
{
  f->append(new drawLabel(*s,*size,t,position,align,p));
}

bool labels(picture *f)
{
  return f->havelabels();
}

realarray *texsize(string *s, pen p=CURRENTPEN)
{
  texinit();
  processDataStruct &pd=processData();

  string texengine=getSetting<string>("tex");
  setpen(pd.tex,texengine,p);

  double width,height,depth;
  texbounds(width,height,depth,pd.tex,*s);

  array *t=new array(3);
  (*t)[0]=width;
  (*t)[1]=height;
  (*t)[2]=depth;
  return t;
}

patharray2 *_texpath(stringarray *s, penarray *p)
{
  size_t n=checkArrays(s,p);
  if(n == 0) return new array(0);

  string prefix=cleanpath(outname());
  string psname=auxname(prefix,"ps");
  string texname=auxname(prefix,"tex");
  string dviname=auxname(prefix,"dvi");
  bbox b;
  string texengine=getSetting<string>("tex");
  bool xe=settings::xe(texengine) || settings::lua(texengine) ||
    settings::context(texengine);
  texfile tex(texname,b,true);
  tex.miniprologue();

  for(size_t i=0; i < n; ++i) {
    tex.setfont(read<pen>(p,i));
    if(i != 0) {
      if(texengine == "context")
        tex.verbatimline("}\\page\\hbox{%");
      else if(texengine == "luatex" || texengine == "tex" ||
              texengine == "pdftex")
        tex.verbatimline("\\eject");
      else
        tex.verbatimline("\\newpage");
    }
    if(!xe) {
      tex.verbatimline("\\special{ps:");
      tex.verbatimline(ASYx);
      tex.verbatimline(ASYy);
      tex.verbatimline("/ASY1 true def");
      tex.verbatimline("/show {"+ASY1+
                       "currentpoint newpath moveto false charpath "+pathforall+
                       "} bind def");
      tex.verbatimline("/V {"+ASY1+"Ry neg Rx 4 copy 4 2 roll 2 copy 6 2 roll 2 copy (M) print ASYy ASYx (L) print ASYy add ASYx (L) print add ASYy add ASYx (L) print add ASYy ASYx (c) print} bind def}");
    }
    tex.verbatimline(read<string>(s,i)+"\\ %");
  }

  tex.epilogue(true);
  tex.close();

  int status=opentex(texname,prefix,!xe);

  string pdfname,psname2;
  bool keep=getSetting<bool>("keep");

  if(!status) {
    if(xe) {
      string psdriver=getSetting<string>("psdriver");
      pdfname=auxname(prefix,"pdf");
      psname2=auxname(prefix+"_","ps");
      if(!fs::exists(pdfname)) return new array(n);
      std::ofstream ps(psname.c_str(),std::ios::binary);
      if(!ps) cannotwrite(psname);

      showpath(ps);

      mem::vector<string> cmd;
      cmd.push_back(getSetting<string>("gs"));
      cmd.push_back("-q");
      cmd.push_back("-dNoOutputFonts");
      cmd.push_back("-dNOPAUSE");
      cmd.push_back("-dBATCH");
      if(safe) cmd.push_back("-dSAFER");
      cmd.push_back("-sDEVICE="+psdriver);
      cmd.push_back("-sOutputFile="+psname2);
      cmd.push_back(pdfname);
      status=System(cmd,0,true,"gs");

      std::ifstream in(psname2.c_str());
      ps << in.rdbuf();
      ps.close();
    } else {
      if(!fs::exists(dviname)) return new array(n);
      mem::vector<string> dcmd;
      dcmd.push_back(getSetting<string>("dvips"));
      dcmd.push_back("-R");
      dcmd.push_back("-Pdownload35");
      dcmd.push_back("-D600");
      push_split(dcmd,getSetting<string>("dvipsOptions"));
      if(verbose <= 2) dcmd.push_back("-q");
      dcmd.push_back("-o"+psname);
      dcmd.push_back(dviname);
      status=System(dcmd,0,true,"dvips");
    }
  } else
    error("texpath failed");

  if(!keep) { // Delete temporary files.
    unlink(texname.c_str());
    if(!getSetting<bool>("keepaux"))
      unlink(auxname(prefix,"aux").c_str());
    unlink(auxname(prefix,"log").c_str());
    unlink(xe ? pdfname.c_str() : dviname.c_str());
    if(settings::context(texengine)) {
      unlink(auxname(prefix,"top").c_str());
      unlink(auxname(prefix,"tua").c_str());
      unlink(auxname(prefix,"tuc").c_str());
      unlink(auxname(prefix,"tui").c_str());
    }
  }
  return xe ? readpath(psname,keep,0.1) : readpath(psname,keep,0.12,-1.0);
}

patharray2 *textpath(stringarray *s, penarray *p)
{
  size_t n=checkArrays(s,p);
  if(n == 0) return new array(0);

  string prefix=cleanpath(outname());
  string outputname=auxname(prefix,getSetting<string>("textoutformat"));

  string textname=auxname(prefix,getSetting<string>("textextension"));
  std::ofstream text(textname.c_str());

  if(!text) cannotwrite(textname);

  for(size_t i=0; i < n; ++i) {
    text << getSetting<string>("textprologue") << newl
         << read<pen>(p,i).Font() << newl
         << read<string>(s,i) << newl
         << getSetting<string>("textepilogue") << endl;
  }
  text.close();

  string psname=auxname(prefix,"ps");
  std::ofstream ps(psname.c_str());
  if(!ps) cannotwrite(psname);

  showpath(ps);

  mem::vector<string> cmd;
  cmd.push_back(getSetting<string>("textcommand"));
  push_split(cmd,getSetting<string>("textcommandOptions"));
  cmd.push_back(textname);
  iopipestream typesetter(cmd);
  typesetter.block(true,false);

  mem::vector<string> cmd2;
  cmd2.push_back(getSetting<string>("gs"));
  cmd2.push_back("-q");
  cmd2.push_back("-dNoOutputFonts");
  cmd2.push_back("-dNOPAUSE");
  cmd2.push_back("-dBATCH");
  cmd2.push_back("-P");
  if(safe) cmd2.push_back("-dSAFER");
  cmd2.push_back("-sDEVICE="+getSetting<string>("psdriver"));
  cmd2.push_back("-sOutputFile=-");
  cmd2.push_back("-");
  iopipestream gs(cmd2,"gs","Ghostscript");
  gs.block(false,false);

  // TODO: Simplify by connecting the pipes directly.
  while(true) {
    string out;
    if(typesetter.isopen()) {
      typesetter >> out;
      if(!out.empty()) gs << out;
      else if(!typesetter.running()) {
        typesetter.pipeclose();
        gs.eof();
      }
    }
    string out2;
    gs >> out2;
    if(out2.empty() && !gs.running()) break;
    ps << out2;
  }
  ps.close();

  if(verbose > 2) cout << endl;

  bool keep=getSetting<bool>("keep");
  if(!keep) // Delete temporary files.
    unlink(textname.c_str());
  return readpath(psname,keep,0.1);
}

patharray *_strokepath(path g, pen p=CURRENTPEN)
{
  array *P=new array(0);
  if(g.size() == 0) return P;

  string prefix=cleanpath(outname());
  string psname=auxname(prefix,"ps");
  bbox b;
  psfile ps(psname,false);
  ps.prologue(b);
  ps.verbatimline(ASYx);
  ps.verbatimline(ASYy);
  ps.verbatimline("/stroke {"+ASYinit+pathforall+"} bind def");
  ps.resetpen();
  ps.setpen(p);
  ps.write(g);
  ps.strokepath();
  ps.stroke(p);
  ps.verbatimline("(M) "+currentpoint);
  ps.epilogue();
  ps.close();
  array *a=readpath(psname,getSetting<bool>("keep"));
  return a->size() > 0 ? read<array *>(a,0) : a;
}
