#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <libgen.h>

#include <Eina.h>

#include <poppler-global.h>
#include <poppler-rectangle.h>
#include <poppler-document.h>
#include <poppler-page.h>
#include <poppler-page-renderer.h>

#include "shmfile.h"
#include "timeout.h"

#ifndef _WIN32
# include <unistd.h>
# include <signal.h>
#endif

#define DATA32 unsigned int
typedef char RGB24[3];

//#define PDF_DBG

#ifdef PDF_DBG
#define D(fmt, args...) fprintf(stderr, fmt, ## args)
#else
#define D(fmt, args...)
#endif

using namespace poppler;

bool locked = false;

document *doc;
page *doc_page;
int width = 0, height = 0;
int crop_width = 0, crop_height = 0;
void *data = NULL;
double dpi = -1.0;

#define DEF_DPI 72.0

Eina_Bool poppler_init(const char *file, int page_nbr, int size_w, int size_h)
{
   double w, h, cw, ch;

   if (!file || !*file)
     return EINA_FALSE;

   if (page_nbr < 0)
     return EINA_FALSE;

   if (!eina_init())
     return EINA_FALSE;


   doc = document::load_from_file(file);

   if (!doc)
     return EINA_FALSE;

   if (page_nbr >= doc->pages())
     goto del_pdfdoc;

   /* load the page */

   doc_page = doc->create_page(page_nbr);
   if (!doc_page)
     goto del_pdfdoc;

   w = doc_page->page_rect(media_box).width();
   h = doc_page->page_rect(media_box).height();
   cw = doc_page->page_rect().width();
   ch = doc_page->page_rect().height();

   if (cw > w) cw = w;
   if (ch > h) ch = h;

   if ((size_w > 0) || (size_h > 0))
     {
        double w2 = cw, h2 = ch;

        w2 = size_w;
        h2 = (size_w * ch) / cw;
        if (h2 > size_h)
          {
             h2 = size_h;
             w2 = (size_h * cw) / ch;
          }
        D("XXXXXXXXXXXXXXXXXXXXx %3.3fx%3.3f\n", w2, h2);
        if (w2 > h2) dpi = (w2 * DEF_DPI) / cw;
        else dpi = (h2 * DEF_DPI) / ch;
     }

   if (dpi > 0.0)
     {
        cw = (cw * dpi) / DEF_DPI;
        ch = (ch * dpi) / DEF_DPI;
        w = (w * dpi) / DEF_DPI;
        h = (h * dpi) / DEF_DPI;
     }
   width = w;
   height = h;
   crop_width = cw;
   crop_height = ch;

   return EINA_TRUE;

 del_pdfdoc:
   delete doc;

   return EINA_FALSE;
}

void poppler_shutdown()
{
   delete doc;
   eina_shutdown();
}


void poppler_load_image(int size_w EINA_UNUSED, int size_h EINA_UNUSED)
{
   page_renderer *renderer;
   image out;
   DATA32 *dst;
   int y, x;

   if (dpi <= 0.0) dpi = DEF_DPI;

   renderer = new page_renderer();

   renderer->set_render_hint(page_renderer::text_antialiasing, 1);
   renderer->set_render_hint(page_renderer::antialiasing, 1);

   out = renderer->render_page(doc_page, dpi, dpi,
                               0, 0, width, height,
                               rotate_0);

   shm_alloc(crop_width * crop_height * sizeof(DATA32));
   if (!shm_addr) goto end;
   data = shm_addr;
   dst = (DATA32 *)data;

#define IMAGE_PIXEL_ITERATOR \
   for (y = 0; y < crop_height; y++) \
     for (x = 0; x < crop_width; x++)

#define ARGB_JOIN(a,r,g,b) \
        (((a) << 24) + ((r) << 16) + ((g) << 8) + (b))

   if (out.format() == image::format_mono)
     {
        // Looks like this is 1 bit per pixel, padded to a single byte.
        // The problem is testing. I have no sample (and no doc).

        uint8_t *src;
        for (y = 0; y < crop_height; y++)
          {
             src = (uint8_t *) out.data() + y * crop_width;
             for (x = 0; x < crop_width; x++)
               {
                  int bit = x & 0x7;
                  int c = (*src & (1 << bit)) ? 0xFF : 0x00;
                  *dst++ = ARGB_JOIN(0xFF, c, c, c);
                  if ((x & 0x7) == 0x7) src++;
               }
          }
     }
   if (out.format() == image::format_rgb24)
     {
        RGB24 *src;
        src = (RGB24*) out.data();
        IMAGE_PIXEL_ITERATOR
          {
             int pos = x+y*crop_width;
             dst[pos] = ARGB_JOIN(0xFF, src[pos][0], src[pos][1], src[pos][2]);
          }
      }
    else if (out.format() == image::format_argb32)
      {
         DATA32 *src;

         src = (DATA32*) out.data();
         IMAGE_PIXEL_ITERATOR
           *dst++ = *src++;
      }

 end:
   delete renderer;
}

static Eina_Tmpstr *tmpdir = NULL;
static Eina_Tmpstr *generated = NULL;

static void
tmp_cleanup(void)
{
   if (tmpdir)
     {
        if (generated)
          {
             unlink(generated);
          }
        if (rmdir(tmpdir) < 0)
          {
             D("Failed to delete tmpdir %s\n", tmpdir);
          }
        eina_tmpstr_del(tmpdir);
        tmpdir = NULL;
        if (generated)
          {
             eina_tmpstr_del(generated);
             generated = NULL;
          }
     }
}

