#ifndef __POSTSCRIPTDOCUMENT_H__
#define __POSTSCRIPTDOCUMENT_H__

#include <list>
#include <string>
#include <cstdio>

struct Point
{
  double x, y;
};
struct Rect
{
  double x, y, w, h;
};
struct Color
{
  double r, g, b;
};

class PostscriptDocument
{
  public:
    enum
    {
      FONT_TIMES_NORMAL,
      FONT_COURIER_NORMAL,
      FONT_HELVETICA_NORMAL,
    };
  protected:
    std::list<std::string> lines;
    char * sprintfBuffer;
    double curFontSize;
    Rect box;

    inline std::string reformatString(const std::string & str)
    {
      int i;
      char * p1, * p2;
      char buf[1025];
      std::string t, ret = "";

      for (i = str.size(); i >= 0; --i)
      {
        switch (str[i])
        {
        case '\\':
        case '(':
        case ')':
          ret = "\\" + str.substr(i, 1) + ret;
          break;
        default:
          ret = str[i] + ret;
          break;
        }
      }
      p1 = (char*)ret.c_str();
      while (p2 = strstr(p1, "\r\n"))
      {
        memmove(p2, p2 + 1, strlen(p2) + 1);
      }
      while (p2 = strstr(p1, "\r"))
      {
        *p2 = '\n';
      }
      t = p1;
      ret = t;

      return ret;
    }
    inline bool getNextLine(const std::string & str, std::string & ret, int & start)
    {
      int ind;
      if (start == std::string::npos || start >= str.size())  return false;
      ind = str.substr(start).find_first_of("\n");
      if (ind == std::string::npos)
      {
        ret = str.substr(start);
        start = std::string::npos;
        return true;
      }
      ret = str.substr(start, ind);
      start = start + ind + 1;
      return true;
    }
    inline void drawSingleLineString(const std::string & str, const Point & pt)
    {
      sprintf(sprintfBuffer, "%.3lf %.3lf moveto (%s) show", pt.x, pt.y, str.c_str());
      lines.push_back(sprintfBuffer);
    }
    inline void drawMultiLineString(const std::string & str, const Point & pt)
    {
      int start = 0;
      Point newpt = pt;
      std::string curLine;

      while (getNextLine(str, curLine, start))
      {
        drawSingleLineString(curLine, newpt);
        newpt.y -= curFontSize * 1.05;
      }
    }
    void init(const Rect & r)
    {
      box = r;
      sprintfBuffer = new char[1048576];
      lines.push_back("%!PS-Adobe EPSF-3.0");
      sprintf(sprintfBuffer, "%%%%BoundingBox: %i %i %i %i", (int)r.x, (int)r.y, (int)(r.x + r.w), (int)(r.y + r.h));
      lines.push_back(sprintfBuffer);
      lines.push_back("%author: postscript document class");
      lines.push_back("");
      lines.push_back("/Times-Roman findfont");
      lines.push_back("12 scalefont");
      lines.push_back("setfont");
      curFontSize = 12;
    }
  public:
    inline PostscriptDocument()
    {
      Rect r = { 0, 0, 612, 792 };
      init(r);
    }
    inline PostscriptDocument(const Rect & boundingBox)
    {
      init(boundingBox);
    }
    inline ~PostscriptDocument()
    {
      delete [] sprintfBuffer;
    }
    inline Rect getBoundingBox() const
    {
      return box;
    }
    inline void drawNothing()
    {
      lines.push_back("");
    }
    inline bool saveToFile(const std::string & path)
    {
      FILE * fp = fopen(path.c_str(), "w");
      if (!fp) return false;
      writeToFile(fp);
      return true;
    }
    inline bool writeToFile(FILE * fp)
    {
      std::list<std::string>::iterator it;
      char buf[1025] = "";
      char * spacing = buf;
      int len = 0;
      if (!fp) return false;
      for (it = lines.begin(); it != lines.end(); ++it)
      {
        if (strcmp(it->c_str(), "grestore") == 0)
        {
          len -= 2;
          spacing[len] = 0;
        }
        fprintf(fp, "%s%s\n", buf, it->c_str());
        if (strcmp(it->c_str(), "gsave") == 0)
        {
          strcpy(spacing + len, "  ");
          len += 2;
        }
      }
      fprintf(fp, "showpage\n");
      return true;
    }
    inline void setLineWidth(const double lineWidth)
    {
      sprintf(sprintfBuffer, "%.3lf setlinewidth", lineWidth);
      lines.push_back(sprintfBuffer);
    }
    inline void setColor(const double r, const double g, const double b)
    {
      Color c = { r, g, b };
      setColor(c);
    }
    inline void setColor(Color & c)
    {
      sprintf(sprintfBuffer, "%.3lf %.3lf %.3lf setrgbcolor", c.r, c.g, c.b);
      lines.push_back(sprintfBuffer);
    }
    inline void drawLine(const double x1, const double y1, const double x2, const double y2)
    {
      Point pt1 = { x1, y1 };
      Point pt2 = { x2, y2 };
      drawLine(pt1, pt2);
    }
    inline void drawLine(const Point & pt1, const Point & pt2)
    {
      sprintf(sprintfBuffer, "newpath %.3lf %.3lf moveto %.3lf %.3lf lineto stroke", pt1.x, pt1.y, pt2.x, pt2.y);
      lines.push_back(sprintfBuffer);
    }
    inline void setFont(const int font, const double fontSize)
    {
      std::string fn = "Times-Roman";
      switch (font)
      {
      case FONT_TIMES_NORMAL:     fn = "Times-Roman"; break;
      case FONT_COURIER_NORMAL:   fn = "Courier";     break;
      case FONT_HELVETICA_NORMAL: fn = "Helvetica";   break;
      }
      curFontSize = fontSize;
      sprintf(sprintfBuffer, "/%s findfont %.3lf scalefont setfont\n", fn.c_str(), fontSize);
      lines.push_back(sprintfBuffer);
    }
    inline void drawString(const std::string & str, const double x, const double y)
    {
      Point pt = { x, y };
      drawString(str, pt);
    }
    inline void drawString(const std::string & str, const Point & pt)
    {
      std::string s = reformatString(str);
      if (s.find_first_of("\r\n") != std::string::npos) drawMultiLineString(s, pt);
      else                                              drawSingleLineString(s, pt);
    }
    inline void drawRect(const double x, const double y, const double w, const double h)
    {
      Rect r = { x, y, w, h };
      drawRect(r);
    }
    inline void drawRect(const Rect & r)
    {
      sprintf(sprintfBuffer, "newpath %.3lf %.3lf %.3lf %.3lf rectstroke", r.x, r.y, r.w, r.h);
      lines.push_back(sprintfBuffer);
    }
    inline void fillRect(const double x, const double y, const double w, const double h)
    {
      Rect r = { x, y, w, h };
      fillRect(r);
    }
    inline void fillRect(const Rect & r)
    {
      sprintf(sprintfBuffer, "newpath %.3lf %.3lf %.3lf %.3lf rectfill", r.x, r.y, r.w, r.h);
      lines.push_back(sprintfBuffer);
    }
    inline void drawArc(const double x, const double y, const double radius, const double start, const double end)
    {
      Point pt = { x, y };
      drawArc(pt, radius, start, end);
    }
    inline void drawArc(const Point & center, const double radius, const double start, const double end)
    {
      sprintf(sprintfBuffer, "newpath %.3lf %.3lf %.3lf %.3lf %.3lf arc stroke", center.x, center.y, radius, start, end);
      lines.push_back(sprintfBuffer);
    }
    inline void drawCircle(const double x, const double y, const double radius)
    {
      Point pt = { x, y };
      drawArc(pt, radius, 0, 360);
    }
    inline void drawCircle(const Point & center, const double radius)
    {
      drawArc(center, radius, 0, 360);
    }
    inline void fillCircle(const double x, const double y, const double radius)
    {
      Point pt = { x, y };
      fillArc(pt, radius, 0, 360);
    }
    inline void fillCircle(const Point & center, const double radius)
    {
      fillArc(center, radius, 0, 360);
    }
    inline void fillArc(const double x, const double y, const double radius, const double start, const double end)
    {
      Point pt = { x, y };
      fillArc(pt, radius, start, end);
    }
    inline void fillArc(const Point & center, const double radius, const double start, const double end)
    {
      sprintf(sprintfBuffer, "newpath %.3lf %.3lf %.3lf %.3lf %.3lf arc fill", center.x, center.y, radius, start, end);
      lines.push_back(sprintfBuffer);
    }
    inline void drawMetricRect()
    {
      drawMetricRect(box);
    }
    inline void drawMetricRect(const std::list<int> & intervals, const std::list<int> & extents)
    {
      int i;
      std::list<int>::const_iterator it1, it2;
      int * ints = new int[intervals.size()];
      int * exts = new int[extents.size()];
      for (i = 0, it1 = intervals.begin(), it2 = extents.begin(); it1 != intervals.end(); ++it1, ++it2, ++i)
      {
        ints[i] = *it1;
        exts[i] = *it2;
      }
      drawMetricRect(box, ints, exts, intervals.size());
      delete [] ints;
      delete [] exts;
    }
    inline void drawMetricRect(const int * const intervals, const int * const extents, const int len)
    {
      drawMetricRect(box, intervals, extents, len);
    }
    inline void drawMetricRect(const Rect & rect)
    {
      int intervals[]  = { 100,  50,  25, };
      int sizes[]      = {   5,   3,   1, };
      drawMetricRect(rect, intervals, sizes, 3);
    }
    inline void drawMetricRect(const Rect & rect, const std::list<int> & intervals, const std::list<int> & extents)
    {
      int i;
      std::list<int>::const_iterator it1, it2;
      int * ints = new int[intervals.size()];
      int * exts = new int[extents.size()];
      for (i = 0, it1 = intervals.begin(), it2 = extents.begin(); it1 != intervals.end(); ++it1, ++it2, ++i)
      {
        ints[i] = *it1;
        exts[i] = *it2;
      }
      drawMetricRect(rect, ints, exts, intervals.size());
      delete [] ints;
      delete [] exts;
    }
    inline void drawMetricRect(const Rect & rect, const int * const intervals, const int * const extents, const int len)
    {
      int i, j;

      drawRect(rect);
      for (i = 1; i < (int)rect.w; ++i)
      {
        for (j = 0; j < len; ++j)
        {
          if (i % intervals[j] == 0)
          {
            const int x = rect.x + i;
            const int y1 = rect.y + 1,          y2 = rect.y + extents[j];
            const int y3 = rect.y + rect.h - 1, y4 = rect.y + rect.h - extents[j];
            drawLine(x, y1, x, y2);
            drawLine(x, y3, x, y4);
            j = len;
          }
        }
      }
      for (i = 1; i < (int)rect.h; ++i)
      {
        for (j = 0; j < len; ++j)
        {
          if (i % intervals[j] == 0)
          {
            const int y = rect.y + i;
            const int x1 = rect.x + 1,          x2 = rect.x + extents[j];
            const int x3 = rect.x + rect.w - 1, x4 = rect.x + rect.w  - extents[j];
            drawLine(x1, y, x2, y);
            drawLine(x3, y, x4, y);
            j = len;
          }
        }
      }
    }
    inline void startPath()
    {
      lines.push_back("newpath");
    }
    inline void moveTo(const double x, const double y)
    {
      Point pt = { x, y };
      moveTo(pt);
    }
    inline void moveTo(const Point & pt)
    {
      char buf[1024];
      sprintf(buf, "%lf %lf moveto", pt.x, pt.y);
      lines.push_back(buf);
    }
    inline void lineTo(const double x, const double y)
    {
      Point pt = { x, y };
      lineTo(pt);
    }
    inline void lineTo(const Point & pt)
    {
      char buf[1024];
      sprintf(buf, "%lf %lf lineto", pt.x, pt.y);
      lines.push_back(buf);
    }
    inline void closePath()
    {
      lines.push_back("closepath");
    }
    inline void fillPath()
    {
      lines.push_back("fill");
    }
    inline void strokePath()
    {
      lines.push_back("stroke");
    }

