/*****
* psfile.h
* Andy Hammerlindl 2002/06/10
*
* Encapsulates the writing of commands to a PostScript file.
* Allows identification and removal of redundant commands.
*****/

#ifndef PSFILE_H
#define PSFILE_H

#include <fstream>
#include <iomanip>
#include <sstream>

#include "pair.h"
#include "path.h"
#include "bbox.h"
#include "pen.h"
#include "array.h"
#include "callable.h"

namespace camp {

inline void BoundingBox(std::ostream& s, const bbox& box)
{
 s << "%%BoundingBox: " << std::setprecision(0) << std::fixed
   << box.LowRes() << newl;
 s.unsetf(std::ios::fixed);
 s << "%%HiResBoundingBox: " << std::setprecision(9) << box << newl;
}

// An ASCII85Encode filter.
class encode85 {
 ostream *out;
 int tuple;
 int pos;
 int count;
 static const int width=72;
public:
 encode85(ostream *out) : out(out), tuple(0), pos(0), count(0) {}

 ~encode85() {
   if(count > 0)
     encode(tuple, count);
   if(pos+2 > width)
     *out << '\n';
   *out << "~>\n";
 }
private:
 void encode(unsigned int tuple, int count) {
   unsigned char buf[5], *s=buf;
   int i=5;
   do {
     *s++=tuple % 85;
     tuple /= 85;
   } while(--i > 0);
   i=count;
   do {
     *out << (unsigned char) (*--s + '!');
     if(pos++ >= width) {
       pos=0;
       *out << '\n';
     }
   } while(i-- > 0);
 }

public:
 void put(unsigned char c) {
   switch (count++) {
     case 0:
       tuple |= (c << 24);
       break;
     case 1:
       tuple |= (c << 16);
       break;
     case 2:
       tuple |= (c <<  8);
       break;
     case 3:
       tuple |= c;
       if(tuple == 0) {
         *out << 'z';
         if(pos++ >= width) {
           pos=0;
           *out << '\n';
         }
       } else
         encode(tuple, count);
       tuple=0;
       count=0;
       break;
   }
 }
};

class psfile {
protected:
 mem::stack<pen> pens;

public:

 string filename;
 bool pdfformat;    // Is final output format PDF?
 bool pdf;          // Output direct PDF?
 unsigned char *buffer;
 size_t count;

 void write(pen *p, size_t ncomponents);
 void writefromRGB(unsigned char r, unsigned char g, unsigned char b,
                   ColorSpace colorspace, size_t ncomponents);

 void writeCompressed(const unsigned char *a, size_t size);
 void dealias(unsigned char *a, size_t width, size_t height, size_t n,
              bool convertrgb=false, ColorSpace colorspace=DEFCOLOR);

 void beginImage(size_t n) {
   buffer=new unsigned char[n];
   count=0;
 }

 void outImage(bool antialias, size_t width, size_t height,
               size_t ncomponents);

 void endImage(bool antialias, size_t width, size_t height,
               size_t ncomponents) {
   outImage(antialias,width,height,ncomponents);
   delete[] buffer;
 }

 void writeByte(unsigned char n) {
   buffer[count++]=n;
 }

protected:
 pen lastpen;
 std::ostream *out;

public:
 bool pdftex() {return settings::pdf(settings::getSetting<string>("tex"));}

 psfile(const string& filename, bool pdfformat);

 psfile() {pdf=pdftex();}

 bool transparentFormat(string outputformat);

 virtual ~psfile();

 void BoundingBox(const bbox& box) {
   camp::BoundingBox(*out,box);
 }

 void prologue(const bbox& box);
 void epilogue();
 void header(bool eps);

 void close();

 void write(double x) {
   *out << " " << x;
 }

 void writenewl() {
   *out << newl;
 }

 void write(pair z) {
   *out << " " << z.getx() << " " << z.gety();
 }

 void write(transform t) {
   if(!pdf) *out << "[";
   *out << " " << t.getxx() << " " << t.getyx()
        << " " << t.getxy() << " " << t.getyy()
        << " " << t.getx() << " " << t.gety();
   if(!pdf) *out << "]";
 }

 void resetpen() {
   lastpen=pen(initialpen);
   lastpen.convert();
 }

 void setcolor(const pen& p, const string& begin, const string& end);
 void setopacity(const pen& p);

 virtual void setpen(pen p);

 void write(const pen& p);

 void write(path p, bool newPath=true);

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

 virtual void dot(path p, pen, bool newPath=true) {
   write(p,newPath);
 }

 virtual void newpath() {
   if(!pdf) *out << "newpath";
 }

 virtual void moveto(pair z) {
   write(z);
   if(pdf) *out << " m" << newl;
   else *out << " moveto" << newl;
 }

