#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <error.h>
#include <errno.h>

#ifndef FALSE
#define FALSE (0!=0)
#define TRUE (0==0)
#endif

static int DEBUG = FALSE;
static int QUIET = FALSE;

/*
  Table from GraphicsMagick (gm):
     11x17         792  1224
     Ledger       1224   792
     Legal         612  1008
     Letter        612   792
     LetterSmall   612   792
     ArchE        2592  3456
     ArchD        1728  2592
     ArchC        1296  1728
     ArchB         864  1296
     ArchA         648   864
     A0           2380  3368
     A1           1684  2380
     A2           1190  1684
     A3            842  1190
     A4            595   842
     A4Small       595   842
     A5            421   595
     A6            297   421
     A7            210   297
     A8            148   210
     A9            105   148
     A10            74   105
     B0           2836  4008
     B1           2004  2836
     B2           1418  2004
     B3           1002  1418
     B4            709  1002
     B5            501   709
     C0           2600  3677
     C1           1837  2600
     C2           1298  1837
     C3            918  1298
     C4            649   918
     C5            459   649
     C6            323   459
     Flsa          612   936
     Flse          612   936
     HalfLetter    396   612
*/

/*
Table 1: Paper Size
     w bp h bp w in  h in  w mm h mm
A0b  2594 3370 36.03 46.81 915  1189
A0   2380 3368 33.06 46.78 840  1188
A1   1684 2380 23.39 33.06 594   840
A2   1190 1684 16.53 23.39 420   594
A3   842  1190 11.69 16.53 297   420
A4   595  842   8.26 11.69 210   297
A5   421  595   5.85  8.26 149   210
A6   298  421   4.14  5.85 105   149
A7   211  298   2.93  4.14  74   105
A8   149  211   2.07  2.93  53    74
A9   106  149   1.47  2.07  37    53
A10   75  106   1.04  1.47  26    37
 */

#define A0b          0   //  "{36.03in,46.81in}"
#define A0           1   //  "{33.06in,46.78in}"
#define A1           2   //  "{23.39in,33.06in}"
#define A2           3   //  "{16.53in,23.39in}"
#define A3           4   //  "{11.69in,16.53in}"
#define TABLOID      5   //  "{11in,17in}"
#define LEGAL        6   //  "{11in,14in}"
#define LETTER       7   //  "{8.5in,11in}"
#define A4           8   //  "{8.26in,11.6in}"
#define A5           9   //  "{5.85in,8.26in}"
#define HALF_LETTER 10   //  "{5.5,4.25in}"
#define A6          11   //  "{4.14in,5.85in}"
#define A7          12   //  "{2.93in,4.14in}"
#define A8          13   //  "{2.07in,2.93in}"
#define A9          14   //  "{1.47in,2.07in}"
#define A10         15   //  "{1.04in,1.47in}"
#define SMALLEST A10

int two_up_size[] = {
  -1,                // "A0b",
  -1,                // "A0",
  A0,                // "A1",
  A1,                // "A2",
  A2,                // "A3",
  A2,                // "TABLOID",
  A2,                // "LEGAL",
  TABLOID,           // "LETTER",
  A3,                // "A4",
  A4,                // "A5",
  LETTER,            // "HALF_LETTER",
  A5,                // "A6",
  A6,                // "A7",
  A7,                // "A8",
  A8,                // "A9",
  A9,                // "A10",
};

char *papername[] = {
  "a0b",
  "a0",
  "a1",
  "a2",
  "a3",
  "tabloid",
  "legal",
  "letter",
  "a4",
  "a5",
  "halfletter",
  "a6",
  "a7",
  "a8",
  "a9",
  "a10",
};
const char *formats = " [--(a0b|a(0-10)|tabloid|11x17|ledger|legal|letter|halfletter)]";

char *papersize[] = {
  "{36.03in,46.81in}", // a0b
  "{33.06in,46.78in}", // a0
  "{23.39in,33.06in}", // a1
  "{16.53in,23.39in}", // a2
  "{11.69in,16.53in}", // a3
  "{11in,17in}",       // tabloid
  "{11in,14in}",       // legal
  "{8.5in,11in}",      // letter
  "{8.26in,11.6in}",   // a4
  "{5.85in,8.26in}",   // a5
  "{5.5in,4.25in}",      // half-letter
  "{4.14in,5.85in}",   // a6
  "{2.93in,4.14in}",   // a7
  "{2.07in,2.93in}",   // a8
  "{1.47in,2.07in}",   // a9
  "{1.04in,1.47in}",   // a10
  NULL
};

