
#include "PS_page_numbering.h"
#include <fstream>
#include <errno.h>
#include <error.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>

namespace {
  // The caller must manually free() the buffer this function returns.
  char *match_dup(
    const char *const line,
    const regmatch_t *const pmatch
  ) {
    return strndup(
      line + pmatch->rm_so,
      pmatch->rm_eo - pmatch->rm_so
    );
  }
}

Page_no::PS_page_numbering::PS_page_numbering(
  const std::string &filename_ps
) {

  using std::string;

  std::ifstream file_ps( filename_ps.c_str() );
  if ( !file_ps ) error(
    EIO, 0,
    "cannot read the PS file"
  );

  n_prefatory_page1 = 0;
  n_corporeal_page1 = 0;

  regex_t preg_Pages;
  const size_t nmatch_Pages = 2;
  const char regex_Pages[] =
    "^%%Pages:[[:space:]]+([[:digit:]]+)[[:space:]]*$";
  regmatch_t pmatch_Pages[nmatch_Pages];

  regex_t preg_PageOrder;
  const size_t nmatch_PageOrder = 2;
  const char regex_PageOrder[] =
    "^%%PageOrder:[[:space:]]+([^[:space:]]+)[[:space:]]*$";
  regmatch_t pmatch_PageOrder[nmatch_PageOrder];

  regex_t preg_Page;
  const size_t nmatch_Page = 3;
  const char regex_Page[] =
    "^%%Page:"
    "[[:space:]]+([[:digit:]]+)"
    "[[:space:]]+([[:digit:]]+)"
    "[[:space:]]*$";
  regmatch_t pmatch_Page[nmatch_Page];

  if (
    regcomp( &preg_Pages    , regex_Pages    , REG_EXTENDED ) ||
    regcomp( &preg_PageOrder, regex_PageOrder, REG_EXTENDED ) ||
    regcomp( &preg_Page     , regex_Page     , REG_EXTENDED )
  ) error(
    EPERM, 0,
    "internal malfunction (cannot compile the PS regexes)"
  );

  int n_page_whole = 0;

  {

    bool have_found_Pages     = false;
    bool have_found_PageOrder = false;
    string line;

    for (;;) {

      std::getline( file_ps, line );
      if ( file_ps.eof() ) break;
      const char *const line1 = line.c_str();

      if (
        !regexec(
          &preg_Page,
          line1,
          nmatch_Page,
          pmatch_Page,
          0
        )
      ) {
        if ( !( have_found_Pages && have_found_PageOrder ) ) error(
          EPERM, 0,
          "found \"%%%%Page:\" in the PS file "
          "before finding \"%%%%Pages:\" and \"%%%%PageOrder:\""
        );
        char *const i_page_part_str =
          match_dup( line1, &pmatch_Page[1] );
        const int i_page_part = atoi( i_page_part_str  );
        free( i_page_part_str );
        char *const i_page_whole_str =
          match_dup( line1, &pmatch_Page[2] );
        const int i_page_whole = atoi( i_page_whole_str );
        free( i_page_whole_str );
        // At this point, i_page_part and i_page_whole hold a single
        // page's page numbers with respect, respectively, to a part of
        // the document and to the whole document.
        {
          const char err_msg[] =
            "confused by irregular page numbering in the PS file; "
            "expecting an orderly sequence of corporeal "
            "(main body) pages "
            "possibly preceded by an orderly sequence of prefatory pages, "
            "each sequence beginning from page 1";
          if ( i_page_part == i_page_whole ) {
            if (
              n_corporeal_page1 ||
              i_page_part != n_prefatory_page1 + 1
            ) error( EPERM, 0, err_msg );
            n_prefatory_page1 = i_page_part;
          }
          else {
            if (
              i_page_whole != i_page_part + n_prefatory_page1 ||
              i_page_part != n_corporeal_page1 + 1
            ) error( EPERM, 0, err_msg );
            n_corporeal_page1 = i_page_part;
          }
        }
      }

      else if (
        !regexec(
          &preg_Pages,
          line1,
          nmatch_Pages,
          pmatch_Pages,
          0
        )
      ) {
        if ( have_found_Pages ) error(
          EPERM, 0,
          "found more than one \"%%%%Pages:\" line in the PS file"
        );
        have_found_Pages = true;
        char *const n_page_whole_str =
          match_dup( line1, &pmatch_Pages[1] );
        n_page_whole = atoi( n_page_whole_str );
        free( n_page_whole_str );
      }

      else if (
        !regexec(
          &preg_PageOrder,
          line1,
          nmatch_PageOrder,
          pmatch_PageOrder,
          0
        )
      ) {
        if ( have_found_PageOrder ) error(
          EPERM, 0,
          "found more than one \"%%%%PageOrder:\" line in the PS file"
        );
        have_found_PageOrder = true;
        char *const pageOrder_str =
          match_dup( line1, &pmatch_PageOrder[1] );
        if ( strcmp( pageOrder_str, "Ascend" ) ) error(
          EPERM, 0,
          "sorry, am (as yet) programmed to handle "
          "only PS \"%%%%PageOrder: Ascend\""
        );
        free( pageOrder_str );
      }

    }

  }

  if ( !n_corporeal_page1 ) {
    n_corporeal_page1 = n_prefatory_page1;
    n_prefatory_page1 = 0;
  }

  if ( n_prefatory_page1 + n_corporeal_page1 != n_page_whole ) error(
    EPERM, 0,
    "the PS \"%%%%Pages:\" line gives a number "
    "different than the number of pages actually found"
  );

}

