#define _POSIX_C_SOURCE 200112L

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "gr3.h"
#include "gr3_internals.h"
#ifndef fabsf
#define fabsf(x) ((x) < 0 ? -(x) : (x))
#endif

int gr3_getpovray_(char *pixels, int width, int height, int use_alpha, int ssaa_factor)
{
  int i;
#ifdef GR3_USE_WIN
  char *povfile = malloc(40);
  char *pngfile = malloc(40);
  snprintf(povfile, 40, "./gr3.%lu.pov", (long unsigned)GetCurrentProcessId());
  snprintf(pngfile, 40, "./gr3.%lu.png", (long unsigned)GetCurrentProcessId());
#else
  char *povfile = malloc(40);
  char *pngfile = malloc(40);
  snprintf(povfile, 40, "/tmp/gr3.%d.pov", getpid());
  snprintf(pngfile, 40, "/tmp/gr3.%d.png", getpid());
#endif
  gr3_export_pov_(povfile, width, height);
  {
    int res;
    char *povray_call = malloc(strlen(povfile) + strlen(povfile) + 80);
#ifdef GR3_USE_WIN
    snprintf(povray_call, strlen(povfile) + strlen(povfile) + 80,
             "megapov +I%s +O%s +W%d +H%d -D +UA +FN +A +R%d 2>NUL", povfile, pngfile, width, height, ssaa_factor);
#else
    snprintf(povray_call, strlen(povfile) + strlen(povfile) + 80,
             "povray +I%s +O%s +W%d +H%d -D +UA +FN +A +R%d 2>/dev/null", povfile, pngfile, width, height, ssaa_factor);
#endif
    res = system(povray_call);
    free(povray_call);
    if (use_alpha)
      {
        res = gr3_readpngtomemory_((int *)pixels, pngfile, width, height);
        if (res)
          {
            RETURN_ERROR(GR3_ERROR_EXPORT);
          }
      }
    else
      {
        char *raw_pixels = malloc(width * height * 4);
        if (!raw_pixels)
          {
            RETURN_ERROR(GR3_ERROR_OUT_OF_MEM);
          }
        res = gr3_readpngtomemory_((int *)raw_pixels, pngfile, width, height);
        if (res)
          {
            free(raw_pixels);
            RETURN_ERROR(GR3_ERROR_EXPORT);
          }
        for (i = 0; i < width * height; i++)
          {
            pixels[3 * i + 0] = raw_pixels[4 * i + 0];
            pixels[3 * i + 1] = raw_pixels[4 * i + 1];
            pixels[3 * i + 2] = raw_pixels[4 * i + 2];
          }
        free(raw_pixels);
      }
  }
  remove(povfile);
  remove(pngfile);
  free(povfile);
  free(pngfile);
  return GR3_ERROR_NONE;
}

static void gr3_write_clipped_by(FILE *povfp)
{
  if (isfinite(context_struct_.clip_xmin) || isfinite(context_struct_.clip_xmax) ||
      isfinite(context_struct_.clip_ymin) || isfinite(context_struct_.clip_ymax) ||
      isfinite(context_struct_.clip_zmin) || isfinite(context_struct_.clip_zmax))
    {
      fprintf(povfp, "clipped_by { intersection {\n");
      if (isfinite(context_struct_.clip_xmin))
        {
          fprintf(povfp, "plane { x, %f inverse }\n", context_struct_.clip_xmin);
        }
      if (isfinite(context_struct_.clip_xmax))
        {
          fprintf(povfp, "plane { x, %f }\n", context_struct_.clip_xmax);
        }
      if (isfinite(context_struct_.clip_ymin))
        {
          fprintf(povfp, "plane { y, %f inverse }\n", context_struct_.clip_ymin);
        }
      if (isfinite(context_struct_.clip_ymax))
        {
          fprintf(povfp, "plane { y, %f }\n", context_struct_.clip_ymax);
        }
      if (isfinite(context_struct_.clip_zmin))
        {
          fprintf(povfp, "plane { z, %f inverse }\n", context_struct_.clip_zmin);
        }
      if (isfinite(context_struct_.clip_zmax))
        {
          fprintf(povfp, "plane { z, %f }\n", context_struct_.clip_zmax);
        }
      fprintf(povfp, "} }\n");
    }
}

