/*****
* texfile.h
* John Bowman 2003/03/14
*
* Encapsulates the writing of commands to a TeX file.
*****/

#ifndef TEXFILE_H
#define TEXFILE_H

#include <fstream>
#include <iomanip>
#include <iostream>

#include "common.h"
#include "pair.h"
#include "bbox.h"
#include "pen.h"
#include "util.h"
#include "interact.h"
#include "path.h"
#include "array.h"
#include "psfile.h"
#include "settings.h"
#include "asyprocess.h"

namespace camp {

template<class T>
void texdocumentclass(T& out, bool pipe=false)
{
 if(settings::latex(settings::getSetting<string>("tex")) &&
    (pipe || !settings::getSetting<bool>("inlinetex")))
   out << "\\documentclass[12pt]{article}" << newl;
}

template<class T>
void texuserpreamble(T& out,
                    mem::list<string>& preamble=processData().TeXpreamble,
                    bool pipe=false)
{
 for(mem::list<string>::iterator p=preamble.begin(); p != preamble.end();
     ++p) {
   out << stripblanklines(*p);
   if(pipe) out << newl << newl;
 }
}

template<class T>
void latexfontencoding(T& out)
{
 out << "\\makeatletter%" << newl
     << "\\let\\ASYencoding\\f@encoding%" << newl
     << "\\let\\ASYfamily\\f@family%" << newl
     << "\\let\\ASYseries\\f@series%" << newl
     << "\\let\\ASYshape\\f@shape%" << newl
     << "\\makeatother%" << newl;
}

std::unordered_set const latexCharacters = {'#', '$', '%', '&', '\\', '^', '_', '{', '}', '~'};

template<class T>
void texpreamble(T& out, mem::list<string>& preamble=processData().TeXpreamble,
                bool pipe=false, bool ASYbox=true)
{
 texuserpreamble(out,preamble,pipe);
 string texengine=settings::getSetting<string>("tex");
 string outPath=stripFile(settings::outname());
 if(settings::context(texengine))
   out << "\\disabledirectives[system.errorcontext]%" << newl;
 if(ASYbox)
   out << "\\newbox\\ASYbox" << newl
       << "\\newdimen\\ASYdimen" << newl;
 out << "\\def\\ASYprefix{" << escapeCharacters(outPath, latexCharacters) << "}" << newl
     << "\\long\\def\\ASYbase#1#2{\\leavevmode\\setbox\\ASYbox=\\hbox{#1}%"
     << "\\ASYdimen=\\ht\\ASYbox%" << newl
     << "\\setbox\\ASYbox=\\hbox{#2}\\lower\\ASYdimen\\box\\ASYbox}" << newl;
 if(!pipe)
   out << "\\long\\def\\ASYaligned(#1,#2)(#3,#4)#5#6#7{\\leavevmode%" << newl
       << "\\setbox\\ASYbox=\\hbox{#7}%" << newl
       << "\\setbox\\ASYbox\\hbox{\\ASYdimen=\\ht\\ASYbox%" << newl
       << "\\advance\\ASYdimen by\\dp\\ASYbox\\kern#3\\wd\\ASYbox"
       << "\\raise#4\\ASYdimen\\box\\ASYbox}%" << newl
       << "\\setbox\\ASYbox=\\hbox{#5\\wd\\ASYbox 0pt\\dp\\ASYbox 0pt\\ht\\ASYbox 0pt\\box\\ASYbox#6}%" << newl
       << "\\hbox to 0pt{\\kern#1pt\\raise#2pt\\box\\ASYbox\\hss}}%" << newl
       << "\\long\\def\\ASYalignT(#1,#2)(#3,#4)#5#6{%" << newl
       << "\\ASYaligned(#1,#2)(#3,#4){%" << newl
       << settings::beginlabel(texengine) << "%" << newl
       << "}{%" << newl
       << settings::endlabel(texengine) << "%" << newl
       << "}{#6}}" << newl
       << "\\long\\def\\ASYalign(#1,#2)(#3,#4)#5{"
       << "\\ASYaligned(#1,#2)(#3,#4){}{}{#5}}" << newl
       << settings::rawpostscript(texengine) << newl;
}

// Work around bug in dvips.def: allow spaces in file names.
template<class T>
void dvipsfix(T &out)
{
 if(!settings::pdf(settings::getSetting<string>("tex"))) {
   out << "\\makeatletter" << newl
       << "\\def\\Ginclude@eps#1{%" << newl
       << " \\message{<#1>}%" << newl
       << "  \\bgroup" << newl
       << "  \\def\\@tempa{!}%" << newl
       << "  \\dimen@\\Gin@req@width" << newl
       << "  \\[email protected]%" << newl
       << "  \\divide\\dimen@\\dimen@ii" << newl
       << "  \\@tempdima\\Gin@req@height" << newl
       << "  \\divide\\@tempdima\\dimen@ii" << newl
       << "    \\special{PSfile=#1\\space" << newl
       << "      llx=\\Gin@llx\\space" << newl
       << "      lly=\\Gin@lly\\space" << newl
       << "      urx=\\Gin@urx\\space" << newl
       << "      ury=\\Gin@ury\\space" << newl
       << "      \\ifx\\Gin@scalex\\@tempa\\else rwi=\\number\\dimen@\\space\\fi" << newl
       << "      \\ifx\\Gin@scaley\\@tempa\\else rhi=\\number\\@tempdima\\space\\fi" << newl
       << "      \\ifGin@clip clip\\fi}%" << newl
       << "  \\egroup}" << newl
       << "\\makeatother" << newl;
 }
}

template<class T>
void texdefines(T& out, mem::list<string>& preamble=processData().TeXpreamble,
               bool pipe=false)
{
 string texengine=settings::getSetting<string>("tex");
 bool latex=settings::latex(texengine);
 bool inlinetex=settings::getSetting<bool>("inlinetex");
 if(pipe || !inlinetex) {
   bool lua=settings::lua(texengine);
   if(latex) {
     if(lua) {
       out << "\\edef\\pdfpageattr{\\pdfvariable pageattr}" << newl
           << "\\ifx\\pdfpagewidth\\undefined\\let\\pdfpagewidth\\paperwidth"
           << "\\fi" << newl
           << "\\ifx\\pdfpageheight\\undefined\\let\\pdfpageheight"
           << "\\paperheight"
           << "\\fi" << newl
           << "\\usepackage{graphicx}" << newl;
     } else {
       out << "\\let\\paperwidthsave\\paperwidth\\let\\paperwidth\\undefined"
           << newl
           << "\\usepackage{graphicx}" << newl
           << "\\let\\paperwidth\\paperwidthsave" << newl;
     }
   } else {
     if(lua) {
       out << "\\edef\\pdfpageattr{\\pdfvariable pageattr}" << newl
           << "\\ifx\\pdfpagewidth\\undefined\\let\\pdfpagewidth\\pagewidth"
           << "\\fi" << newl
           << "\\ifx\\pdfpageheight\\undefined\\let\\pdfpageheight"
           << "\\pageheight"
           << "\\fi" << newl;
     }
   }
   texpreamble(out,preamble,pipe);
 }

 if(pipe) {
   // Make tex pipe aware of a previously generated aux file.
   string name=auxname(settings::outname(),"aux");
   std::ifstream fin(name.c_str());
   if(fin) {
     std::ofstream fout("texput.aux");
     string s;
     while(getline(fin,s))
       fout << s << endl;
   }
 }

 if(latex) {
   if(!inlinetex) {
     dvipsfix(out);
   }
   if(pipe) {
     out << "\\begin{document}" << newl;
     latexfontencoding(out);
   }
 } else if(!settings::context(texengine)) {
   out << "\\input graphicx" << newl // Fix miniltx path parsing bug:
       << "\\makeatletter" << newl
       << "\\def\\filename@parse#1{%" << newl
       << "  \\let\\filename@area\\@empty" << newl
       << "  \\expandafter\\filename@path#1/\\\\}" << newl
       << "\\def\\filename@path#1/#2\\\\{%" << newl
       << "  \\ifx\\\\#2\\\\%" << newl
       << "     \\def\\reserved@a{\\filename@simple#1.\\\\}%" << newl
       << "  \\else" << newl
       << "     \\edef\\filename@area{\\filename@area#1/}%" << newl
       << "     \\def\\reserved@a{\\filename@path#2\\\\}%" << newl
       << "  \\fi" << newl
       << "  \\reserved@a}" << newl
       << "\\makeatother" << newl;
   dvipsfix(out);

   if(!pipe)
     out << "\\input picture" << newl;
 }
}

template<class T>
bool setlatexfont(T& out, const pen& p, const pen& lastpen)
{
 if(p.size() != lastpen.size() || p.Lineskip() != lastpen.Lineskip()) {
   out <<  "\\fontsize{" << p.size()*settings::ps2tex << "}{"
       << p.Lineskip()*settings::ps2tex << "}\\selectfont%" << newl;
   return true;
 }
 return false;
}

template<class T>
bool settexfont(T& out, const pen& p, const pen& lastpen, bool latex)
{
 string font=p.Font();
 if(font != lastpen.Font() || (!latex && p.size() != lastpen.size())) {
   out << font << "%" << newl;
   return true;
 }
 return false;
}

class texfile : public psfile {
protected:
 bbox box;
 bool inlinetex;
 double Hoffset;
 int level;
 bool pdf;

public:
 string texengine;

