// Gmsh - Copyright (C) 1997-2015 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// bugs and problems to the public mailing list <gmsh@geuz.org>.
//
// Contributed by Sebastian Eiser

#include <stdlib.h>
#include <stdio.h>
#include "GmshConfig.h"
#include "PixelBuffer.h"
#include "OS.h"
#include "Context.h"
#include "PView.h"
#include "PViewData.h"
#include "Numeric.h"
#include "Options.h"
#include "StringUtils.h"
#include "gl2png.h"

static int assembleColmapStr(const int num, const int intType, int &samples,
                             std::string &ret)
{
  GmshColorTable * ct = GetColorTable(num); // i
  ret.assign("\\pgfplotsset{\ncolormap={gmshcolormap}{%% note: "
             "Only needed once if colorbars do not change\n");

  samples = (int) opt_view_nb_iso(num, GMSH_GET, 0);
  double step = 0.;
  switch (intType){
    case 2: samples=64; break; // continuous is approximated by 64 samples
    case 3: break; // filled (sampled colorbar)
    case 1: break; // iso lines
    case 4:  // numericals
    default:
      return 1;
  }
  step = (double) (1.*ct->size)/(samples-1.);
  int r, g, b, a;
  unsigned int idx;
  char tmp[265];
  for (unsigned int j=0; j < (unsigned int) samples-1; j++ /*j+=4*/) {
    idx = (unsigned int)j*step;
    //printf("j=%d, idx=%d step=%f\n", j, idx, step);
    r = CTX::instance()->unpackRed(ct->table[idx]);
    g = CTX::instance()->unpackGreen(ct->table[idx]);
    b = CTX::instance()->unpackBlue(ct->table[idx]);
    a = CTX::instance()->unpackAlpha(ct->table[idx]);
    if (a != 255)
      Msg::Warning("PGF export does not handle transparent colormaps");

    sprintf(tmp, "rgb255=(%d,%d,%d) ",r,g,b);
    ret.append(tmp);

    if (intType != 2) // sampled
      // reinsert, because then the end color is interpreted correctly
      // for shader=flat mean
      ret.append(tmp);
  }
  //endpoint
  r = CTX::instance()->unpackRed(ct->table[ct->size-1]);
  g = CTX::instance()->unpackGreen(ct->table[ct->size-1]);
  b = CTX::instance()->unpackBlue(ct->table[ct->size-1]);
  sprintf(tmp, "rgb255=(%d,%d,%d) ",r,g,b);
  ret.append(tmp);
  if (intType != 2) // sampled
    // reinsert, because then the end color is interpreted correctly
    // for shader=flat mean
    ret.append(tmp);

  ret.append("}\n}%%\n");

  return 0;
}

static int assembleColbarStr(const int num, const int intType, const int samples,
                             std::string &ret)
{
  double cbmin, cbmax;
  cbmin = opt_view_min(num, GMSH_GET, 0);
  cbmax = opt_view_max(num, GMSH_GET, 0);
  int rangeType = (int) opt_view_range_type(num, GMSH_GET,0);
  char tmp[265];
  switch (rangeType) {
    case 2:
      {
        cbmin = opt_view_custom_min(num, GMSH_GET, 0);
        cbmax = opt_view_custom_max(num, GMSH_GET, 0);
        break;
      }
    case 1: // default
    case 3: // per time step FIXME
    default: break;
  }
  ret.assign("\tcolorbar style={\n\t\t%%width=0.5cm, "
             "%% adjust width of colorbar\n"
             "\t\t%%height=6cm,%% adjust height of colorbar,\n");
  if (intType != 2) { // sampled
    sprintf(tmp, "\t\tsamples=%d,\n", samples+1);
    ret.append(tmp);
  }

  int scaleType = (int) opt_view_scale_type(num, GMSH_GET, 0);
  int horizontal = (int) opt_print_pgf_horiz_bar(0, GMSH_GET, 0);
  //1 linear
  //2 log
  //3 double log ???
  if (scaleType == 2) { // log
    // see http://tex.stackexchange.com/questions/23750/log-color-bar-meta-data-in-pgfplot
    if (horizontal)
      sprintf(tmp, "\t\txticklabel={$10^{\\pgfmathparse{\\tick}"
              "\\pgfmathprintnumber\\pgfmathresult}$},\n");
    else
      sprintf(tmp, "\t\tyticklabel={$10^{\\pgfmathparse{\\tick}"
              "\\pgfmathprintnumber\\pgfmathresult}$},\n");
    ret.append(tmp);
  }

  ret.append("\t}]\n\t  %% a dummy plot for the colorbar (invisible):\n");
  if (scaleType == 2) {// log
    cbmin = log10 (cbmin);
    cbmax = log10 (cbmax);
  }
  sprintf(tmp, "\t  \\addplot[point meta min=%f,"
          "point meta max=%f, update limits=false, draw=none, colorbar source]\n\t"
          "coordinates{(1,1)};\n", cbmin, cbmax);
  ret.append(tmp);
  return 0;
}