static void print_pigment_and_finish(GR3_DrawList_t_ *draw, char *alpha_text, float alpha_val, int i, FILE *povfp,
                                     float red, float green, float blue)
{
  if (draw->alpha_mode == 0)
    {
      fprintf(povfp,
              "pigment { color rgb <%f, %f, %f> } finish { ambient %f diffuse %f "
              "specular %f roughness %f } \n",
              draw->colors[i * 3 + 0] * red, draw->colors[i * 3 + 1] * green, draw->colors[i * 3 + 2] * blue,
              context_struct_.light_parameters.ambient, context_struct_.light_parameters.diffuse,
              context_struct_.light_parameters.specular, 1 / (context_struct_.light_parameters.specular_exponent + 1));
    }
  else
    {
      fprintf(povfp,
              "pigment { color rgb <%f, %f, %f> %s %f} finish { ambient %f diffuse %f specular "
              "%f roughness %f} \n",
              draw->colors[i * 3 + 0] * red, draw->colors[i * 3 + 1] * green, draw->colors[i * 3 + 2] * blue,
              alpha_text, alpha_val, context_struct_.light_parameters.ambient * (1 - alpha_val),
              context_struct_.light_parameters.diffuse * (1 - alpha_val),
              context_struct_.light_parameters.specular * (1 - alpha_val),
              (1 - alpha_val) / (context_struct_.light_parameters.specular_exponent + 1));
    }
}