 texfile(const string& texname, const bbox& box, bool pipe=false);
 virtual ~texfile();

 void prologue(bool deconstruct=false);
 virtual void beginpage() {}

 void epilogue(bool pipe=false);
 virtual void endpage() {}

 void setpen(pen p);

 void setfont(pen p);

 void gsave(bool tex=true);

 void grestore(bool tex=true);

 void beginspecial();

 void endspecial();

 void special(const string &s);

 void beginraw();

 void endraw();

 void begingroup() {++level;}

 void endgroup() {--level;}

 bool toplevel() {return level == 0;}

 virtual void beginpicture(const bbox& b);
 void endpicture(const bbox& b, bool newPage=false);

 virtual void newpage(const bbox&) {
   verbatimline(settings::newpage(texengine));
 }

 void BBox(const bbox& b) {
   bbox B=b.shift(pair(-hoffset(),-voffset()));
   if(pdf) {
     if(settings::xe(texengine))
       *out << "\\special{pdf: put @thispage <</MediaBox [" << B << "]>>}%"
            << newl;
     else
       if(settings::context(texengine)) {
         double width=B.right-B.left;
         double height=B.top-B.bottom;
         *out << "\\definepapersize[asy]["
              << "width=" << width << "bp,"
              << "height=" << height << "bp]%" << newl
              << "\\setuppapersize[asy][asy]%" << newl
              << "\\setuplayout["
              << "backspace=" << -B.left << "bp,"
              << "topspace=" << B.top-(box.top-box.bottom) << "bp]%" << newl;
       } else
         *out << "\\pdfpageattr{/MediaBox [" << B << "]}%" << newl;
   }
 }

