/*****
* drawelement.h
* Andy Hammerlindl 2002/06/06
*
* Abstract base class of any drawable item in camp.
*****/

#ifndef DRAWELEMENT_H
#define DRAWELEMENT_H

#include <vector>

#include "common.h"
#include "bbox.h"
#include "bbox3.h"
#include "pen.h"
#include "psfile.h"
#include "texfile.h"
#include "prcfile.h"
#include "jsfile.h"
#include "v3dfile.h"
#include "glrender.h"
#include "arrayop.h"
#include "material.h"

namespace camp {

static const double pixelResolution=1.0; // Adaptive rendering constant.

enum Interaction {EMBEDDED=0,BILLBOARD};

void copyArray4x4C(double*& dest, const vm::array *a);

class box {
 pair p[4];
public:

 box() {}
 box(const pair& a, const pair& b, const pair& c, const pair& d) {
   p[0]=a; p[1]=b; p[2]=c; p[3]=d;
 }

// Returns true if the line a--b intersects box b.
 bool intersect(const pair& a, const pair& b) const
 {
   for(Int i=0; i < 4; ++i) {
     pair A=p[i];
     pair B=p[i < 3 ? i+1 : 0];
     double de=(b.x-a.x)*(A.y-B.y)-(A.x-B.x)*(b.y-a.y);
     if(de != 0.0) {
       de=1.0/de;
       double t=((A.x-a.x)*(A.y-B.y)-(A.x-B.x)*(A.y-a.y))*de;
       double T=((b.x-a.x)*(A.y-a.y)-(A.x-a.x)*(b.y-a.y))*de;
       if(0 <= t && t <= 1 && 0 <= T && T <= 1) return true;
     }
   }
   return false;
 }

 pair operator [] (Int i) const {return p[i];}

 bool intersect(const box& b) const {
   for(Int i=0; i < 4; ++i) {
     pair A=b[i];
     pair B=b[i < 3 ? i+1 : 0];
     if(intersect(A,B)) return true;
   }
   return false;
 }

 double xmax() {
   return max(max(max(p[0].x,p[1].x),p[2].x),p[3].x);
 }

 double ymax() {
   return max(max(max(p[0].y,p[1].y),p[2].y),p[3].y);
 }

 double xmin() {
   return min(min(min(p[0].x,p[1].x),p[2].x),p[3].x);
 }

 double ymin() {
   return min(min(min(p[0].y,p[1].y),p[2].y),p[3].y);
 }

};

class bbox2 {
public:
 double x,y,X,Y;
 bbox2(size_t n, const triple *v) {
   Bounds(v[0]);
   for(size_t i=1; i < n; ++i)
     bounds(v[i]);
 }

 bbox2(const triple& m, const triple& M) {
   Bounds(m);
   bounds(triple(m.getx(),m.gety(),M.getz()));
   bounds(triple(m.getx(),M.gety(),m.getz()));
   bounds(triple(m.getx(),M.gety(),M.getz()));
   bounds(triple(M.getx(),m.gety(),m.getz()));
   bounds(triple(M.getx(),m.gety(),M.getz()));
   bounds(triple(M.getx(),M.gety(),m.getz()));
   bounds(M);
 }

 bbox2(const triple& m, const triple& M, const Billboard& BB) {
   Bounds(BB.transform(m));
   bounds(BB.transform(triple(m.getx(),m.gety(),M.getz())));
   bounds(BB.transform(triple(m.getx(),M.gety(),m.getz())));
   bounds(BB.transform(triple(m.getx(),M.gety(),M.getz())));
   bounds(BB.transform(triple(M.getx(),m.gety(),m.getz())));
   bounds(BB.transform(triple(M.getx(),m.gety(),M.getz())));
   bounds(BB.transform(triple(M.getx(),M.gety(),m.getz())));
   bounds(BB.transform(M));
 }

// Is 2D bounding box formed by projecting 3d points in vector v offscreen?
 bool offscreen() {
   double eps=1.0e-2;
   double min=-1.0-eps;
   double max=1.0+eps;
   return X < min || x > max || Y < min || y > max;
 }

 void Bounds(const triple& v) {
   pair V=Transform2T(gl::dprojView,v);
   x=X=V.getx();
   y=Y=V.gety();
 }

