/*
 * v3dfile.cc
 * V3D Export class
 *
 * Supakorn "Jamie" Rassameemasmuang <jamievlin@outlook.com> and
 * John C. Bowman
 */

#include "v3dfile.h"

#ifdef HAVE_LIBTIRPC

#ifdef HAVE_LIBGLM

#include "drawelement.h"
#include "makeUnique.h"

namespace camp
{

using settings::getSetting;

void v3dfile::writeInit()
{
  uint32_t doubleprecision = !singleprecision;
  getXDRFile() << v3dVersion << doubleprecision;
  addHeaders();

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

void v3dfile::addHeaders()
{
  getXDRFile() << v3dtypes::header;
  std::vector<std::unique_ptr<AHeader>> headers;

  headers.emplace_back(make_unique<Uint32Header>(v3dheadertypes::canvasWidth, gl::fullWidth));
  headers.emplace_back(make_unique<Uint32Header>(v3dheadertypes::canvasHeight, gl::fullHeight));
  headers.emplace_back(make_unique<Uint32Header>(v3dheadertypes::absolute, getSetting<bool>("absolute")));
  headers.emplace_back(make_unique<TripleHeader>(v3dheadertypes::minBound, triple(gl::Xmin, gl::Ymin, gl::Zmin)));
  headers.emplace_back(make_unique<TripleHeader>(v3dheadertypes::maxBound, triple(gl::Xmax, gl:: Ymax, gl::Zmax)));
  headers.emplace_back(make_unique<Uint32Header>(v3dheadertypes::orthographic, gl::orthographic));
  headers.emplace_back(make_unique<DoubleHeader>(v3dheadertypes::angleOfView, gl::Angle));
  headers.emplace_back(make_unique<DoubleHeader>(v3dheadertypes::initialZoom, gl::Zoom0));
  if(gl::Shift != pair(0.0,0.0))
    headers.emplace_back(make_unique<PairHeader>(v3dheadertypes::viewportShift, gl::Shift*gl::Zoom0));
  headers.emplace_back(make_unique<PairHeader>(v3dheadertypes::viewportMargin, gl::Margin));

  for(size_t i=0; i < gl::nlights; ++i) {
    size_t i4=4*i;
    headers.emplace_back(make_unique<LightHeader>(
                           gl::Lights[i],
                           prc::RGBAColour(gl::Diffuse[i4], gl::Diffuse[i4+1], gl::Diffuse[i4+2], 1.0)
                           ));
  }

  headers.emplace_back(make_unique<RGBAHeader>(
                         v3dheadertypes::background,
                         prc::RGBAColour(gl::Background[0],gl::Background[1],gl::Background[2],gl::Background[3])));

  headers.emplace_back(make_unique<DoubleHeader>(v3dheadertypes::zoomFactor, getSetting<double>("zoomfactor")));
  headers.emplace_back(make_unique<DoubleHeader>(
                         v3dheadertypes::zoomPinchFactor, getSetting<double>("zoomPinchFactor")));
  headers.emplace_back(make_unique<DoubleHeader>(
                         v3dheadertypes::zoomPinchCap, getSetting<double>("zoomPinchCap")));
  headers.emplace_back(make_unique<DoubleHeader>(v3dheadertypes::zoomStep, getSetting<double>("zoomstep")));
  headers.emplace_back(make_unique<DoubleHeader>(
                         v3dheadertypes::shiftHoldDistance, getSetting<double>("shiftHoldDistance")));
  headers.emplace_back(make_unique<DoubleHeader>(
                         v3dheadertypes::shiftWaitTime, getSetting<double>("shiftWaitTime")));
  headers.emplace_back(make_unique<DoubleHeader>(
                         v3dheadertypes::vibrateTime, getSetting<double>("vibrateTime")));

  getXDRFile() << (uint32_t) headers.size();
  for(const auto& header : headers) {
    getXDRFile() << header->ty << header->getWordSize(singleprecision);
    header->writeContent(getXDRFile());
  }
}

void v3dfile::addCenters()
{
  getXDRFile() << v3dtypes::centers;
  size_t nelem=drawElement::centers.size();
  getXDRFile() << (uint32_t) nelem;
  if(nelem > 0)
    addTriples(drawElement::centers.data(), nelem);
}

void v3dfile::addTriples(triple const* triples, size_t n)
{
  for(size_t i=0; i < n; ++i)
    getXDRFile() << triples[i];
}

void v3dfile::addColors(prc::RGBAColour const* col, size_t nc)
{
  for(size_t i=0; i < nc; ++i)
    getXDRFile() << col[i];
}


void v3dfile::addPatch(triple const* controls, prc::RGBAColour const* c)
{
  getXDRFile() << (c ? v3dtypes::bezierPatchColor : v3dtypes::bezierPatch);
  addTriples(controls,16);
  addCenterIndexMat();

  if(c)
    addColors(c,4);
}

void v3dfile::addStraightPatch(triple const* controls,
                               prc::RGBAColour const* c)
{
  getXDRFile() << (c ? v3dtypes::quadColor : v3dtypes::quad);
  addTriples(controls,4);
  addCenterIndexMat();

  if(c)
    addColors(c,4);
}

void v3dfile::addBezierTriangle(triple const* controls,
                                prc::RGBAColour const* c)
{
  getXDRFile() << (c ? v3dtypes::bezierTriangleColor : v3dtypes::bezierTriangle);
  addTriples(controls,10);
  addCenterIndexMat();

  if(c)
    addColors(c,3);
}

void v3dfile::addStraightBezierTriangle(triple const* controls,
                                        prc::RGBAColour const* c)
{
  getXDRFile() << (c ? v3dtypes::triangleColor : v3dtypes::triangle);
  addTriples(controls,3);
  addCenterIndexMat();

  if(c)
    addColors(c,3);
}

void v3dfile::addMaterial(Material const& mat)
{
  getXDRFile() << v3dtypes::material;
  addvec4(mat.diffuse);
  addvec4(mat.emissive);
  addvec4(mat.specular);
  glm::vec4 vec=mat.parameters;
  getXDRFile() << static_cast<float>(vec.x) << static_cast<float>(vec.y)
               << static_cast<float>(vec.z);
}

void v3dfile::addCenterIndexMat()
{
  getXDRFile() << (uint32_t) drawElement::centerIndex << (uint32_t) materialIndex;
}

void v3dfile::addvec4(glm::vec4 const& vec)
{
  getXDRFile() << static_cast<float>(vec.x) << static_cast<float>(vec.y)
               << static_cast<float>(vec.z) << static_cast<float>(vec.w);
}

void v3dfile::addHemisphere(triple const& center, double radius, double const& polar, double const& azimuth)
{
  getXDRFile() << v3dtypes::halfSphere << center << radius;
  addCenterIndexMat();
  getXDRFile() << polar << azimuth;
}

void v3dfile::addSphere(triple const& center, double radius)
{
  getXDRFile() << v3dtypes::sphere << center << radius;
  addCenterIndexMat();
}

void
v3dfile::addCylinder(triple const& center, double radius, double height, double const& polar, double const& azimuth,
                     bool core)
{
  getXDRFile() << v3dtypes::cylinder << center << radius << height;
  addCenterIndexMat();
  getXDRFile() << polar << azimuth << core;
}

void v3dfile::addDisk(triple const& center, double radius, double const& polar, double const& azimuth)
{
  getXDRFile() << v3dtypes::disk << center << radius;
  addCenterIndexMat();
  getXDRFile() << polar << azimuth;
}

void v3dfile::addTube(triple const* g, double width, bool core)
{
  getXDRFile() << v3dtypes::tube;
  for(int i=0; i < 4; ++i)
    getXDRFile() << g[i];
  getXDRFile() << width;
  addCenterIndexMat();
  getXDRFile() << core;
}

void v3dfile::addTriangles(size_t nP, triple const* P, size_t nN,
                           triple const* N, size_t nC,
                           prc::RGBAColour const* C, size_t nI,
                           uint32_t const (* PI)[3], uint32_t const (* NI)[3],
                           uint32_t const (* CI)[3])
{
  getXDRFile() << v3dtypes::triangles;
  getXDRFile() << (uint32_t) nI;

  getXDRFile() << (uint32_t) nP;
  addTriples(P,nP);

  getXDRFile() << (uint32_t) nN;
  addTriples(N,nN);

  bool explicitNI=false;
  for(size_t i=0; i < nI; ++i) {
    const uint32_t *PIi=PI[i];
    const uint32_t *NIi=NI[i];
    if(distinct(NIi,PIi)) {
      explicitNI=true;
      break;
    }
  }
  getXDRFile() << (uint32_t) explicitNI;

  getXDRFile() << (uint32_t) nC;
  bool explicitCI=false;
  if(nC) {
    addColors(C,nC);
    for(size_t i=0; i < nI; ++i) {
      const uint32_t *PIi=PI[i];
      const uint32_t *CIi=CI[i];
      if(distinct(CIi,PIi)) {
        explicitNI=true;
        break;
      }
    }
    getXDRFile() << (uint32_t) explicitCI;
  }

  for(size_t i=0; i < nI; ++i) {
    const uint32_t *PIi=PI[i];
    const uint32_t *NIi=NI[i];
    addIndices(PIi);
    if(explicitNI)
      addIndices(NIi);
    if(nC) {
      const uint32_t *CIi=CI[i];
      if(explicitCI)
        addIndices(CIi);
    }
  }

  addCenterIndexMat();
}

void v3dfile::addIndices(uint32_t const* v)
{
  getXDRFile() << v[0] << v[1] << v[2];
}

void v3dfile::addCurve(triple const& z0, triple const& c0, triple const& c1,
                       triple const& z1)
{
  getXDRFile() << v3dtypes::curve << z0 << c0 << c1 << z1;
  addCenterIndexMat();

}

void v3dfile::addCurve(triple const& z0, triple const& z1)
{
  getXDRFile() << v3dtypes::line << z0 << z1;
  addCenterIndexMat();
}

void v3dfile::addPixel(triple const& z0, double width)
{
  getXDRFile() << v3dtypes::pixel << z0 << width;
  getXDRFile() << (uint32_t) materialIndex;
}

void v3dfile::finalize()
{
  if(!finalized) {
    addCenters();
    finalized=true;
  }
}

// gzv3dfile

xdr::oxstream& gzv3dfile::getXDRFile()
{
  return memxdrfile;
}

gzv3dfile::gzv3dfile(string const& name, bool singleprecision): v3dfile(singleprecision), memxdrfile(singleprecision), name(name), destroyed(false)
{
  writeInit();
}

gzv3dfile::~gzv3dfile()
{
  close();
}

void gzv3dfile::close()
{
  if(!destroyed) {
    finalize();

    std::vector<uint8_t> const resultingData = memxdrfile.createCopyOfCurrentData();

    memxdrfile.close();
    gzFile file=gzopen(name.c_str(), "wb9");
    gzwrite(file, resultingData.data(), resultingData.size());
    gzclose(file);
    if(settings::verbose > 0)
      cout << "Wrote " << name << endl;
    destroyed=true;
  }
}

uint32_t LightHeader::getWordSize(bool singleprecision) const
{
  return (singleprecision ? 1 : 2)*3+3;
}

void LightHeader::writeContent(xdr::oxstream& ox) const
{
  ox << direction << (float) color.R << (float) color.G << (float) color.B;
}

LightHeader::LightHeader(triple const& direction, prc::RGBAColour const& color) :
  AHeader(v3dheadertypes::light), direction(direction), color(color)
{
}

} //namespace camp

#endif

#endif