 void writepair(pair z) {
   *out << z;
 }

 void miniprologue();

 void writeshifted(path p, bool newPath=true);
 virtual double hoffset() {return Hoffset;}
 virtual double voffset() {return box.bottom;}

 // Draws label transformed by T at position z.
 void put(const string& label, const transform& T, const pair& z,
          const pair& Align);

 void beginlayer(string psname, bool postscript);
 void endlayer();

 virtual void Offset(const bbox& box, bool special=false) {};
};

class svgtexfile : public texfile {
 mem::stack<size_t> clipstack;
 size_t clipcount;
 size_t gradientcount;
 size_t gouraudcount;
 size_t tensorcount;
 bool inspecial;
 static string nl;
 pair offset;
 bool first;
 bool deconstruct;
public:
 svgtexfile(const string& texname, const bbox& box, bool pipe=false,
            bool deconstruct=false) :
   texfile(texname,box,pipe), deconstruct(deconstruct) {
   inspecial=false;

   *out << "\\catcode`\\%=12" << newl
        << "\\def\\percent{%}" << newl
        << "\\catcode`\\%=14" << newl;

   first=true;
   Offset(box);
 }

 void Offset(const bbox& b, bool special=false) {
   box=b;
   if(special) {
     texfile::beginpicture(b);
     pair bboxshift=pair(-2*b.left,b.top-b.bottom);
     bbox b0=svgbbox(b,bboxshift);
     *out << "\\special{dvisvgm:bbox f "
          << b0.left << "bp "
          << b0.bottom << "bp "
          << b0.right << "bp "
          << b0.top << "bp}%" << newl;
   }

   Hoffset=inlinetex ? box.right : box.left;
   offset=pair(box.left,box.top);
   clipstack=mem::stack<size_t>();
   clipcount=0;
   gradientcount=0;
   gouraudcount=0;
   tensorcount=0;
 }