 void bounds(const triple& v) {
   pair V=Transform2T(gl::dprojView,v);
   double a=V.getx();
   double b=V.gety();
   if(a < x) x=a;
   else if(a > X) X=a;
   if(b < y) y=b;
   else if(b > Y) Y=b;
 }
};

typedef mem::vector<box> boxvector;

typedef mem::list<bbox> bboxlist;

typedef mem::map<const string,unsigned> groupmap;
typedef mem::vector<groupmap> groupsmap;
typedef mem::map<const triple, int> centerMap;

inline bool operator < (const triple& a, const triple& b) {
 return a.getx() < b.getx() ||
                (a.getx() == b.getx() &&
                 (a.gety() < b.gety() ||
                  (a.gety() == b.gety() &&
                   (a.getz() < b.getz()))));
}

class drawElement : public gc
{
public:
 string KEY;

 drawElement(const string& key="") : KEY(key == "" ? processData().KEY : key)
 {}

 virtual ~drawElement() {}

 static mem::vector<triple> centers;
 static centerMap centermap;
 static size_t centerIndex;

 static pen lastpen;
 static const triple zero;

 // Adjust the bbox of the picture based on the addition of this
 // element. The iopipestream is needed for determining label sizes.
 virtual void bounds(bbox&, iopipestream&, boxvector&, bboxlist&) {}
 virtual void bounds(const double*, bbox3&) {}
 virtual void bounds(bbox3& b) { bounds(NULL, b); }

 // Compute bounds on ratio (x,y)/z for 3d picture (not cached).
 virtual void ratio(const double *t, pair &b, double (*m)(double, double),
                    double fuzz, bool &first) {}

 virtual void minratio(const double *t, pair &b, double fuzz, bool &first) {
   ratio(t,b,camp::min,fuzz,first);
 }

 virtual void maxratio(const double *t,pair &b, double fuzz, bool &first) {
   ratio(t,b,camp::max,fuzz,first);
 }

 virtual void ratio(pair &b, double (*m)(double, double), double fuzz,
                    bool &first) {
   ratio(NULL,b,m,fuzz,first);
 }

 virtual void minratio(pair &b, double fuzz, bool &first) {
   minratio(NULL,b,fuzz,first);
 }

 virtual void maxratio(pair &b, double fuzz, bool &first) {
   maxratio(NULL,b,fuzz,first);
 }

 virtual bool islabel() {return false;}

 virtual bool isnewpage() {return false;}

 virtual bool islayer() {return false;}

 virtual bool is3D() {return false;}

// Implement element as raw SVG code?
 virtual bool svg() {return false;}

// Implement SVG element as png image?
 virtual bool svgpng() {return false;}

 virtual bool beginclip() {return false;}
 virtual bool endclip() {return false;}

 virtual bool begingroup() {return false;}
 virtual bool begingroup3() {return false;}

 virtual bool endgroup() {return false;}
 virtual bool endgroup3() {return false;}

 virtual const double* transf3() {return NULL;}

 virtual void save(bool b) {}

 // Output to a PostScript file
 virtual bool draw(psfile *) {
   return false;
 }

 // Output to a TeX file
 virtual bool write(texfile *, const bbox&) {
   return false;
 }

 // Output to a PRC file
 virtual bool write(prcfile *out, unsigned int *count, double compressionlimit,
                    groupsmap& groups) {
   return false;
 }

 // Output to a WebGL or v3d file
 virtual bool write(abs3Doutfile *out) {
   return false;
 }

 // Used to compute deviation of a surface from a quadrilateral.
 virtual void displacement() {}

 // Render with OpenGL
 virtual void render(double size2, const triple& Min, const triple& Max,
                     double perspective, bool remesh)
 {}

 virtual void meshinit() {}

 size_t centerindex(const triple& center) {
   centerMap::iterator p=centermap.find(center);
   if(p != centermap.end()) centerIndex=p->second;
   else {
     centers.push_back(center);
     centermap[center]=centerIndex=centers.size();
   }
   return centerIndex;
 }

 // Transform as part of a picture.
 virtual drawElement *transformed(const transform&) {
   return this;
 }

 virtual drawElement *transformed(const double* t) {
   return this;
 }

};

// Hold transform of an object.
class drawElementLC : public virtual drawElement {
public:
 double *T; // Keep track of accumulative picture transform

 drawElementLC() : T(NULL) {}

 drawElementLC(const double *t) : T(NULL) {
   copyTransform3(T,t);
 }

 drawElementLC(const vm::array& t) : T(NULL) {
   copyArray4x4C(T,&t);
 }

 drawElementLC(const double* t, const drawElementLC *s) :
   drawElement(s->KEY), T(NULL) {
   multiplyTransform3(T,t,s->T);
 }