 virtual void lineto(pair z) {
   write(z);
   if(pdf) *out << " l" << newl;
   else *out << " lineto" << newl;
 }

 virtual void curveto(pair zp, pair zm, pair z1) {
   write(zp); write(zm); write(z1);
   if(pdf) *out << " c" << newl;
   else *out << " curveto" << newl;
 }

 virtual void closepath() {
   if(pdf) *out << "h" << newl;
   else *out << "closepath" << newl;
 }

 virtual void stroke(const pen &p, bool dot=false) {
   if(pdf) *out << "S" << newl;
   else *out << "stroke" << newl;
 }

 virtual void strokepath() {
   if(pdf) reportError("PDF does not support strokepath");
   else *out << "strokepath" << newl;
 }

 virtual void fill(const pen &p) {
   if(p.evenodd()) {
     if(pdf) *out << "f*" << newl;
     else *out << "eofill" << newl;
   } else {
     if(pdf) *out << "f" << newl;
     else *out << "fill" << newl;
   }
 }

 virtual void beginclip() {
   newpath();
 }

 virtual void endclip(const pen &p) {
   if(p.evenodd()) {
     if(pdf) *out << "W* n" << newl;
     else *out << "eoclip" << newl;
   } else {
     if(pdf) *out << "W n" << newl;
     else *out << "clip" << newl;
   }
 }

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

 void checkLevel() {
   int n=settings::getSetting<Int>("level");
   if(n < 3)
     reportError("PostScript shading requires -level 3");
 }

 virtual void beginlatticeshade(const vm::array& a, const bbox& b) {}
 virtual void latticeshade(const vm::array& a, const transform& t);

 virtual void begingradientshade(bool axial, ColorSpace colorspace,
                                 const pen& pena, const pair& a, double ra,
                                 const pen& penb, const pair& b, double rb) {}

 virtual 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);

 virtual void begingouraudshade(const vm::array& pens,
                                const vm::array& vertices,
                                const vm::array& edges) {}
 virtual void gouraudshade(const pen& pentype, const vm::array& pens,
                           const vm::array& vertices, const vm::array& edges);

 virtual void tensorshade(const pen& pentype, const vm::array& pens,
                          const vm::array& boundaries, const vm::array& z);

 void vertexpen(vm::array *pi, int j, ColorSpace colorspace);

 void imageheader(size_t width, size_t height, ColorSpace colorspace);

 void image(const vm::array& a, const vm::array& p, bool antialias);
 void image(const vm::array& a, bool antialias);
 void image(vm::stack *Stack, vm::callable *f, Int width, Int height,
            bool antialias);

 void rawimage(unsigned char *a, size_t width, size_t height, bool antialias);

 virtual void gsave(bool tex=false) {
   if(pdf) *out << "q";
   else *out << "gsave";
   if(!tex) *out << newl;
   pens.push(lastpen);
 }

 virtual void grestore(bool tex=false) {
   if(pens.size() < 1)
     reportError("grestore without matching gsave");
   lastpen=pens.top();
   pens.pop();
   if(pdf) *out << "Q";
   else *out << "grestore";
   if(!tex) *out << newl;
 }

 virtual void translate(pair z) {
   if(z == pair(0.0,0.0)) return;
   if(pdf) *out << " 1 0 0 1 " << newl;
   write(z);
   if(pdf) *out << " cm" << newl;
   *out << " translate" << newl;
 }

 // Multiply on a transform to the transformation matrix.
 virtual void concat(transform t) {
   if(t.isIdentity()) return;
   write(t);
   if(pdf) *out << " cm" << newl;
   else *out << " concat" << newl;
 }

 void verbatimline(const string& s) {
   *out << s << newl;
 }

 void verbatim(const string& s) {
   *out << s;
 }

 // Determine shading and image transparency based on first pen.
 void setfirstopacity(const vm::array& pens) {
   if(pens.size() > 0) {
     pen *p=vm::read<pen *>(pens,0);
     setopacity(*p);
   }
 }

 ColorSpace maxcolorspace(const vm::array& pens) {
   ColorSpace colorspace=DEFCOLOR;
   size_t size=pens.size();
   for(size_t i=0; i < size; i++) {
     pen *p=vm::read<pen *>(pens,i);
     p->convert();
     colorspace=max(colorspace,p->colorspace());
   }
   return colorspace;
 }

 ColorSpace maxcolorspace2(const vm::array& penarray) {
   ColorSpace colorspace=DEFCOLOR;
   size_t size=penarray.size();
   for(size_t i=0; i < size; i++)
     colorspace=max(colorspace,
                    maxcolorspace(vm::read<vm::array>(penarray,i)));
   return colorspace;
 }

};

} //namespace camp

#endif