static int assemblePostAxis(const int num, const int intType, std::string &ret)
{
  int horizontal = (int) opt_print_pgf_horiz_bar(0, GMSH_GET, 0);
  std::string post_var;
  char tmp[265];
  post_var = PView::list[num]->getData()->getName();

  if (!post_var.empty()) {
    sprintf(tmp,"\ttitle={%s},\n", post_var.c_str() );
    ret.assign(tmp);
  }
  ret.append("\tcolorbar,\n\tcolormap name=gmshcolormap,\n");
  if (horizontal) {
    ret.append("\tcolorbar horizontal,\n");
  }
  else {
    ret.append("\tcolorbar right, %% or left...\n");
  }
  if (intType == 3){  // sampled
    ret.append("\tcolorbar sampled,\n");
  }
  else if (intType == 1){
    ret.append("\tcolorbar sampled line,\n");
  }
  return 0;
}

static int getMinMaxOfAxis(const int num, double result[8][3])
{
  double xmin, xmax, ymin, ymax, zmin, zmax;
  // axes ranges
  if (!(int) opt_general_axes_auto_position(0, GMSH_GET,0)) {
    // needs to get manual axes set
    xmin = opt_general_axes_xmin(0, GMSH_GET, 0);
    xmax = opt_general_axes_xmax(0, GMSH_GET, 0);
    ymin = opt_general_axes_ymin(0, GMSH_GET, 0);
    ymax = opt_general_axes_ymax(0, GMSH_GET, 0);
    zmin = opt_general_axes_zmin(0, GMSH_GET, 0);
    zmax = opt_general_axes_zmax(0, GMSH_GET, 0);
    fprintf(stderr,"General axes non auto, using\n");
    fprintf(stderr,"x=(%f,%f), y=(%f,%f), z=(%f,%f)\n",
            xmin, xmax, ymin, ymax, zmin, zmax);
  }
  else if (num >= 0 && !(int) opt_view_axes_auto_position(num, GMSH_GET,0) ) {
    // needs to get manual axes set
    xmin = opt_view_axes_xmin(num, GMSH_GET, 0);
    xmax = opt_view_axes_xmax(num, GMSH_GET, 0);
    ymin = opt_view_axes_ymin(num, GMSH_GET, 0);
    ymax = opt_view_axes_ymax(num, GMSH_GET, 0);
    zmin = opt_view_axes_zmin(num, GMSH_GET, 0);
    zmax = opt_view_axes_zmax(num, GMSH_GET, 0);
    fprintf(stderr,"View axes non auto, using:\n");
    fprintf(stderr,"x=(%f,%f), y=(%f,%f), z=(%f,%f)\n",
            xmin, xmax, ymin, ymax, zmin, zmax);
  }
  else {// default
    xmin = CTX::instance()->min[0];
    xmax = CTX::instance()->max[0];
    ymin = CTX::instance()->min[1];
    ymax = CTX::instance()->max[1];
    zmin = CTX::instance()->min[2];
    zmax = CTX::instance()->max[2];
    fprintf(stderr,"Axes auto, using:\n");
    fprintf(stderr,"x=(%f,%f), y=(%f,%f), z=(%f,%f)\n",
            xmin, xmax, ymin, ymax, zmin, zmax);
  }
  result[0][0] = result[1][0] = result[2][0] = result[3][0] = xmin;
  result[4][0] = result[5][0] = result[6][0] = result[7][0] = xmax;

  result[0][1] = result[1][1] = result[4][1] = result[5][1] = ymin;
  result[2][1] = result[3][1] = result[6][1] = result[7][1] = ymax;

  result[0][2] = result[2][2] = result[4][2] = result[6][2] = zmin;
  result[1][2] = result[3][2] = result[5][2] = result[7][2] = zmax;
  // result = {
  //     { xmin, ymin, zmin}, // origin
  //     { xmin, ymin, zmax}, // y end
  //     { xmin, ymax, zmin}, // y end z end
  //     { xmin, ymax, zmax}, // y end z end
  //     { xmax, ymin, zmin}, // x end
  //     { xmax, ymin, zmax}, // x end y end
  //     { xmax, ymax, zmin}, // x end y end z end
  //     { xmax, ymax, zmax} // x end  z end
  // };
  return 0;
}