 virtual ~drawElementLC() {}

 virtual bool is3D() {return true;}

 virtual const double* transf3() {return T;}

 virtual drawElement* transformed(const double* t) {
   return new drawElementLC(t,this);
 }
};

// Base class for drawElements that involve paths.
class drawPathBase : public virtual drawElement {
protected:
 path p;

 path transpath(const transform& t) const {
   return p.transformed(t);
 }

public:
 drawPathBase() {}
 drawPathBase(path p) : p(p) {}

 virtual ~drawPathBase() {}

 virtual void bounds(bbox& b, iopipestream&, boxvector&, bboxlist&) {
   b += p.bounds();
 }

 virtual void writepath(psfile *out,bool) {
   out->write(p);
 }

 virtual void writeclippath(psfile *out, bool newpath=true) {
   out->writeclip(p,newpath);
 }

 virtual void writeshiftedpath(texfile *out) {
   out->writeshifted(p);
 }
};

// Base class for drawElements that involve paths and pens.
class drawPathPenBase : public drawPathBase {
protected:
 pen pentype;

 pen transpen(const transform& t) const {
   return camp::transformed(shiftless(t),pentype);
 }

public:
 drawPathPenBase(path p, pen pentype) :
   drawPathBase(p), pentype(pentype) {}

 drawPathPenBase(pen pentype) :
   pentype(pentype) {}

 virtual bool empty() {
   return p.empty();
 }

 virtual bool cyclic() {
   return p.cyclic();
 }

 void strokebounds(bbox& b, const path& p);

 virtual void penSave(psfile *out)
 {
   if (!pentype.getTransform().isIdentity())
     out->gsave();
 }

 virtual void penTranslate(psfile *out)
 {
   out->translate(shiftpair(pentype.getTransform()));
 }

 virtual void penConcat(psfile *out)
 {
   out->concat(shiftless(pentype.getTransform()));
 }

 virtual void penRestore(psfile *out)
 {
   if (!pentype.getTransform().isIdentity())
     out->grestore();
 }
};

// Base class for drawElements that involve superpaths and pens.
class drawSuperPathPenBase : public drawPathPenBase {
protected:
 vm::array P;
 size_t size;
 bbox bpath;

 vm::array transpath(const transform& t) const {
   vm::array *Pt=new vm::array(size);
   for(size_t i=0; i < size; i++)
     (*Pt)[i]=vm::read<path>(P,i).transformed(t);
   return *Pt;
 }

public:
 drawSuperPathPenBase(const vm::array& P, pen pentype) :
   drawPathPenBase(pentype), P(P), size(P.size()) {}

 bool empty() {
   for(size_t i=0; i < size; i++)
     if(vm::read<path>(P,i).size() != 0) return false;
   return true;
 }

 bool cyclic() {
   for(size_t i=0; i < size; i++)
     if(!vm::read<path>(P,i).cyclic()) return false;
   return true;
 }

 void bounds(bbox& b, iopipestream&, boxvector&, bboxlist&) {
   for(size_t i=0; i < size; i++)
     bpath += vm::read<path>(P,i).bounds();
   b += bpath;
 }

 void strokepath(psfile *out) {
   out->strokepath();
 }

 void strokebounds(bbox& b) {
   for(size_t i=0; i < size; i++)
     drawPathPenBase::strokebounds(bpath,vm::read<path>(P,i));
   b += bpath;
 }

 void writepath(psfile *out, bool newpath=true) {
   if(size > 0) out->write(vm::read<path>(P,0),newpath);
   for(size_t i=1; i < size; i++)
     out->write(vm::read<path>(P,i),false);
 }

 void writeclippath(psfile *out, bool newpath=true) {
   if(size > 0) out->writeclip(vm::read<path>(P,0),newpath);
   for(size_t i=1; i < size; i++)
     out->writeclip(vm::read<path>(P,i),false);
 }

 void writeshiftedpath(texfile *out) {
   for(size_t i=0; i < size; i++)
     out->writeshifted(vm::read<path>(P,i),i == 0);
 }
};

#ifdef HAVE_LIBGLM
void setcolors(const prc::RGBAColour& diffuse, const prc::RGBAColour& emissive,
              const prc::RGBAColour& specular, double shininess,
              double metallic, double fresnel0, abs3Doutfile *out=NULL);
#endif



}

GC_DECLARE_PTRFREE(camp::box);
GC_DECLARE_PTRFREE(camp::drawElement);

#endif