 void beginpicture(const bbox& b) {
   Offset(b,true);
 }

 void newpage(const bbox& b) {
   if(deconstruct) {
     if(first)
       first=false;
     else
       endpicture(b,true);
     beginpicture(b);
   }
 }

 void writeclip(path p, bool newPath=true) {
   write(p,false);
 }

 void dot(path p, pen, bool newPath=true);

 void writeshifted(pair z) {
   write(conj(shift(-offset)*z)*settings::ps2tex);
 }

 double hoffset() {return Hoffset+offset.getx();}
 double voffset() {return box.bottom+offset.gety();}

 void translate(pair z) {}
 void concat(transform t) {}

 void beginspecial(bool def=false);
 void endspecial();

 void beginpage() {
   beginpicture(box);
 }

 void endpage() {
   endpicture(box);
 }

 void transform();
 void begintransform();
 void endtransform();

 void clippath();

 void beginpath();
 void endpath();

 void newpath() {
   beginspecial();
   begintransform();
   beginpath();
 }

// Workaround libc++ parsing bug under MacOS.
#ifdef __APPLE__
 const string sep=" ";
#else
 const string sep="";
#endif
 void moveto(pair z) {
   *out << sep << "M";
   writeshifted(z);
 }

 void lineto(pair z) {
   *out << sep << "L";
   writeshifted(z);
 }

 void curveto(pair zp, pair zm, pair z1) {
   *out << sep << "C";
   writeshifted(zp); writeshifted(zm); writeshifted(z1);
 }

 void closepath() {
   *out << sep << "Z";
 }

 string rgbhex(pen p) {
   p.torgb();
   return p.hex();
 }

 void properties(const pen& p);
 void color(const pen &p, const string& type);

 void stroke(const pen &p, bool dot=false);
 void strokepath();

 void fillrule(const pen& p, const string& type="fill");
 void fill(const pen &p);

 void begingradientshade(bool axial, ColorSpace colorspace,
                         const pen& pena, const pair& a, double ra,
                         const pen& penb, const pair& b, double rb);
 void gradientshade(bool axial, ColorSpace colorspace,
                    const pen& pena, const pair& a, double ra,
                    bool extenda, const pen& penb, const pair& b,
                    double rb, bool extendb);

 void gouraudshade(const pen& p0, const pair& z0,
                   const pen& p1, const pair& z1,
                   const pen& p2, const pair& z2);
 void begingouraudshade(const vm::array& pens, const vm::array& vertices,
                        const vm::array& edges);
 void gouraudshade(const pen& pentype, const vm::array& pens,
                   const vm::array& vertices, const vm::array& edges);

 void beginclip();

 void endclip(const pen &p);
 void endpsclip(const pen &p) {}

 void setpen(pen p) {if(!inspecial) texfile::setpen(p);}

 void gsave(bool tex=false);

 void grestore(bool tex=false);
};

} //namespace camp

#endif