/*****
 * drawfill.h
 * Andy Hammerlindl 2002/06/06
 *
 * Stores a cyclic path that will outline a filled shape in a picture.
 *****/

#ifndef DRAWFILL_H
#define DRAWFILL_H

#include "drawelement.h"
#include "path.h"

namespace camp {

class drawFill : public drawSuperPathPenBase {
protected:
  bool stroke;
public:
  void noncyclic() {
    reportError("non-cyclic path cannot be filled");
  }

  drawFill(const vm::array& src, bool stroke, pen pentype,
           const string& key="") :
    drawElement(key), drawSuperPathPenBase(src,pentype), stroke(stroke) {
    if(!stroke && !cyclic()) noncyclic();
  }

  bool svg() {return true;}

  // dvisvgm doesn't yet support SVG patterns.
  bool svgpng() {return pentype.fillpattern() != "";}

  virtual ~drawFill() {}

  virtual bool draw(psfile *out);

  virtual void palette(psfile *out) {
    penSave(out);
    penTranslate(out);
  }
  virtual void fill(psfile *out) {
    out->setpen(pentype);
    if(stroke) out->strokepath();
    out->fill(pentype);
    penRestore(out);
  };

  drawElement *transformed(const transform& t);
};

class drawShade : public drawFill {
public:
  drawShade(const vm::array& src, bool stroke, pen pentype,
            const string& key="")
    : drawFill(src,stroke,pentype,key) {}

  void bounds(bbox& b, iopipestream& iopipe, boxvector& vbox,
              bboxlist& bboxstack) {
    if(stroke) strokebounds(b);
    else drawSuperPathPenBase::bounds(b,iopipe,vbox,bboxstack);
  }

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

  // Shading in SVG is incomplete and not supported at all by dvisvgm --pdf.
  bool svgpng() {return true;}

  virtual void beginshade(psfile *out)=0;
  virtual void shade(psfile *out)=0;

  bool draw(psfile *out) {
    if(pentype.invisible() || empty()) return true;

    palette(out);
    beginshade(out);
    writeclippath(out);
    if(stroke) strokepath(out);
    out->endpsclip(pentype.Fillrule());
    shade(out);
    out->grestore();
    return true;
  }
};

class drawLatticeShade : public drawShade {
protected:
  vm::array pens;
  const transform T;
public:
  drawLatticeShade(const vm::array& src, bool stroke,
                   pen pentype, const vm::array& pens,
                   const camp::transform& T=identity, const string& key="")
    : drawShade(src,stroke,pentype,key), pens(pens), T(T) {}

  void palette(psfile *out) {
    out->gsave();
  }

  void beginshade(psfile *out) {
    out->beginlatticeshade(pens,bpath);
  }

  void shade(psfile *out) {
    bbox b;
    for(size_t i=0; i < size; i++) {
      path p=vm::read<path>(P,i).transformed(inverse(T));
      if(stroke)
        drawPathPenBase::strokebounds(b,p);
      else
        b += p.bounds();
    }
    out->latticeshade(pens,T*matrix(b.Min(),b.Max()));
  }

  drawElement *transformed(const transform& t);
};

class drawAxialShade : public drawShade {
protected:
  pair a;
  bool extenda;
  pen penb;
  pair b;
  bool extendb;
  ColorSpace colorspace;
public:
  drawAxialShade(const vm::array& src, bool stroke,
                 pen pentype, pair a, bool extenda, pen penb, pair b,
                 bool extendb, const string& key="")
    : drawShade(src,stroke,pentype,key), a(a), extenda(extenda),
      penb(penb), b(b), extendb(extendb) {}

  bool svgpng() {return !extenda || !extendb || pdf();}

  void palette(psfile *out);

  void beginshade(psfile *out) {
    out->begingradientshade(true,colorspace,pentype,a,0,penb,b,0);
  }

  void shade(psfile *out) {
    out->gradientshade(true,colorspace,pentype,a,0,extenda,penb,b,0,extendb);
  }

  drawElement *transformed(const transform& t);
};

class drawRadialShade : public drawAxialShade {
protected:
  double ra;
  double rb;
public:
  drawRadialShade(const vm::array& src, bool stroke,
                  pen pentype, pair a, double ra, bool extenda, pen penb,
                  pair b, double rb, bool extendb, const string& key="")
    : drawAxialShade(src,stroke,pentype,a,extenda,penb,b,
                     extendb,key), ra(ra), rb(rb) {}

  bool svgpng() {return a != b || ra > 0.0 || !extenda || !extendb || pdf();}

  void beginshade(psfile *out) {
    out->begingradientshade(false,colorspace,pentype,a,ra,penb,b,rb);
  }

  void shade(psfile *out) {
    out->gradientshade(false,colorspace,pentype,a,ra,extenda,penb,b,rb,extendb);
  }

  drawElement *transformed(const transform& t);
};

class drawGouraudShade : public drawShade {
protected:
  vm::array pens,vertices,edges;
public:
  drawGouraudShade(const vm::array& src, bool stroke,
                   pen pentype, const vm::array& pens,
                   const vm::array& vertices, const vm::array& edges,
                   const string& key="")
    : drawElement(key), drawShade(src,stroke,pentype,key), pens(pens),
      vertices(vertices), edges(edges) {}

  bool svgpng() {return settings::xasy || !settings::getSetting<bool>("svgemulation") || pdf();}

  void palette(psfile *out) {
    out->gsave();
  }

  void beginshade(psfile *out) {
    out->begingouraudshade(pens,vertices,edges);
  }

  void shade(psfile *out) {
    out->gouraudshade(pentype,pens,vertices,edges);
  }

  drawElement *transformed(const transform& t);
};

class drawTensorShade : public drawShade {
protected:
  vm::array pens,boundaries,z;
public:
  drawTensorShade(const vm::array& src, bool stroke,
                  pen pentype, const vm::array& pens,
                  const vm::array& boundaries, const vm::array& z,
                  const string& key="") :
    drawShade(src,stroke,pentype,key), pens(pens), boundaries(boundaries),
    z(z) {
  }

  void palette(psfile *out) {
    out->gsave();
  }

  void beginshade(psfile *out) {}

  void shade(psfile *out) {
    out->tensorshade(pentype,pens,boundaries,z);
  }

  drawElement *transformed(const transform& t);
};

class drawFunctionShade : public drawFill {
protected:
  string shader;
public:
  drawFunctionShade(const vm::array& src, bool stroke,
                    pen pentype, const string& shader, const string& key="")
    : drawFill(src,stroke,pentype,key), shader(shader) {
    string texengine=settings::getSetting<string>("tex");
    if(!settings::pdf(texengine))
      reportError("functionshade is not implemented for the '"+texengine+
                  "' tex engine");
  }

  virtual ~drawFunctionShade() {}

  bool draw(psfile *out) {return false;}

  bool write(texfile *, const bbox&);

  bool islabel() {return true;}

  drawElement *transformed(const transform& t);
};

}

#endif