static int assemble2d(const int num, const int exportAxis, std::string &axisstr,
                      std::string &plotstr, double *eulerAngles)
{
  double axPts[8][3];
  double factor=1.;
  double xmin, xmax, ymin, ymax;

  axisstr.append("\taxis equal image, %% use png aspect ratio\n");

  if (exportAxis) {
    getMinMaxOfAxis(num, axPts);
    std::string xlab, ylab, zlab;
    xlab = CTX::instance()->axesLabel[0];
    ylab = CTX::instance()->axesLabel[1];
    zlab = CTX::instance()->axesLabel[2];
    if (xlab.empty())
      xlab = "x";
    if (ylab.empty())
      ylab = "y";
    if (zlab.empty())
      zlab = "z";

    fprintf(stderr,"Euler two dim: 0:%f, 1:%f, 2:%f\n",
           eulerAngles[0], eulerAngles[1], eulerAngles[2]);
    int r0 = (int) (eulerAngles[0]+0.5);
    int r1 = (int) (eulerAngles[1]+0.5);
    int r2 = (int) (eulerAngles[2]+0.5);
    if (r0 % 90 != 0 || r1 % 90 != 0 || r2 % 90 !=0) {
      fprintf(stderr,"Euler two dim: 0:%d, 1:%d, 2:%d\n", r0, r1, r2);
      Msg::Error("Please select a plane view (X, Y, Z)");
      return 1;
    }
    if (r0 % 180 == 0 && r1 % 360 == 0 && r2 % 180 == 0) {
      // xy
      xmin=axPts[0][0]; xmax=axPts[4][0]; ymin=axPts[0][1]; ymax=axPts[2][1];
      if (r2 == 180)
        axisstr.append("\tx dir=reverse,\n");
      if ((r2 == 180 && abs(r0) < 1) || (r0 == 180 && abs(r2) < 1))
        axisstr.append("\ty dir=reverse,\n");
      axisstr.append("\txlabel={" + xlab + "},\n");
      axisstr.append("\tylabel={" + ylab + "},\n");
    }
    else if (r0 % 180 == 0 && r1 % 360 ==0 && (r2 == 90 || r2 == 270)) {
      // yx
      xmin=axPts[0][1]; xmax=axPts[2][1]; ymin=axPts[0][0]; ymax=axPts[4][0];
      if (r2 == 90)
        axisstr.append("\tx dir=reverse,\n");
      if (r2 == 270 || (r2 == 90 && r0 == 180))
        axisstr.append("\ty dir=reverse,\n");
      axisstr.append("\txlabel={" + ylab + "},\n");
      axisstr.append("\tylabel={" + xlab + "},\n");
    }
    else if ((r0 == 90 || r0 == 270) && r1 % 360 == 0 &&
             (r2 == 90 || r2 == 270)) {
      xmin=axPts[0][1]; xmax=axPts[2][1]; ymin=axPts[0][2]; ymax=axPts[1][2];
      if(r2 == 90)
        axisstr.append("\tx dir=reverse,\n");
      if(r0 == 90)
        axisstr.append("\ty dir=reverse,\n");
      // yz
      axisstr.append("\txlabel={" + ylab + "},\n");
      axisstr.append("\tylabel={" + zlab + "},\n");
    }
    else if (r0 % 360 == 0 && (r1 == 90 || r1 == 270) && r2 % 180 == 0) {
      // zy
      xmin=axPts[0][2]; xmax=axPts[1][2]; ymin=axPts[0][1]; ymax=axPts[2][1];
      if (r1 == 270)
        axisstr.append("\tx dir=reverse,\n");
      if (r2 == 180)
        axisstr.append("\ty dir=reverse,\n");
      axisstr.append("\txlabel={" + zlab + "},\n");
      axisstr.append("\tylabel={" + ylab + "},\n");
    }
    else if ((r0 == 90 || r0 == 270) && r1 % 360 == 0 && r2 % 180 == 0) {
      // xz
      xmin=axPts[0][0]; xmax=axPts[4][0]; ymin=axPts[0][2]; ymax=axPts[1][2];
      if (r2 == 180) // x dir=reverse
        axisstr.append("\tx dir=reverse,\n");
      if (r0 == 90)
        axisstr.append("\ty dir=reverse,\n");
      axisstr.append("\txlabel={" + xlab + "},\n");
      axisstr.append("\tylabel={" + zlab + "},\n");
    }
    else if (r0 % 360 == 0 && (r1 == 90 || r1 == 270) &&
             (r2 == 90 || r2 == 270)) {
      if (r1 == 270)
        axisstr.append("\tx dir=reverse,\n");
      if (r2 == 270)
        axisstr.append("\ty dir=reverse,\n");
      // zx
      xmin=axPts[0][2]; xmax=axPts[1][2]; ymin=axPts[0][0]; ymax=axPts[4][0];
      axisstr.append("\txlabel={" + zlab + "},\n");
      axisstr.append("\tylabel={" + xlab + "},\n");
    }
    else {
      Msg::Error("Cannot infer orientation from Euler angles...");
      // this should not happen
      //error=true;
      return 2;
    }
    if (fabs(xmax - xmin) < 1e-8 ||
        fabs(ymax - ymin) < 1e-8) {
      Msg::Error("I inferred x (%f) or y (%f) dimension to be zero. Cannot produce.",
                 fabs(xmax - xmin), fabs(ymax - ymin));
      return 3;
    }

    double diagonal[3] = {xmax-xmin, ymax-ymin, 0};
    double minlen = norm3(diagonal);
    std::string suffix;
    if(minlen < 1e-5) {
      factor=1e6;
      suffix.assign(" / $\\mu$m");
    }
    else if(minlen < 0.01) {
      factor=1e3;
      suffix.assign(" / mm");
    }
    else if(minlen > 1e6) {
      factor=1e-6;
      suffix.assign(" / Mm");
    }
    else if(minlen > 1000) {
      factor=1e-3;
      suffix.assign(" / Km");
    }
    if (factor != 1) {
      char tmp[265];
      sprintf(tmp, "The pgf output has been rescaled in order to please "
              "the TeX number precision/range. Rescaling your results by "
              "a factor %g", factor);
      Msg::Warning(tmp);
      // sprintf(tmp, "$\\times 10^{%d}$},",(int)(log10(factor)+0.5));
      // std::string repl = tmp;
      // replace two labels
      std::size_t foundy = axisstr.rfind("},");
      if (foundy!=std::string::npos)
        axisstr.insert(foundy,suffix);
      else
        return 4;

      std::size_t foundx = axisstr.rfind("},", foundy);
      if (foundx!=std::string::npos)
        axisstr.insert(foundx,suffix);
      else
        return 4;
    }
    // axis options
    axisstr.append("\tenlargelimits=false, %% tight axis, use xmin=<val>, ");
    axisstr.append("xmax=<val> for custom bounding box\n");
    axisstr.append("\taxis on top,\n\tscale only axis,\n");
  }
  else {
    // no axis
    xmin=0, xmax=1, ymin=0, ymax=1;
    axisstr.append("\thide axis,\n");
  }
  char tmp[265];
  sprintf(tmp,"\t  \\addplot graphics[xmin=%f, xmax=%f, ymin=%f, ymax=%f]\n",
          xmin*factor, xmax*factor, ymin*factor, ymax*factor);

  plotstr.assign(tmp);

  return 0;
}

