
#include "update_catalog.h"
#include <set>
#include <string>
#include <sstream>
#include <iomanip>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include "../TOC/def.h"
#include "PDF.h"
#include "PDF_rep.h"

using std::string;
typedef std::set<string> set;
const int magic = 0x9f05; // deprecated

// Beware that Libpoppler's Dict::add() method does not copy the key-string you
// feed it.  In an earlier Libpoppler, didn't it used to?  Maybe not; I can't
// clearly remember.  In any case, letting the key-string go out of scope now
// invalidates the Dict.  ---THB, March 2010---

namespace {

  // This function copies key-value pairs from source to destination
  // Poppler Dicts, excepting pairs with the specified keys.
  // It expects the caller to have preinitialized the destination
  // as an empty Dict.
  int copy_but(
    Dict *const dest,
    Dict *const src,
    const set &keys
  ) {
    const int size = src->getLength();
    for ( int i = 0; i < size; ++i ) {
      char *const key = src->getKey(i);
      if ( keys.count(key) ) continue;
      Object obj;
      src ->getValNF( i  , &obj );
      dest->add     ( key, &obj );
    }
    return size;
  }

}

string PDF::update_catalog(
  PDF &pdf,
  const Page_no::PS_page_numbering &nog
) {

  PDF_rep *const rep = pdf.get_PDF_rep(magic);

  // To understand this code, refer to the Libpoppler headers and
  // to Adobe's PDF Reference 1.7, sect. 8.3.1.

  Object catalog_obj;
  catalog_obj.initDict(static_cast<XRef*>(0));
  Dict *catalog = catalog_obj.getDict();
  {
    set keys;
    { char s[] = "PageLabels"; keys.insert(s); }
    { char s[] = "Outlines"  ; keys.insert(s); }
    copy_but( catalog, rep->catalog, keys );
  }

  Object dict_Roman_obj;
  char s_Roman[] = "S";
  {
    dict_Roman_obj.initDict(static_cast<XRef*>(0));
    Dict *const dict_Roman = dict_Roman_obj.getDict();
    Object name_Roman;
    { char s[] = "r"; name_Roman.initName(s); }
    dict_Roman->add( s_Roman, &name_Roman );
  }

  Object dict_Arabic_obj;
  char s_Arabic[] = "S";
  {
    dict_Arabic_obj.initDict(static_cast<XRef*>(0));
    Dict *const dict_Arabic = dict_Arabic_obj.getDict();
    Object name_Arabic;
    { char s[] = "D"; name_Arabic.initName(s); }
    dict_Arabic->add( s_Arabic, &name_Arabic );
  }

  Object array_obj;
  {
    Object zero;
    zero.initInt( 0 );
    Object n_page;
    n_page.initInt( nog.count_prefatory_page() );
    array_obj.initArray(static_cast<XRef*>(0));
    Array *const array = array_obj.getArray();
    array->add( &zero            );
    array->add( &dict_Roman_obj  );
    array->add( &n_page          );
    array->add( &dict_Arabic_obj );
  }

  Object dict_obj;
  char s_Nums[] = "Nums";
  {
    dict_obj.initDict(static_cast<XRef*>(0));
    Dict *const dict = dict_obj.getDict();
    dict->add( s_Nums, &array_obj );
  }

  Object ref_obj;
  ref_obj.initRef( n_obj(pdf), 0 );

  char s_PageLabels[] = "PageLabels";
  char s_Outlines  [] = "Outlines"  ;
  catalog->add( s_PageLabels, &dict_obj );
  catalog->add( s_Outlines  , &ref_obj  );

  string res;
  {
    Iref iref = iref_catalog(pdf);
    std::ostringstream oss;
    oss
      << std::setw(TOC::width_i_obj) << iref.i
      << " " << iref.gen << " obj\n";
    res += oss.str();
  }

  // Do print() to a string rather than to stdout or a file.
  {
    int fd[2];
    pipe(fd);
    {
      FILE *q = fdopen( fd[1], "w" );
      catalog_obj.print(q);
      fclose(q);
    }
    {
      FILE *q = fdopen( fd[0], "r" );
      int c;
      while ( (c=fgetc(q)) != EOF ) res += c;
      fclose(q);
    }
  }

  res += "\nendobj\n";

  return res;

}

string PDF::add_title_to_info(
  PDF &pdf,
  const string &title
) {

  PDF_rep *const rep = pdf.get_PDF_rep(magic);

  Object info_obj;
  info_obj.initDict(static_cast<XRef*>(0));
  Dict *info = info_obj.getDict();
  {
    set keys;
    copy_but( info, rep->info, keys );
  }

  char s_Title[] = "Title";
  {
    Object obj_old;
    info->lookup( s_Title, &obj_old );
    if ( obj_old.isNull() ) {
      Object obj_new;
      {
        GooString &gs = *( new GooString( title.c_str() ) );
        obj_new.initString( &gs );
      }
      info->add( s_Title, &obj_new );
    }
  }

  string res;
  {
    const Iref iref = iref_info(pdf);
    std::ostringstream oss;
    oss
      << std::setw(TOC::width_i_obj) << iref.i
      << " " << iref.gen << " obj\n";
    res += oss.str();
  }

  // Do print() to a string rather than to stdout or a file.
  {
    int fd[2];
    pipe(fd);
    {
      FILE *q = fdopen( fd[1], "w" );
      info_obj.print(q);
      fclose(q);
    }
    {
      FILE *q = fdopen( fd[0], "r" );
      int c;
      while ( (c=fgetc(q)) != EOF ) res += c;
      fclose(q);
    }
  }

  res += "\nendobj\n";

  return res;

}

string PDF::update_trailer(
  PDF &pdf,
  const int n_pdf_obj,
  const int offset_xref,
  const std::string &pdf_filename
) {

  PDF_rep *const rep = pdf.get_PDF_rep(magic);

  // To understand this code, refer to the Libpoppler headers and
  // to Adobe's PDF Reference 1.7, sect. 3.4.4.

  Object new_trailer_obj;
  new_trailer_obj.initDict(static_cast<XRef*>(0));
  Dict *new_trailer = new_trailer_obj.getDict();
  {
    set keys;
    { char s[] = "Size"; keys.insert(s); }
    { char s[] = "Prev"; keys.insert(s); }
    { char s[] = "ID"  ; keys.insert(s); }
    copy_but( new_trailer, rep->trailer, keys );
  }

  char s_Size[] = "Size";
  {
    Object obj;
    obj.initInt( n_pdf_obj );
    new_trailer->add( s_Size, &obj );
  }

  char s_Prev[] = "Prev";
  {
    Object obj;
    obj.initInt( offset_last_xref_table(pdf_filename) );
    new_trailer->add( s_Prev, &obj );
  }

  string res = "trailer\n";

  // Do print() to a string rather than to stdout or a file.
  {
    int fd[2];
    pipe(fd);
    {
      FILE *q = fdopen( fd[1], "w" );
      new_trailer_obj.print(q);
      fclose(q);
    }
    {
      FILE *q = fdopen( fd[0], "r" );
      int c;
      while ( (c=fgetc(q)) != EOF ) res += c;
      fclose(q);
    }
  }

  res += "\nstartxref\n";
  {
    std::ostringstream oss;
    oss << offset_xref << '\n';
    res += oss.str();
  }
  res += "%%EOF\n";

  return res;

}