float papersizef[] = {
  36.03,46.81, // a0b
  33.06,46.78, // a0
  23.39,33.06, // a1
  16.53,23.39, // a2
  11.69,16.53, // a3
  11.0,17.0,   // tabloid
  11.0,14.0,   // legal
  8.5,11.0,    // letter
  8.26,11.6,   // a4
  5.85,8.26,   // a5
  5.5,4.25,    // half-letter
  4.14,5.85,   // a6
  2.93,4.14,   // a7
  2.07,2.93,   // a8
  1.47,2.07,   // a9
  1.04,1.47,   // a10
};

int main(int argc, char **argv) {
  int sheet = 1;
  int i, rc;
  static char pdfseparate[1024];
  static char pdfinfo[1024];
  static char twoup[1024];
  static char landscape[1024];
  static char portrait[1024];
  int skip_front_cover = FALSE, skip_back_cover = FALSE;
  int pages = 0, rotation, paper = -1;
  int base_page;
  FILE *info;
  float width, height;
  char *line = NULL;
  size_t len = 0;
  ssize_t nread;

  while (argc >= 2) {
    int p = SMALLEST;
    char pp[64];
    int argpaper = -1;
    while (p >= 0) {
      sprintf(pp, "--%s", papername[p]);
      if (strcasecmp(argv[1], pp)==0) {
        argpaper = p;
        break;
      }
      p -= 1;
    }
    if (argpaper >= 0) {
      argv += 1; argc -= 1; paper = argpaper; // allow for more than one explicit paper assignment. Keep last.
      if (DEBUG) fprintf(stderr, "Options: --%s\n", papername[argpaper]);
      continue;
    }
    // Need to add --landscape for input documents which are in landscape format.  Or determine format
    // by looking at the width/height of the input pages.  Currently such documents are not handled.
    if ((strcasecmp(argv[1], "--remove-cover")==0) || (strcasecmp(argv[1], "--remove-front-cover")==0)) {
      // Padding to X4 pages should be done after removing covers.
      argv += 1; argc -= 1; skip_front_cover = TRUE; continue;
      if (DEBUG) fprintf(stderr, "Options: --remove-front-cover\n");
    } else if (strcasecmp(argv[1], "--remove-back-cover")==0) {
      argv += 1; argc -= 1; skip_back_cover = TRUE; continue;
      // (not yet implemented)
      if (DEBUG) fprintf(stderr, "Options: --remove-back-cover\n");
    } else if (strcasecmp(argv[1], "--remove-covers")==0) {
      argv += 1; argc -= 1; skip_front_cover = TRUE; skip_back_cover = TRUE; continue;
      // (back cover not yet implemented)
      if (DEBUG) fprintf(stderr, "Options: --remove-front-cover --remove-back-cover\n");
    } else if (strcasecmp(argv[1], "--one-sided-cover")==0) {
      // Instead of removing the cover, it is printed as if it were a normal page,
      // but the inside faces of the cover (front and back) are left blank, i.e.
      // none of the book contents will be printed on the inside surface of the cover.
      // This option also allows you to print cover and book in one run, by placing a
      // sheet of card on top of the stack of paper in the printer paper drawer.
      // (not yet implemented)
      argv += 1; argc -= 1; continue;
      if (DEBUG) fprintf(stderr, "Options: --one-sided-cover\n");
    } else if (((strcasecmp(argv[1], "--debug")==0) || (strcasecmp(argv[1], "-d")==0))) {
      argv += 1; argc -= 1; DEBUG = TRUE; continue;
      if (DEBUG) fprintf(stderr, "Options: -d DEBUG=TRUE\n");
    } else if (((strcasecmp(argv[1], "--quiet")==0) || (strcasecmp(argv[1], "-q")==0))) {
      argv += 1; argc -= 1; DEBUG = FALSE; QUIET = TRUE; continue;
      if (DEBUG) fprintf(stderr, "Options: -q QUIET=TRUE\n");
    } else if (((strcasecmp(argv[1], "--help")==0) || (strcasecmp(argv[1], "-h")==0))) {
      argv += 1; argc -= 1;
      fprintf(stderr, "Syntax: layout [-h] [-q] [-d] [--remove-cover]%s file.pdf\n", formats);
      fprintf(stderr, "\nOptions:\n");
      fprintf(stderr, "   --remove-cover  PDF has a front page which is a book cover that\n");
      fprintf(stderr, "                   should be printed differently, eg on card or in colour\n");
      fprintf(stderr, "   --[paper size]  Output to fit paper of given size.  Allowed sizes are:\n");
      fprintf(stderr, "                   %s", papername[0]);
      for (i = 1; i < sizeof(papername)/sizeof(*papername); i++) {
        fprintf(stderr, ", %s", papername[i]);
      }
      fprintf(stderr, "\n   -q              Suppress unnecessary information\n");
      fprintf(stderr, "   -d              Print programmer debugging information\n\n");
      fprintf(stderr, "Examples:\n");
      fprintf(stderr, "   layout file.pdf          Convert file for printing as a booklet\n");
      fprintf(stderr, "   layout -q file.pdf       Don't print informative info on screen\n");
      fprintf(stderr, "   layout --remove-cover --11x17 file.pdf\n");
      fprintf(stderr, "                            Remove first page (book cover), and print\n");
      fprintf(stderr, "                            on Tabloid (a.k.a. Ledger) paper (11x17in)\n\n");
      fprintf(stderr, "This utility requires the following commands to be available:\n");
      fprintf(stderr, "   pdfjam\n");
      fprintf(stderr, "   pdfseparate\n");
      fprintf(stderr, "   pdfinfo\n");
      fprintf(stderr, "\n");
      exit(0);
      continue;
    } else if ((argc >= 2) && ((strcasecmp(argv[1], "--11x17")==0) || (strcasecmp(argv[1], "--ledger")==0))) {
      argv += 1; argc -= 1; paper = TABLOID; continue;
      if (DEBUG) fprintf(stderr, "Options: --tabloid\n");
    } else if (*argv[1] == '-') {
        fprintf(stderr, "syntax: layout [--help] [--remove-cover]%s file.pdf\n", formats);
      exit(1);
    }
    break; // remainder should be filename only
  }
  
  if (argc != 2) {
    fprintf(stderr, "syntax: layout [--help] [--remove-cover]%s file.pdf\n", formats);
    exit(1);
  }

  sprintf(pdfinfo, "pdfinfo \"%s\"", argv[1]);
  info = popen(pdfinfo, "r");
  if (info == NULL) {
    fprintf(stderr, "layout: %s\n", strerror(errno));
    exit(3);
  }

  while ((nread = getline(&line, &len, info)) != -1) {

    rc = sscanf(line, "Pages:          %d\n", &pages);
    if (rc == 1) {
      if (skip_front_cover) {
        if (!QUIET) fprintf(stderr, "Document contains a cover (not to be included) and %d pages\n", pages-1);
      } else {
        if (!QUIET) fprintf(stderr, "Document contains %d pages\n", pages);
      }
    }

    rc = sscanf(line, "Page size:      %f x %f pts\n", &width, &height);
    if (rc == 2) {
      if (!QUIET) fprintf(stderr, "Page size is %f by %f pts\n", width, height);
      if (paper < 0) {
        paper = SMALLEST;

        // pdfinfo TG.pdf|grep "^Page size:"
        // Page size:      610.56 x 773.52 pts
        #define ConvertToInches (36.03/2594)
        
        if (DEBUG) fprintf(stderr, "Assuming a PDF file at %d points per inch\n", (int)(1/ConvertToInches));
        while (paper >= 0) {
          if (DEBUG) {
            fprintf(stderr, "Comparing %f x %f pts (%f x %f in) to %f x %f in (%s)\n", width, height,
                  width*ConvertToInches, height*ConvertToInches,
                  papersizef[paper*2], papersizef[paper*2+1],
                  papername[paper]);
          }
          if ((width*ConvertToInches <= papersizef[paper*2]) && (height*ConvertToInches <= papersizef[paper*2+1])) {
            if (!QUIET) {
              fprintf(stderr, "Assuming input paper option is --%s", papername[paper]);
              if (paper > 0) fprintf(stderr, ". Can be overridden, e.g. with --%s", papername[paper-1]);
              fprintf(stderr, "\n");
              fprintf(stderr, "Output paper size will be %s %s\n", papername[two_up_size[paper]], papersize[two_up_size[paper]]);
            }
            break;
          }
          paper -= 1;
        }
      }
      if (paper < 0) {
        paper = TABLOID; // my default
        if (!QUIET) fprintf(stderr, "Unknown large paper size - using --%s\n", papername[paper]);
      }
    }

    rc = sscanf(line, "Page rot:       %d\n", &rotation);
    if (rc == 1) {
      if (!QUIET) {
        if (rotation != 0) fprintf(stderr, "WARNING: "); // don't know if this is handled at all... untested...
        fprintf(stderr, "Rotation is %d\n", rotation);
      }
    }
  }
  free(line);
  pclose(info);

  if (paper < 0) {
    fprintf(stderr, "layout: cannot determine input page size\?\n");
    exit(4);
  }
  
  if (two_up_size[paper] < 0) {
    fprintf(stderr, "layout: no output paper large enough\?\n");
    exit(4);
  }
  
  if (pages == 0) {
    fprintf(stderr, "layout: no pages to arrange\?\n");
    exit(4);
  }
  
  system("rm -f foliotmp[0-9]*.pdf");
  if (skip_front_cover) {
    sprintf(pdfseparate, "pdfseparate -f 2 \"%s\" foliotmp%%d.pdf", argv[1]); // pages numbered from 1 upwards.  so cover sheet is page 1
    pages -= 1; base_page = 2;
  } else {
    sprintf(pdfseparate, "pdfseparate \"%s\" foliotmp%%d.pdf", argv[1]);
    base_page = 1;
  }
  if (DEBUG) fprintf(stderr, "%s\n", pdfseparate);
  rc = system(pdfseparate);
  if (WIFSIGNALED(rc) && (WTERMSIG(rc) == SIGINT || WTERMSIG(rc) == SIGQUIT)) exit(rc);

  if (rc != 0) {
    if (errno != 0) fprintf(stderr, "layout: %s\n", strerror(errno));
    exit(2);
  }

  // convert each pdf page to an image,
  // use imagemagik (convert) to threshold it to remove grey background (feature removed for now)
  // repackage back into same-sized pages
  //   pdftoppm foliotmp69.pdf |pnmtopng -hist |convert png:- -sharpen 0x3 -white-threshold '90%' foliotmp69-new.pdf
  
  if (!QUIET) {
    printf("     two-sided printing\n");
    printf(" lower side     upper side\n");
  }
  system("rm -f twoup[0-9]*.pdf");
  for (i = 0; i < pages/2; i += 2) {
    if (!QUIET) {
      printf("+-----+-----+  +-----+-----+\n");
      printf("|     |     |  |     |     |\n");
      printf("| %03d | %03d |  | %03d | %03d |\n",  pages+1-(i+2)+1, i+0+1, i+1+1, pages+1-(i+3)+1);
      printf("|     |     |  |     |     |\n");
      printf("|     |     |  |     |     |\n");
      printf("+-----+-----+  +-----+-----+  sheet %d\n\n", sheet);
    }
    sprintf(twoup, "pdfjam --nup '2x1' --landscape --papersize '%s'"
            " --noautoscale 'true' --outfile twoup%04d-a.pdf -- foliotmp%0d.pdf - foliotmp%0d.pdf -%s",
            papersize[two_up_size[paper]], sheet, pages+1-(i+2)+(skip_front_cover ? 2 : 1), i+0+(skip_front_cover ? 2 : 1), DEBUG ? "" : " > /dev/null 2> /dev/null");
    if (DEBUG) fprintf(stderr, "%s\n", twoup);
    system(twoup);

    sprintf(twoup, "pdfjam --nup '2x1' --landscape --papersize '%s'"
            " --noautoscale 'true' --outfile twoup%04d-b.pdf -- foliotmp%0d.pdf - foliotmp%0d.pdf -%s",
            papersize[two_up_size[paper]], sheet, i+1+(skip_front_cover ? 2 : 1), pages+1-(i+3)+(skip_front_cover ? 2 : 1), DEBUG ? "" : " > /dev/null 2> /dev/null");
    if (DEBUG) fprintf(stderr, "%s\n", twoup);
    system(twoup);

    sheet += 1;
  }
  if (DEBUG) {
    system("pdfjam twoup*.pdf");
  } else {
    system("pdfjam twoup*.pdf > /dev/null 2> /dev/null");
  }
  sprintf(landscape, "mv twoup%04d-b-pdfjam.pdf landscape-tmp.pdf", sheet-1);
  if (DEBUG) fprintf(stderr, "%s\n", landscape);
  system(landscape);
  if (DEBUG) {
    fprintf(stderr, "%s\n", "cpdf -rotate 90 -i landscape-tmp.pdf -o portrait-tmp.pdf");
    system("cpdf -rotate 90 -i landscape-tmp.pdf -o portrait-tmp.pdf");
  } else {
    system("cpdf -rotate 90 -i landscape-tmp.pdf -o portrait-tmp.pdf > /dev/null 2> /dev/null");
  }
  // --landscape below?
  sprintf(twoup, "pdfjam --papersize '%s' --twoside --scale 2.2 --outfile \"print-%s\"  portrait-tmp.pdf%s",
          papersize[two_up_size[paper]], argv[1], DEBUG ? "" : " > /dev/null 2> /dev/null");
  system(twoup);
  if (!QUIET) fprintf(stderr, "Output is in print-%s\n", argv[1]);
  if (!DEBUG) system("rm -f foliotmp[0-9]*.pdf twoup[0-9]*.pdf landscape-tmp.pdf portrait-tmp.pdf");
  if (pages&3) {
    fprintf(stderr, "Warning: not a multiple of 4 pages.  Need to tweak the code to add padding...\n");
    // Which we shall probably do by adding the requisite number of blank pages.
    exit(5);
  }
  
  exit(0);
}
