
#include "Table.h"
#include <fstream>
#include <sstream>
#include <iomanip>
#include <errno.h>
#include <error.h>
#include <ctype.h>
#include <stdlib.h>
#include "def.h"
#include "../Util/roman_numeral.h"
#include "../Util/pdf_stringize.h"

using std::string;
using Util::TeX_atom;
using Util::TeX_atom_nonterminal;
using Util::TeX_atom_terminal;

TOC::Item *TOC::next_in_seq( const Item &item, const bool descend ) {
  if      ( descend && item.first  ) { return item.first; }
  else if (            item.next   ) { return item.next ; }
  else if (            item.parent ) {
    return next_in_seq( *item.parent, false );
  }
  return 0;
}

int TOC::count_children_recursively( const Item &item ) {
  int n = 0;
  for ( Item *p = item.first; p; p = p->next )
    n += count_children_recursively(*p) + 1;
  return n;
}

int TOC::count_items( const Table &table ) {
  return count_children_recursively( *table.root() ) + 1;
}

std::string TOC::pdf_object_str(
  const PDF::PDF &pdf,
  const Page_no::PS_page_numbering &nog,
  const Item &item,
  const int offset,
  const Offset_mode offset_mode
) {

  using std::setw;

  class I_obj {
    private:
      const PDF::PDF &pdf;
      const Page_no::PS_page_numbering &nog;
      const int offset;
      const Offset_mode offset_mode;
    public:
      I_obj(
        const PDF::PDF &pdf0,
        const Page_no::PS_page_numbering &nog0,
        const int offset0,
        const Offset_mode offset_mode0
      ) : pdf(pdf0), nog(nog0), offset(offset0), offset_mode(offset_mode0) {}
      int operator()( const Item *const item ) {
        return item->i() + offset +
          ( offset_mode == OFFSET_RELATIVE ? n_obj(pdf) : 0 );
      }
  } i_obj( pdf, nog, offset, offset_mode );

  std::ostringstream oss;

  const int &w = TOC::width_i_obj;

  if ( item.i() ) {

    {
      const string sect = item.sect_no();
      const string sect_and_title =
        ( sect.length() ? sect + "  " : "" ) + item.title();
      oss
        << setw(w) << i_obj( &item ) << " 0 obj\n"
        << "<<\n"
        << "  /Title " << Util::pdf_stringize(sect_and_title) << "\n";
    }
    oss
      << "  /Parent "
       << setw(w) << i_obj( item.parent ) << " 0 R\n";
    if ( item.prev ) oss
      << "  /Prev   "
        << setw(w) << i_obj( item.prev   ) << " 0 R\n";
    if ( item.next ) oss
      << "  /Next   "
        << setw(w) << i_obj( item.next   ) << " 0 R\n";
    if ( item.first && item.last ) {
      oss
        << "  /First  "
          << setw(w) << i_obj( item.first  ) << " 0 R\n"
        << "  /Last   "
          << setw(w) << i_obj( item.last   ) << " 0 R\n";
      const int count = count_children_recursively( item );
      oss << "  /Count  " << setw(w)
        << ( item.level() < level_open_in_outline ? +count : -count )
        << "\n";
    }
    {
      const Page_no::Page_number i_page = item.i_page();
      const int i_page_whole = (
        i_page.part == Page_no::BODY
        ? nog.count_prefatory_page() : 0
      ) + i_page.i;
      PDF::Iref iref = PDF::iref_page( pdf, i_page_whole );
      oss
        << "  /Dest [ "
        << setw(w) << iref.i << ' ' << iref.gen
        << " R /XYZ null null null ]\n";
    }
    oss
      << ">>\n"
      << "endobj\n";

  }

  else {

    oss
      << setw(w) << i_obj( &item ) << " 0 obj\n"
      << "<<\n"
      << "  /Type /Outlines\n";
    if ( item.first && item.last ) oss
      << "  /First  "
        << setw(w) << i_obj( item.first  ) << " 0 R\n"
      << "  /Last   "
        << setw(w) << i_obj( item.last   ) << " 0 R\n"
      << "  /Count  "
        << setw(w) << count_children_recursively( item ) << "\n";
    oss
      << ">>\n"
      << "endobj\n";

  }

  return oss.str();

}

void TOC::Item::clear_obj() {
  level1   = ROOT;
  sect_no1 = "";
  title1   = "";
  i_page1  = Page_number();
  parent = prev = next = first = last = 0;
}

// As an example, here is a typical line from a LaTeX TOC file:
// \contentsline {subsection}{\numberline {1.2.1}Axiom and definition}{2}