int gr3_export_pov_(const char *filename, int width, int height)
{
  int i, j, k, l;
  FILE *povfp;
  GR3_DrawList_t_ *draw;


  povfp = fopen(filename, "w");
  if (!povfp)
    {
      RETURN_ERROR(GR3_ERROR_CANNOT_OPEN_FILE);
    }

  fprintf(povfp, "camera {\n");
  if (context_struct_.projection_type == GR3_PROJECTION_ORTHOGRAPHIC)
    {
      fprintf(povfp, "  orthographic\n");
    }
  fprintf(povfp, "  location <%f, %f, %f>\n", context_struct_.camera_x, context_struct_.camera_y,
          context_struct_.camera_z);
  fprintf(povfp, "  look_at <%f, %f, %f>\n", context_struct_.center_x, context_struct_.center_y,
          context_struct_.center_z);
  fprintf(povfp, "  sky <%f, %f, %f>\n", context_struct_.up_x, context_struct_.up_y, context_struct_.up_z);
  if (context_struct_.projection_type != GR3_PROJECTION_ORTHOGRAPHIC)
    {
      fprintf(povfp, "  up <0,1,0>\n");
      fprintf(povfp, "  right <-%f,0,0>\n", 1.0 * width / height);
      fprintf(povfp, "  angle %f\n", context_struct_.vertical_field_of_view * 1.0 * width / height);
    }
  else
    {
      fprintf(povfp, "  up <0 %f 0>\n", fabsf(context_struct_.top) + fabsf(context_struct_.bottom));
      fprintf(povfp, "  right <-%f,0,0>\n", fabsf(context_struct_.right) + fabsf(context_struct_.left));
    }
  fprintf(povfp, "}\n");

  if (context_struct_.num_lights == 0)
    {
      GLfloat camera_pos[3];
      camera_pos[0] = context_struct_.camera_x;
      camera_pos[1] = context_struct_.camera_y;
      camera_pos[2] = context_struct_.camera_z;
      fprintf(povfp, "light_source { <%f, %f, %f> color rgb <1.0, 1.0, 1.0> parallel point_at <0,0,0>}\n",
              camera_pos[0], camera_pos[1], camera_pos[2]);
    }
  else
    {
      for (i = 0; i < context_struct_.num_lights; i++)
        {
          if (context_struct_.light_sources[i].x == 0 && context_struct_.light_sources[i].y == 0 &&
              context_struct_.light_sources[i].z == 0)
            {
              GLfloat camera_pos[3];
              camera_pos[0] = context_struct_.camera_x;
              camera_pos[1] = context_struct_.camera_y;
              camera_pos[2] = context_struct_.camera_z;
              fprintf(povfp, "light_source { <%f, %f, %f> color rgb <%f, %f, %f> parallel point_at <0,0,0>}\n",
                      camera_pos[0], camera_pos[1], camera_pos[2], context_struct_.light_sources[i].r,
                      context_struct_.light_sources[i].g, context_struct_.light_sources[i].b);
            }
          else
            {
              fprintf(povfp, "light_source { <%f, %f, %f> color rgb <%f, %f, %f> parallel point_at <0,0,0>}\n",
                      context_struct_.light_sources[i].x, context_struct_.light_sources[i].y,
                      context_struct_.light_sources[i].z, context_struct_.light_sources[i].r,
                      context_struct_.light_sources[i].g, context_struct_.light_sources[i].b);
            }
        }
    }
  fprintf(povfp, "background { color rgb <%f, %f, %f> }\n", context_struct_.background_color[0],
          context_struct_.background_color[1], context_struct_.background_color[2]);
  draw = context_struct_.draw_list_;
  while (draw)
    {
      float alpha_val;
      char *alpha_text;
      if (draw->alpha_mode == 0)
        {
          alpha_text = "";
        }
      else if (draw->alpha_mode == 1)
        {
          alpha_text = "transmit";
          alpha_val = 1 - draw->alphas[0];
        }
      else
        {
          alpha_text = "filter";
          alpha_val = 1 - (draw->alphas[0] + draw->alphas[1] + draw->alphas[2]) / 3;
        }
      gr3_sortindexedmeshdata(draw->mesh);
      switch (context_struct_.mesh_list_[draw->mesh].data.type)
        {
        case kMTSphereMesh:
          for (i = 0; i < draw->n; i++)
            {
              fprintf(povfp, "sphere {\n");
              fprintf(povfp, "  <%f, %f, %f>, %f\n", draw->positions[i * 3 + 0], draw->positions[i * 3 + 1],
                      draw->positions[i * 3 + 2], draw->scales[i * 3 + 0]);
              fprintf(povfp, "  texture {\n");
              print_pigment_and_finish(draw, alpha_text, alpha_val, i, povfp, 1, 1, 1);
              fprintf(povfp, "  }\n");
              gr3_write_clipped_by(povfp);
              fprintf(povfp, " no_shadow \n");
              fprintf(povfp, "}\n");
            }
          break;
        case kMTCylinderMesh:
          for (i = 0; i < draw->n; i++)
            {
              float len_sq = draw->directions[i * 3 + 0] * draw->directions[i * 3 + 0] +
                             draw->directions[i * 3 + 1] * draw->directions[i * 3 + 1] +
                             draw->directions[i * 3 + 2] * draw->directions[i * 3 + 2];
              float len = sqrt(len_sq);
              fprintf(povfp, "cylinder {\n");
              fprintf(povfp, "  <%f, %f, %f>, <%f, %f, %f>, %f\n", draw->positions[i * 3 + 0],
                      draw->positions[i * 3 + 1], draw->positions[i * 3 + 2],
                      draw->positions[i * 3 + 0] + draw->directions[i * 3 + 0] / len * draw->scales[i * 3 + 2],
                      draw->positions[i * 3 + 1] + draw->directions[i * 3 + 1] / len * draw->scales[i * 3 + 2],
                      draw->positions[i * 3 + 2] + draw->directions[i * 3 + 2] / len * draw->scales[i * 3 + 2],
                      draw->scales[i * 3 + 0]);
              fprintf(povfp, "  texture {\n");
              print_pigment_and_finish(draw, alpha_text, alpha_val, i, povfp, 1, 1, 1);
              fprintf(povfp, "  }\n");
              gr3_write_clipped_by(povfp);
              fprintf(povfp, " no_shadow \n");
              fprintf(povfp, "}\n");
            }
          break;
        case kMTConeMesh:
          for (i = 0; i < draw->n; i++)
            {
              float len_sq = draw->directions[i * 3 + 0] * draw->directions[i * 3 + 0] +
                             draw->directions[i * 3 + 1] * draw->directions[i * 3 + 1] +
                             draw->directions[i * 3 + 2] * draw->directions[i * 3 + 2];
              float len = sqrt(len_sq);
              fprintf(povfp, "cone {\n");
              fprintf(povfp, "  <%f, %f, %f>, %f, <%f, %f, %f>, %f\n", draw->positions[i * 3 + 0],
                      draw->positions[i * 3 + 1], draw->positions[i * 3 + 2], draw->scales[i * 3 + 0],
                      draw->positions[i * 3 + 0] + draw->directions[i * 3 + 0] / len * draw->scales[i * 3 + 2],
                      draw->positions[i * 3 + 1] + draw->directions[i * 3 + 1] / len * draw->scales[i * 3 + 2],
                      draw->positions[i * 3 + 2] + draw->directions[i * 3 + 2] / len * draw->scales[i * 3 + 2], 0.0);
              fprintf(povfp, "  texture {\n");
              print_pigment_and_finish(draw, alpha_text, alpha_val, i, povfp, 1, 1, 1);
              fprintf(povfp, "  }\n");
              gr3_write_clipped_by(povfp);
              fprintf(povfp, " no_shadow \n");
              fprintf(povfp, "}\n");
            }
          break;
        case kMTIndexedMesh:
        case kMTNormalMesh:
          for (i = 0; i < draw->n; i++)
            {
              GLfloat model_matrix[4][4] = {{0}};
              const float *vertices = context_struct_.mesh_list_[draw->mesh].data.vertices;
              const float *normals = context_struct_.mesh_list_[draw->mesh].data.normals;
              const float *colors = context_struct_.mesh_list_[draw->mesh].data.colors;
              {
                int m;
                GLfloat forward[3], up[3], left[3];
                float tmp;
                /* Calculate an orthonormal base in IR^3, correcting the up vector
                 * in case it is not perpendicular to the forward vector. This base
                 * is used to create the model matrix as a base-transformation
                 * matrix.
                 */
                /* forward = normalize(&directions[i*3]); */
                tmp = 0;
                for (m = 0; m < 3; m++)
                  {
                    tmp += draw->directions[i * 3 + m] * draw->directions[i * 3 + m];
                  }
                tmp = sqrt(tmp);
                for (m = 0; m < 3; m++)
                  {
                    forward[m] = draw->directions[i * 3 + m] / tmp;
                  } /* up = normalize(&ups[i*3]); */
                tmp = 0;
                for (m = 0; m < 3; m++)
                  {
                    tmp += draw->ups[i * 3 + m] * draw->ups[i * 3 + m];
                  }
                tmp = sqrt(tmp);
                for (m = 0; m < 3; m++)
                  {
                    up[m] = draw->ups[i * 3 + m] / tmp;
                  }
                /* left = cross(forward,up); */
                for (m = 0; m < 3; m++)
                  {
                    left[m] = forward[(m + 1) % 3] * up[(m + 2) % 3] - up[(m + 1) % 3] * forward[(m + 2) % 3];
                  }
                /* up = cross(left,forward); */
                for (m = 0; m < 3; m++)
                  {
                    up[m] = left[(m + 1) % 3] * forward[(m + 2) % 3] - forward[(m + 1) % 3] * left[(m + 2) % 3];
                  }
                for (m = 0; m < 3; m++)
                  {
                    model_matrix[0][m] = -left[m];
                    model_matrix[1][m] = up[m];
                    model_matrix[2][m] = forward[m];
                    model_matrix[3][m] = draw->positions[i * 3 + m];
                  }
                model_matrix[3][3] = 1;
              }
              fprintf(povfp, "mesh {\n");
              for (j = 0; j < context_struct_.mesh_list_[draw->mesh].data.number_of_vertices / 3; j++)
                {
                  float red = (colors[j * 9 + 0] + colors[j * 9 + 3] + colors[j * 9 + 6]) / 3.0;
                  float green = (colors[j * 9 + 1] + colors[j * 9 + 4] + colors[j * 9 + 7]) / 3.0;
                  float blue = (colors[j * 9 + 2] + colors[j * 9 + 5] + colors[j * 9 + 8]) / 3.0;
                  fprintf(povfp, "#local tex = texture { ");
                  print_pigment_and_finish(draw, alpha_text, alpha_val, i, povfp, red, green, blue);
                  fprintf(povfp, "}\n");
                  fprintf(povfp, "  smooth_triangle {\n");
                  for (k = 0; k < 3; k++)
                    {
                      float vertex1[4];
                      float vertex2[4];
                      float normal1[3];
                      float normal2[3];
                      for (l = 0; l < 3; l++)
                        {
                          vertex1[l] = draw->scales[i * 3 + l] * vertices[j * 9 + k * 3 + l];
                        }
                      vertex1[3] = 1;
                      for (l = 0; l < 4; l++)
                        {
                          vertex2[l] = model_matrix[0][l] * vertex1[0] + model_matrix[1][l] * vertex1[1] +
                                       model_matrix[2][l] * vertex1[2] + model_matrix[3][l] * vertex1[3];
                        }
                      for (l = 0; l < 3; l++)
                        {
                          normal1[l] = normals[j * 9 + k * 3 + l];
                        }
                      vertex1[3] = 1;
                      for (l = 0; l < 3; l++)
                        {
                          normal2[l] = model_matrix[0][l] * normal1[0] + model_matrix[1][l] * normal1[1] +
                                       model_matrix[2][l] * normal1[2];
                        }
                      fprintf(povfp, "    <%f, %f, %f>,", vertex2[0], vertex2[1], vertex2[2]);
                      fprintf(povfp, " <%f, %f, %f>", normal2[0], normal2[1], normal2[2]);
                      if (k < 2)
                        {
                          fprintf(povfp, ",");
                        }
                      fprintf(povfp, "\n");
                    }
                  fprintf(povfp, "    texture { tex }\n");
                  fprintf(povfp, "  }\n");
                }
              gr3_write_clipped_by(povfp);
              fprintf(povfp, " no_shadow \n");
              fprintf(povfp, "}\n");
            }
          break;
        default:
          gr3_log_("Unknown mesh type");
          break;
        }
      draw = draw->next;
    }
  fclose(povfp);
  return 0;
}