    inline void pushState()
    {
      lines.push_back("gsave");
    }
    inline void popState()
    {
      lines.push_back("grestore");
    }
    inline void setDash(const double * const pattern)
    {
      int i;
      const double * p;
      for (i = 0, p = pattern; *p != 0; ++p, ++i) { }
      setDash(pattern, i);
    }
    inline void setDash(const double * const pattern, const int length)
    {
      int i;
      std::list<double> p;
      for (i = 0; i < length; ++i) p.push_back(pattern[i]);
      setDash(p);
    }
    inline void setDash(const std::list<double> & pattern)
    {
      int i;
      char tbuf[1000];
      std::list<double>::const_iterator it;

      strcpy(sprintfBuffer, "[");
      for (it = pattern.begin(); it != pattern.end(); ++it)
      {
        sprintf(tbuf, " %.3lf", *it);
        strcat(sprintfBuffer, tbuf);
      }
      strcat(sprintfBuffer, " ] 0 setdash");
      lines.push_back(sprintfBuffer);
    }
    inline void translate(const double x, const double y)
    {
      Point pt = { x, y };
      translate(pt);
    }
    inline void translate(const Point & pt)
    {
      sprintf(sprintfBuffer, "%.3lf %.3lf translate", pt.x, pt.y);
      lines.push_back(sprintfBuffer);
    }
    inline void scale(const double x, const double y)
    {
      sprintf(sprintfBuffer, "%.3lf %.3lf scale", x, y);
      lines.push_back(sprintfBuffer);
    }
    inline void rotate(const double degrees)
    {
      sprintf(sprintfBuffer, "%.3lf rotate", degrees);
      lines.push_back(sprintfBuffer);
    }
};

#endif