void TOC::Item::init( const TeX_atom_nonterminal &atom ) {

  clear_obj();

  TeX_atom_nonterminal      ::const_iterator p     = atom.begin();
  const TeX_atom_nonterminal::const_iterator p_end = atom.end  ();

  // Get the \contentsline command name.
  if (
    !(
      p != p_end          &&
      (*p)->is_terminal() &&
      (*p)->term() == "\\contentsline "
    )
  ) throw Exc();

  // Read the section's TOC level.
  if ( !( ++p != p_end && !(*p)->is_terminal() ) ) throw Exc();
  {
    const string l = (*p)->term();
    if      ( l == "{part}"          ) level1 = PART         ;
    else if ( l == "{chapter}"       ) level1 = CHAPTER      ;
    else if ( l == "{section}"       ) level1 = SECTION      ;
    else if ( l == "{subsection}"    ) level1 = SUBSECTION   ;
    else if ( l == "{subsubsection}" ) level1 = SUBSUBSECTION;
    else if ( l == "{paragraph}"     ) level1 = PARAGRAPH    ;
    else if ( l == "{subparagraph}"  ) level1 = SUBPARAGRAPH ;
    else throw Exc();
    // The next line is not necessarily permanent.  It tells the code
    // to ignore the book's parts, regarding only chapters, sections
    // and so forth.
    if ( level1 == PART ) throw Exc();
  }

  // Read the section's number and title.
  if ( !( ++p != p_end ) ) throw Exc();
  {
    // The collect() functor collects and stringizes
    // all remaining available tokens.
    struct {
      string operator()(
        TeX_atom_nonterminal      ::const_iterator *const qp,
        const TeX_atom_nonterminal::const_iterator        q_end
      ) {
        string res;
        for ( ; *qp != q_end; ++*qp ) {
          if ( suppress_emph && (**qp)->term() == "\\emph " ) {
            TeX_atom_nonterminal::const_iterator r = *qp + 1;
            if ( r != q_end ) {
              ++*qp;
              const string t = (*r)->term();
              res += (*r)->is_terminal()
                ? t : t.substr( 1, t.length()-2 );
            }
          }
          else res += (**qp)->term();
        }
        return res;
      }
    } collect;
    TeX_atom_nonterminal *d = dynamic_cast<TeX_atom_nonterminal*>(*p);
    if (d) {
      TeX_atom_nonterminal      ::const_iterator q     = d->begin();
      const TeX_atom_nonterminal::const_iterator q_end = d->end  ();
      if (
        q != q_end          &&
        (*q)->is_terminal() &&
        (*q)->term() == "\\numberline "
      ) {
        if ( !( ++q != q_end ) ) throw Exc();
        {
          const string t = (*q)->term();
          sect_no1 = (*q)->is_terminal()
            ? t : t.substr( 1, t.length()-2 );
        }
        title1 += collect( &++q, q_end );
      }
      // This "else" is for the Preface's TOC listing,
      // which has no section number.
      title1 += collect( &q, q_end );
    }
    else title1 = (*p)->term(); // unlikely
  }

  // Read the page number.
  if ( !( ++p != p_end && !(*p)->is_terminal() ) ) throw Exc();
  {
    const string t = (*p)->term();
    const string s = t.substr( 1, t.length()-2 );
    if ( s.end() != s.begin() && isdigit(s[0]) ) {
      i_page1.part = BODY;
      i_page1.i    = atoi( s.c_str() );
    }
    else {
      i_page1.part = PREFACE;
      i_page1.i    = Util::roman_to_int( s.c_str() );
    }
  }

  // Verify that nothing follows except maybe a TeX comment.
  if (
    !(
      ++p == p_end || (
        (*p)->is_terminal() && (*p)->term() == " " && (
          ++p == p_end || (
            (*p)->is_terminal() && (*p)->term() == "%"
          )
        )
      )
    )
  ) throw Exc();

}

int TOC::Item::delete_children() {
  int n = 0;
  {
    Item *pnext = 0;
    for ( Item *p = first; p; p = pnext ) {
      n += p->delete_children();
      pnext = p->next;
      delete p;
      ++n;
    }
  }
  first = last = 0;
  return n;
}

TOC::Table::Table( const string &filename_toc ) {

  root1 = new Item();

  std::ifstream file_toc( filename_toc.c_str() );
  if ( !file_toc ) error(
    EIO, 0,
    "cannot read the TOC file"
  );

  // Build the tree.  (Node-tree code is always dense,
  // but commenting it really does not help much.  The clearest
  // explanation of such code is probably the code itself.)
  {
    Item *target = root1;
    int i = 1;
    for (;;) {
      string line;
      std::getline( file_toc, line );
      if ( file_toc.eof() ) break;
      Item *item;
      try { item = new Item( line, i ); }
      catch ( Item::Exc ) { continue; }
      ++i;
      while ( target->level() >= item->level() ) {
        target = target->parent;
        if ( !target ) error(
          EINVAL, 0,
          "internal malfunction "
          "(generated a loose TOC::Item)"
        );
      }
      item->parent = target;
      if ( target->first ) {
        item        ->prev = target->last;
        target->last->next = item;
        target->last       = item;
      }
      else target->first = target->last = item;
      target = item;
    }
  }

}

TOC::Table::Table() {
  root1 = new Item();
}

TOC::Table::~Table() {
  root1->delete_children();
  delete root1;
}