static void
_crash(int val)
{
   D("Crash\n");
   tmp_cleanup();
   _exit(-1 | val);
}

int
main(int argc, char **argv)
{
   char *extension;
   char *dir;
   char *file;
   int i;
   int size_w = 0, size_h = 0;
   int head_only = 0;
   int page_num = 0;
   int ret = 0;

   if (argc < 2) return -1;
   // file is ALWAYS first arg, other options come after
   file = argv[1];
   for (i = 2; i < argc; i++)
     {
        if      (!strcmp(argv[i], "-head"))
           // asked to only load header, not body/data
           head_only = 1;
        else if (!strcmp(argv[i], "-key"))
          {
             i++;
             page_num = atoi(argv[i]);
             i++;
          }
        else if (!strcmp(argv[i], "-opt-scale-down-by"))
          { // not used by pdf loader
             i++;
             // int scale_down = atoi(argv[i]);
          }
        else if (!strcmp(argv[i], "-opt-dpi"))
          {
             i++;
             dpi = ((double)atoi(argv[i])) / 1000.0; // dpi is an int multiplied by 1000 (so 72dpi is 72000)
             i++;
          }
        else if (!strcmp(argv[i], "-opt-size"))
          { // not used by pdf loader
             i++;
             size_w = atoi(argv[i]);
             i++;
             size_h = atoi(argv[i]);
          }
     }

   D("dpi....: %f\n", dpi);
   D("page...: %d\n", page_num);

   // This is a funny hack to call an external tool to generate a pdf that will then be processed by poppler
   extension = strrchr(file, '.');
   dir = dirname(argv[0]);
   if (extension && dir && (!(!strcasecmp(extension, ".pdf"))))
     {
#ifndef _WIN32
        signal(SIGSEGV, _crash);
        signal(SIGBUS, _crash);
        signal(SIGFPE, _crash);
        signal(SIGABRT, _crash);
        signal(SIGILL, _crash);
        signal(SIGSYS, _crash);
        signal(SIGINT, _crash);
        signal(SIGTERM, _crash);
        signal(SIGQUIT, _crash);
#endif
        timeout_func_set(tmp_cleanup);
        if (eina_file_mkdtemp("evas_generic_pdf_loaderXXXXXX", &tmpdir))
          {
             Eina_Strbuf *tmp;
             FILE *cmd;

             tmp = eina_strbuf_new();
             eina_strbuf_append_printf(tmp, "%s/evas_generic_pdf_loader.%s '%s' %s", dir, extension + 1, file, tmpdir);

             cmd = popen(eina_strbuf_string_get(tmp), "r");
             D("running preprocessing process '%s'...\n", eina_strbuf_string_get(tmp));
             eina_strbuf_reset(tmp);

             if (cmd)
               {
                  struct stat st;
                  const char *filename;
                  char buf[1024];

                  while (fgets(buf, sizeof (buf), cmd))
                    ;
                  pclose(cmd);

                  filename = basename(file);

                  generated = eina_tmpstr_add_length(filename, strlen(filename) - strlen(extension));

                  eina_strbuf_append_printf(tmp, "%s/%s.pdf", tmpdir, generated);

                  eina_tmpstr_del(generated);
                  generated = NULL;

                  if (stat(eina_strbuf_string_get(tmp), &st) == 0)
                    {
                       generated = eina_tmpstr_add_length(eina_strbuf_string_get(tmp),
                                                          eina_strbuf_length_get(tmp));
                       file = (char*) generated;
                    }
               }

             D("generated file: '%s'\n", generated);
             eina_strbuf_free(tmp);
          }
     }

   // Let's force a timeout if things go wrong
   timeout_init(30);

   // Now process the pdf (or the generated pdf)
   D("poppler_file_init\n");
   if (!poppler_init(file, page_num, size_w, size_h))
     {
        ret = 1;
        goto cleanup;
     }
   D("poppler_file_init done\n");

   D("dpi2...: %f\n", dpi);
   if (!head_only)
     {
        poppler_load_image(size_w, size_h);
     }

   D("size...: %ix%i\n", width, height);
   D("crop...: %ix%i\n", crop_width, crop_height);
   D("alpha..: 1\n");

   printf("size %i %i\n", crop_width, crop_height);
   printf("alpha 0\n");

   if (!head_only)
     {
#ifdef _WIN32
        if (shm_fd)
#else
        if (shm_fd >= 0)
#endif
          printf("shmfile %s\n", shmfile);
        else
          {
             // could also to "tmpfile %s\n" like shmfile but just
             // a mmaped tmp file on the system
             printf("data\n");
             if (fwrite(data, crop_width * crop_height * sizeof(DATA32), 1, stdout) != 1)
               {
                  // nothing much to do, the receiver will simply ignore the
                  // data as it's too short
                  D("fwrite failed (%d bytes): %m\n", crop_width * crop_height * sizeof(DATA32));
               }
          }
        shm_free();
     }
   else
     printf("done\n");

   poppler_shutdown();
cleanup:
   timeout_func_set(NULL);
   tmp_cleanup();
   fflush(stdout);
   return ret;
}