static int assemble3d(const int num, const int exportAxis, std::string &axisstr,
                      std::string &plotstr, double *eulerAngles, int *viewport,
                      double *proj, double *model, int ypix, int xpix)
{
  if (exportAxis) {
    axisstr.append("\tenlargelimits=false, %% tight axis, use xmin=<val>, ");
    axisstr.append("xmax=<val> for custom bounding box\n");
    axisstr.append("\tgrid=both,\n\tminor tick num=1,\n");
    std::string xlab, ylab, zlab;
    xlab = CTX::instance()->axesLabel[0];
    ylab = CTX::instance()->axesLabel[1];
    zlab = CTX::instance()->axesLabel[2];
    if (xlab.empty())
      xlab = "x";
    if (ylab.empty())
      ylab = "y";
    if (zlab.empty())
      zlab = "z";
    axisstr.append("\txlabel={" + xlab + "}, %%\n\tylabel={" +
            ylab + "},\n\tzlabel={" + zlab + "},\n");

    axisstr.append("\tzlabel style={rotate=-90},\n"); // bug?
  }
  else {
    if(opt_general_orthographic(0, GMSH_GET, 0) == 0 ) {
      Msg::Warning("Axes are not orthogonal, but because you do not want "
                   "any axes, I'll continue with a 2d picture.");
      return assemble2d(num, 0, axisstr, plotstr, eulerAngles);
    }
    if (CTX::instance()->camera) {
      Msg::Warning("Camera output not supported, but since you do not want "
                   "any axes, I'll continue with a 2d picture.");
      return assemble2d(num, 0, axisstr, plotstr, eulerAngles);
    }
    axisstr.append("\thide axis,\n");
  }

  if(opt_general_orthographic(0, GMSH_GET, 0) == 0 && exportAxis) {
    Msg::Warning("Cannot produce output if axes are not orthogonal.");
    Msg::Error("Please switch to orthographic projection mode "
               "('Alt + o') and retry if you want to output axes.");
    return 1;
  }

  double axPts[8][3];
  getMinMaxOfAxis(num, axPts);
  double factor=1.;
  // requires the pixel coordinates of the axis ends (actually any four
  // points with all different x/y/z <pixX, pixY> would suffice)
  double axViewPt[8][3];
  std::vector<int> acceptableAnchors;
  std::vector<int> masked;

  bool  reorder = false;
  double minlen = 0.;
  std::string suffix;
  for (unsigned int j=0; j < 8; j++) {
    // project the 8 axis end points to pixel coordinates,
    // accept if they are in the screen range.

    gluProject(axPts[j][0], axPts[j][1], axPts[j][2], model, proj,
            viewport, &axViewPt[j][0], &axViewPt[j][1], &axViewPt[j][2]);
    // printf("x=%f, y=%f, z=%f\n", axPts[j][0], axPts[j][1], axPts[j][2]);
    // printf("xprn=%f, yprn=%f, zprn=%f\n",
    //        axViewPt[j][0], axViewPt[j][1], axViewPt[j][2]);
    if ((int)(axViewPt[j][0]+0.5) <= xpix &&
            (int)(axViewPt[j][1]+0.5) <= ypix)  {
      acceptableAnchors.push_back(j);
    }
    else
      masked.push_back(j);
    if (j>0) {
      // respecting TeXs range limts (1e-4 relative precision)
      minlen = norm3(axPts[j]);
      //fprintf(stderr,"j=%d, vec length %f:\n", j, minlen);
      if(minlen < 1e-5) {
        factor=1e6;
        suffix.assign("/$\\mu$m");
      }
      else if(minlen < 0.01) {
        factor=1e3;
        suffix.assign("/mm");
      }
      else if(minlen > 1e6) {
        factor=1e-6;
        suffix.assign("/Mm");
      }
      else if(minlen > 1000) {
        factor=1e-3;
        suffix.assign("/Km");
      }
    }
    if (j == 1 && acceptableAnchors.size() == 2) {
      // precaution: if the first two coordinates are accepted, a
      // division by zero can occur in pgfplots
      // furthermore, four points with equal x=xmin leads to
      // singular system in pgfplots
      reorder=true;
      acceptableAnchors.pop_back();
    }
  }
  if (reorder) acceptableAnchors.push_back(1);

  if (acceptableAnchors.size() < 4) {
    Msg::Error("Unable to calculate anchors for pgf output. "
               "Make sure the entire scene is visible or adjust "
               "the axes min/max values to fit inside your screen.");
    return 2;
  }
  if (factor != 1) {
    char tmp[265];
    sprintf(tmp, "The pgf output has been rescaled in order to please "
            "the TeX number precision/range. Rescaling your results by "
            "a factor %g", factor);
    Msg::Warning(tmp);
    // replace three labels
    if (exportAxis) {
      // xlabel={x<>}, %%
      // ylabel={y<>},
      // zlabel={z<>},
      // zlabel style={rotate=-90},
      std::size_t foundrot = axisstr.rfind("},");
      std::size_t foundz = axisstr.rfind("},", foundrot-1);
      if (foundz!=std::string::npos)
        axisstr.insert(foundz,suffix);
      else
        return 4;
      std::size_t foundy = axisstr.rfind("},",foundz);
      if (foundy!=std::string::npos)
        axisstr.insert(foundy,suffix);
      else
        return 4;

      std::size_t foundx = axisstr.rfind("},", foundy);
      if (foundx!=std::string::npos)
        axisstr.insert(foundx,suffix);
      else
        return 4;
    }
  }

  char tmp[265];
  plotstr.assign("\t  \\addplot3[surf] graphics[debug=false,%%=visual,\n");
  plotstr.append("\t    points={%%\n");

  unsigned int j=0;
  for (std::vector<int>::iterator it = acceptableAnchors.begin();
       it != acceptableAnchors.end(); ++it, j++) {
    sprintf(tmp,"\t    (%f,%f,%f)", factor*axPts[*it][0],
            factor*axPts[*it][1], factor*axPts[*it][2]);
    plotstr.append(tmp);
    if (j > 3) {
      plotstr.append("%%");
    }
    // ypix-y syntax for easier debugging w/ e.g. gimp pixel
    sprintf(tmp," => (%d, %d-%d)\n", (int)(axViewPt[*it][0]+0.5),
            ypix,ypix-(int)(axViewPt[*it][1]+0.5));
    plotstr.append(tmp);
  }
  for (std::vector<int>::iterator it = masked.begin();
       it != masked.end(); ++it) {
    sprintf(tmp,"\t    (%f,%f,%f)", factor*axPts[*it][0],
            factor*axPts[*it][1], factor*axPts[*it][2]);
    plotstr.append(tmp);
    plotstr.append(" %% out of pixel range, discarded\n");
  }
  plotstr.append("\t    }]\n");
  return 0;
}

