#include "jsfile.h"

#include "settings.h"
#include "glrender.h"
#include "drawelement.h"

using namespace settings;

namespace camp {

const string s="document.asy.";

#ifndef HAVE_LIBGLM
size_t materialIndex=0;
#endif

jsfile::jsfile() : finished(false), fileName(""), transformInitialized(false)
{

}

jsfile::jsfile(string name) : finished(false), fileName(name), transformInitialized(false)
{
  open(name);
}

jsfile::~jsfile()
{
  if (!finished)
    {
      finish(fileName);
    }
}

void jsfile::close()
{
  if (!finished)
    {
      finish(fileName);
    }
}

void jsfile::copy(string name, bool header)
{
  std::ifstream fin(locateFile(name).c_str());
  string s;
  if(header) getline(fin,s);
  while(getline(fin,s))
    out << s << newl;
}

void jsfile::header(string name)
{
  out.open(name);
  out << "<!DOCTYPE html>" << newl << newl;
}

void jsfile::meta(string name, bool svg)
{
  out << "<html lang=\"\">" << newl
      << newl
      << "<head>" << newl
      << "<title>" << stripExt(name) << "</title>" << newl
      << newl
      << "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/>" << newl;
  if(svg)
    out << "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>";
  else
    out << "<meta name=\"viewport\" content=\"user-scalable=no\"/>";
  out << newl << "<style>" << newl;
  if(svg && !getSetting<bool>("absolute"))
    out << "svg, #container {" << newl
        << "display: block;" << newl
        << "width: 100vw;" << newl
        << "height: 100vh;" << newl
        << "}" << newl;
  out << "body {margin: 0;}" << newl
      << "</style>" << newl;
  if(svg)
    out << "</head>" << newl;
  out << newl;
}

void jsfile::footer(string name)
{
  out << newl << "</body>"
      << newl << newl << "</html>"
      << newl;
  out.flush();
  if(verbose > 0)
    cout << "Wrote " << name << endl;
}

void jsfile::svgtohtml(string prefix)
{
  string name=buildname(prefix,"html");
  header(name);
  meta(name);

  out << "<body>" << newl << newl;
  copy(locateFile(auxname(prefix,"svg")),true);
  footer(name);
  finished=true;
}

void jsfile::comment(string name)
{
#ifdef HAVE_LIBGLM
  out << "<!-- Use the following line to embed this file within another web page:" << newl
      << newl
      << "<iframe src=\"" << name
      << "\" width=\"" << gl::fullWidth
      << "\" height=\"" << gl::fullHeight
      << "\" frameborder=\"0\"></iframe>" << newl
      << newl
      << "-->" << newl << newl;
#endif
}

void jsfile::open(string name)
{
  header(name);
  comment(name);
  meta(name,false);

#ifdef HAVE_LIBGLM
  out.precision(getSetting<Int>("digits"));

  bool ibl=getSetting<bool>("ibl");
  bool webgl2=ibl || getSetting<bool>("webgl2");
  if(ibl)
    out << "<script src=\"https://vectorgraphics.gitlab.io/asymptote/ibl/tinyexr.js\">"
        << newl << "</script>" << newl;

  if(getSetting<bool>("offline")) {
    out << "<script>" << newl;
    copy(locateFile(AsyGL));
    out << newl << "</script>" << newl;
  } else
    out << "<script" << newl << "src=\""
        << getSetting<string>("asygl") << "\">" << newl << "</script>" << newl;

  out << newl << "<script>" << newl;
  out << newl
      << s << "canvasWidth=" << gl::fullWidth << ";" << newl
      << s << "canvasHeight=" << gl::fullHeight << ";" << newl << newl
      << s << "webgl2=" << std::boolalpha << webgl2 << ";"
      << newl
      << s << "ibl=" << std::boolalpha << ibl << ";"
      << newl
      << s << "absolute=" << std::boolalpha << getSetting<bool>("absolute") << ";"
      << s << "autoplay=" << std::boolalpha << getSetting<bool>("autoplay") << ";"
      << newl;
  if(ibl) {
    out << s << "imageURL=\"" << getSetting<string>("imageURL")+"/\";" << newl;
    out << s << "image=\"" << getSetting<string>("image") << "\";" << newl << newl;
  }
  out << newl
      <<  s << "minBound=[" << gl::Xmin << "," << gl::Ymin << "," << gl::Zmin << "];"
      << newl
      <<  s << "maxBound=[" << gl::Xmax << "," << gl::Ymax << "," << gl::Zmax << "];"
      << newl
      << s << "orthographic=" << gl::orthographic << ";"
      << newl
      << s << "angleOfView=" << gl::Angle << ";"
      << newl
      << s << "initialZoom=" << gl::Zoom0 << ";" << newl;
    if(gl::Shift != pair(0.0,0.0))
      out << s << "viewportShift=" << gl::Shift*gl::Zoom0 << ";" << newl;
    out << s << "viewportMargin=" << gl::Margin << ";" << newl << newl
        << s << "zoomFactor=" << getSetting<double>("zoomfactor") << ";" << newl
        << s << "zoomPinchFactor=" << getSetting<double>("zoomPinchFactor") << ";"
      << newl
        << s << "zoomPinchCap=" << getSetting<double>("zoomPinchCap") << ";" << newl
        << s << "zoomStep=" << getSetting<double>("zoomstep") << ";" << newl
        << s << "shiftHoldDistance=" << getSetting<double>("shiftHoldDistance") << ";"
      << newl
        << s << "shiftWaitTime=" << getSetting<double>("shiftWaitTime") << ";"
      << newl
        << s << "vibrateTime=" << getSetting<double>("vibrateTime") << ";"
        << newl << newl;
  out << s << "background=[" << gl::Background[0] << "," << gl::Background[1] << ","
      << gl::Background[2] << "," << gl::Background[3] << "];"
      << newl << newl;
  out << s << "Transform=[" << gl::T[0];
  for(int i=1; i < 16; ++i)
    out << "," << newl << gl::T[i];
  out << "];" << newl << newl;

  for(size_t i=0; i < gl::nlights; ++i) {
    size_t i4=4*i;
    out << "light(" << newl
        << gl::Lights[i] << "," << newl
        << "[" << gl::Diffuse[i4] << "," << gl::Diffuse[i4+1] << ","
        << gl::Diffuse[i4+2] << "]);" << newl;
  }
  out << newl;

  camp::clearCenters();
  camp::clearMaterials();
#endif
}

void jsfile::finish(string name)
{
#ifdef HAVE_LIBGLM
  finished=true;
  size_t ncenters=drawElement::centers.size();
  if(ncenters > 0) {
    out << s << "Centers=[";
    size_t last=ncenters-1;
    for(size_t i=0; i < last; ++i)
      out << newl << drawElement::centers[i] << ",";
    out << newl << drawElement::centers[last] << newl << "];" << newl;
  }
  out << "</script>"
      << newl << "</head>"
      << newl << newl << "<body style=\"overflow: hidden;\" onload=\"webGLStart();\">"
      << newl << "<canvas id=\"Asymptote\" width=\""
      << gl::fullWidth << "\" height=\"" <<  gl::fullHeight
      << "\" style=\"border: none; cursor: pointer;\">"
      << newl << "</canvas>";
  footer(name);
#endif
}

void jsfile::addColor(const prc::RGBAColour& c)
{
  out << "[" << c.R << "," << c.G << "," << c.B << "," << c.A << "]";
}

void jsfile::addIndices(const uint32_t *I)
{
  out << "[" << I[0] << "," << I[1] << "," << I[2] << "]";
}

void jsfile::addRawPatch(triple const* controls, size_t n,
                         const prc::RGBAColour *c, size_t nc)
{
  if(n) {
    addKey();
    out << "patch([" << newl;
    size_t last=n-1;
    for(size_t i=0; i < last; ++i)
      out << controls[i] << "," << newl;
    out << controls[last] << newl << "],"
        << drawElement::centerIndex << "," << materialIndex;
    if(c) {
      out << ",[" << newl;
      for(size_t i=0; i < nc; ++i) {
        addColor(c[i]);
        out << "," << newl;
      }
      out << "]";
    }
    out << ");" << newl << newl;
  }
}

void jsfile::addCurve(const triple& z0, const triple& c0,
                      const triple& c1, const triple& z1)
{
  addKey();
  out << "curve([" << newl;
  out << z0 << "," << newl
      << c0 << "," << newl
      << c1 << "," << newl
      << z1 << newl << "],"
      << drawElement::centerIndex << "," << materialIndex
      << ");" << newl << newl;
}

void jsfile::addCurve(const triple& z0, const triple& z1)
{
  addKey();
  out << "curve([" << newl;
  out << z0 << "," << newl
      << z1 << newl << "],"
      << drawElement::centerIndex << "," << materialIndex
      << ");" << newl << newl;
}

void jsfile::addPixel(const triple& z0, double width)
{
  addKey();
  out << "pixel(" << newl;
  out << z0 << "," << width << "," << newl << materialIndex
      << ");" << newl << newl;
}

#ifdef HAVE_LIBGLM
void jsfile::addMaterial(Material const& material)
{
  out << "material(" << newl
      << material
      << ");" << newl << newl;
}
#endif

void jsfile::addTriangles(size_t nP, const triple* P, size_t nN,
                          const triple* N, size_t nC, const prc::RGBAColour* C,
                          size_t nI, const uint32_t (*PI)[3],
                          const uint32_t (*NI)[3], const uint32_t (*CI)[3])
{
  addKey();
  if(nP) {
    out << "Positions=[";
    size_t last=nP-1;
    for(size_t i=0; i < last; ++i)
      out << newl << P[i] << ",";
    out << newl << P[last] << newl << "];" << newl;
  }

  if(nN) {
    size_t last=nN-1;
    out << "Normals=[";
    for(size_t i=0; i < last; ++i)
      out << newl << N[i] << ",";
    out << newl << N[last] << newl << "];" << newl;
  }

  if(nC) {
    size_t last=nC-1;
    out << "Colors=[";
    for(size_t i=0; i < last; ++i) {
      out << newl;
      addColor(C[i]);
      out << ",";
    }
    out << newl;
    addColor(C[last]);
    out << newl << "];" << newl;
  }

  if(nI) {
    out << "Indices=[";
    size_t last=nI-1;
    for(size_t i=0; i < nI; ++i) {
      const uint32_t *PIi=PI[i];
      const uint32_t *NIi=NI[i];
      bool keepNI=distinct(NIi,PIi);
      bool keepCI=nC && distinct(CI[i],PIi);
      out << newl << "[";
      addIndices(PIi);
      if(keepNI || keepCI) {
        out << ",";
        if(keepNI) addIndices(NIi);
      }
      if(keepCI) {
        out << ",";
        addIndices(CI[i]);
      }
      out << "]";
      if(i < last)
        out << ",";
    }
    out << newl << "];" << newl;
  }

  out << "triangles("
      << drawElement::centerIndex << "," << materialIndex
      << ");" << newl << newl;
}

void jsfile::addSphere(const triple& center, double radius)
{
  addKey();
  out << "sphere(" << center << "," << radius << ","
      << drawElement::centerIndex << "," << materialIndex
      << ");" << newl << newl;
}

void jsfile::addHemisphere(const triple& center, double radius,
                           const double& polar, const double& azimuth)
{
  addKey();
  out << "sphere(" << center << "," << radius << ","
      << drawElement::centerIndex << "," << materialIndex
      << "," << newl << "[" << polar << "," << azimuth << "]";
  out << ");" << newl << newl;
}

// core signifies whether to also draw a central line for better small-scale
// rendering.
void jsfile::addCylinder(const triple& center, double radius, double height,
                         const double& polar, const double& azimuth,
                         bool core)
{
  addKey();
  out << "cylinder(" << center << "," << radius << "," << height << ","
      << drawElement::centerIndex << "," << materialIndex
      << "," << newl << "[" << polar << "," << azimuth << "]," << core
      << ");" << newl << newl;
}

void jsfile::addDisk(const triple& center, double radius,
                     const double& polar, const double& azimuth)
{
  addKey();
  out << "disk(" << center << "," << radius << ","
      << drawElement::centerIndex << "," << materialIndex
      << "," << newl << "[" << polar << "," << azimuth << "]"
      << ");" << newl << newl;
}

void jsfile::addTube(const triple *g, double width, bool core)
{
  addKey();
  out << "tube(["
      << g[0] << "," << newl
      << g[1] << "," << newl
      << g[2] << "," << newl
      << g[3] << newl << "],"
      << width << ","
      << drawElement::centerIndex << "," << materialIndex << "," << core
      << ");" << newl << newl;
}

void jsfile::addPatch(triple const* controls, prc::RGBAColour const* c)
{
  addRawPatch(controls,16,c,4);
}

void jsfile::addStraightPatch(triple const* controls,
                              prc::RGBAColour const* c)
{
  addRawPatch(controls,4,c,4);
}

void jsfile::addBezierTriangle(triple const* controls,
                               prc::RGBAColour const* c)
{
  addRawPatch(controls,10,c,3);
}

void jsfile::addStraightBezierTriangle(triple const* controls,
                                       prc::RGBAColour const* c)
{
  addRawPatch(controls,3,c,3);
}

void jsfile::write(const string& s)
{
  out << s;
}

void jsfile::write(double x)
{
  out << x;
}

void jsfile::initTransform()
{
  if(transformInitialized) return;
   transformInitialized=true;
   out << "initTransform();" << newl;
}

}