int print_pgf(const std::string &name, const int num, const int cnt, 
              PixelBuffer *buffer, double *eulerAngles,
              int *viewport, double *proj, double *model)
{
  int ypix = buffer->getHeight();
  int xpix = buffer->getWidth();

  std::string base = SplitFileName(name)[1];
  std::string path = SplitFileName(name)[0];
  std::string pngfilen = path + base + ".png";
  std::string pgffilen = path + base + ".pgf";
  std::string texfilen = path + base + ".tex";
  FILE *fp = Fopen(pngfilen.c_str(), "wb");
  if(!fp){
    Msg::Error("Unable to open file '%s'", pngfilen.c_str());
    return 1;
  }
  create_png(fp, buffer, 100);
  fclose(fp);

  // write pgf
  int twoDim = (int)opt_print_pgf_two_dim(0, GMSH_GET, 0);
  int exportAxis = (int)opt_print_pgf_export_axis(0, GMSH_GET, 0);
  if (cnt > 1)
    Msg::Warning("PGF colorbar output works only with a single visible "
                 "scale. Consider disabling all but one. I can only create a "
                 "single colorbar. Colorbar will be suppressed");

  int samples;
  std::string colmap_s, colbar_s, post_axis_s;
  // color map
  if (cnt == 1) { // one post processing view scale visible
    int intType = (int) opt_view_intervals_type(num, GMSH_GET, 0);
    if(assembleColmapStr(num, intType, samples, colmap_s) != 0) {
      Msg::Error("Unable to assemble colormap for PGF output");
      return 1;
    }
    if(assemblePostAxis(num, intType, post_axis_s) != 0) {
      Msg::Error("Unable to assemble post processing axis for PGF output");
      return 1;
    }
    if(assembleColbarStr(num, intType, samples, colbar_s) != 0) {
      Msg::Error("Unable to assemble colorbar for PGF output");
      return 1;
    }
  }
  else {
    colbar_s.assign("\t]\n"); // close axis without colorbar and dummy plot
  }

  std::string axis_s, plot_s;
  axis_s.assign("\\begin{tikzpicture}\n\\begin{axis}[\n\twidth=.5\\linewidth,"
                "%% set figure width here\n");
  if (twoDim) {
    if (assemble2d(num, exportAxis, axis_s, plot_s, eulerAngles) != 0) {
      return 1;
    }
  }
  else { // 3d
    if (assemble3d(num, exportAxis, axis_s, plot_s, eulerAngles,
                   viewport, proj, model, ypix, xpix) != 0) {
      return 1;
    }
  }
  char tmp[265];
  sprintf(tmp,"\t    {%s.png};\n", base.c_str());
  plot_s.append(tmp);

  fp = Fopen(pgffilen.c_str(), "wb");
  if(!fp){
    Msg::Error("Unable to open file '%s'", pgffilen.c_str());
    return 1;
  }
  fprintf(fp, "%s", colmap_s.c_str());
  fprintf(fp, "%s", axis_s.c_str());
  fprintf(fp, "%s", post_axis_s.c_str());
  fprintf(fp, "%s", colbar_s.c_str());
  fprintf(fp, "%s", plot_s.c_str());
  fprintf(fp, "\\end{axis}\n\\end{tikzpicture}%%\n");
  fclose(fp);

  if (twoDim)  {
    // try to trim the png...
    char tmp[2048];
    if (system(NULL)) {
      std::string pngname = name;
      pngname.replace(pngname.end()-3, pngname.end(), "png");
      sprintf(tmp, "convert -trim %s %s", pngname.c_str(), pngname.c_str());
      Msg::Info("Running:");
      Msg::Info(tmp);
      int ret = system(tmp);
      Msg::Info("Conversion returned value %d", ret);
      if (ret == 0) // success
        Msg::Info("Automatic trim successful.");
      else {
        Msg::Warning("Cannot automatically trim output png.");
        sprintf(tmp, "One should now manually crop the margins, using e.g."
                "gimp or `convert -trim %s %s` to get rid of any remaining"
                "margins.", pngfilen.c_str(), pngfilen.c_str());
        Msg::Warning(tmp);
      }
    }
    else {
      Msg::Warning("Cannot automatically trim output png.");
      sprintf(tmp, "One should now manually crop the margins, using e.g."
              "gimp or `convert -trim %s %s` to get rid of any remaining"
              "margins.", pngfilen.c_str(), pngfilen.c_str());
      Msg::Warning(tmp);
    }
  }

  // try to add transparency, do not(!) crop otherwise the transformation
  // matrix is wrong!!!!
  if (!twoDim)  {
    char tmp[2048];
    if (system(NULL)) {
      std::string pngname = name;
      pngname.replace(pngname.end()-3, pngname.end(), "png");
      sprintf(tmp, "convert -transparent white %s %s",
              pngname.c_str(), pngname.c_str());
      Msg::Info("Running:");
      Msg::Info(tmp);
      int ret = system(tmp);
      Msg::Info("Conversion returned value %d", ret);
      if (ret == 0) // success
        Msg::Info("Automatic transparent white background successful.");
      else {
        Msg::Warning("Cannot automatically add transparency to png.");
        sprintf(tmp, "One should now manually add a transparent layer in "
                "order to not obstruct the axis. e.g. using gimp or "
                "convert -transparent white %s %s`.",
                pngfilen.c_str(), pngfilen.c_str());
        Msg::Warning(tmp);
      }
    }
    else { // exit (EXIT_FAILURE);
      Msg::Warning("Cannot automatically add transparency to output png.");
      sprintf(tmp, "One should now manually add a transparent layer in "
              "order to not obstruct the axis. e.g. using gimp or "
              "`convert -transparent white %s %s`.",
              pngfilen.c_str(), pngfilen.c_str());
      Msg::Warning(tmp);
    }
  }

  // try to write a helper tex file just in case it does not exist
  fp = Fopen(texfilen.c_str(), "r");
  if(fp){
    fclose(fp);
    Msg::Info("File '%s' exists, please add '\\input{%s}' by yourself.",
              texfilen.c_str(), pgffilen.c_str());
  }
  else {
    fp = Fopen(texfilen.c_str(), "w");
    fprintf(fp,"\\documentclass{article}\n\\usepackage{pgfplots}\n"
            "\\pgfplotsset{compat=1.8}\n\\begin{document}\n");
    fprintf(fp,"\n\\input{%s}\n", pgffilen.c_str());
    fprintf(fp,"\n\\end{document}\n");
    fclose(fp);
  }
  return 0;
}
