//========================================================================
//
// Hints.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright 2010, 2012 Hib Eris <hib@hiberis.nl>
// Copyright 2010, 2011, 2013, 2014, 2016-2019 Albert Astals Cid <aacid@kde.org>
// Copyright 2010, 2013 Pino Toscano <pino@kde.org>
// Copyright 2013 Adrian Johnson <ajohnson@redneon.com>
// Copyright 2014 Fabio D'Urso <fabiodurso@hotmail.it>
// Copyright 2016 Jeffrey Morlan <jmmorlan@sonic.net>
// Copyright 2019 LE GARREC Vincent <legarrec.vincent@gmail.com>
// Copyright 2019 Adam Reichold <adam.reichold@t-online.de>
//
//========================================================================

#include <config.h>

#include "Hints.h"

#include "Linearization.h"
#include "Object.h"
#include "Stream.h"
#include "XRef.h"
#include "Parser.h"
#include "Lexer.h"
#include "SecurityHandler.h"

#include <climits>

class StreamBitReader {
public:
  StreamBitReader(Stream *strA)
    : str(strA)
    , inputBits(0)
    , isAtEof(false)
  {
  }

  void resetInputBits()
  {
    inputBits = 0;
  }

  bool atEOF() const
  {
    return isAtEof;
  }

  unsigned int readBit()
  {
    unsigned int bit;
    int c;

    if (inputBits == 0) {
      if ((c = str->getChar()) == EOF) {
        isAtEof = true;
        return (unsigned int) -1;
      }
      bitsBuffer = c;
      inputBits = 8;
    }
    bit = (bitsBuffer >> (inputBits - 1)) & 1;
    --inputBits;
    return bit;
  }

  unsigned int readBits(int n)
  {
    unsigned int bit, bits;

    if (n < 0) return -1;
    if (n == 0) return 0;

    if (n == 1)
      return readBit();

    bit = readBit();
    if (bit == (unsigned int) -1)
      return -1;

    bit = bit << (n-1);

    bits = readBits(n - 1);
    if (bits == (unsigned int) -1)
      return -1;

    return bit | bits;
  }

private:
  Stream *str;
  int inputBits;
  char bitsBuffer;
  bool isAtEof;
};

//------------------------------------------------------------------------
// Hints
//------------------------------------------------------------------------

Hints::Hints(BaseStream *str, Linearization *linearization, XRef *xref, SecurityHandler *secHdlr)
{
  mainXRefEntriesOffset = linearization->getMainXRefEntriesOffset();
  nPages = linearization->getNumPages();
  pageFirst = linearization->getPageFirst();
  pageEndFirst = linearization->getEndFirst();
  pageObjectFirst = linearization->getObjectNumberFirst();
  if (pageObjectFirst < 0 || pageObjectFirst >= xref->getNumObjects()) {
    error(errSyntaxWarning, -1,
      "Invalid reference for first page object ({0:d}) in linearization table ",
      pageObjectFirst);
    pageObjectFirst = 0;
  }
  XRefEntry *pageObjectFirstXRefEntry = xref->getEntry(pageObjectFirst);
  if (!pageObjectFirstXRefEntry) {
      error(errSyntaxWarning, -1, "No XRef entry for first page object");
      pageOffsetFirst = 0;
  } else {
      pageOffsetFirst = pageObjectFirstXRefEntry->offset;
  }

  if (nPages >= INT_MAX / (int)sizeof(unsigned int)) {
     error(errSyntaxWarning, -1, "Invalid number of pages ({0:d}) for hints table", nPages);
     nPages = 0;
  }
  nObjects = (unsigned int *) gmallocn_checkoverflow(nPages, sizeof(unsigned int));
  pageObjectNum = (int *) gmallocn_checkoverflow(nPages, sizeof(int));
  xRefOffset = (unsigned int *) gmallocn_checkoverflow(nPages, sizeof(unsigned int));
  pageLength = (unsigned int *) gmallocn_checkoverflow(nPages, sizeof(unsigned int));
  pageOffset = (Goffset *) gmallocn_checkoverflow(nPages, sizeof(Goffset));
  numSharedObject = (unsigned int *) gmallocn_checkoverflow(nPages, sizeof(unsigned int));
  sharedObjectId = (unsigned int **) gmallocn_checkoverflow(nPages, sizeof(unsigned int*));
  if (!nObjects || !pageObjectNum || !xRefOffset || !pageLength || !pageOffset ||
      !numSharedObject || !sharedObjectId) {
    error(errSyntaxWarning, -1, "Failed to allocate memory for hints table");
    nPages = 0;
  }

  if (nPages != 0) {
    memset(pageLength, 0, nPages * sizeof(unsigned int));
    memset(pageOffset, 0, nPages * sizeof(unsigned int));
    memset(numSharedObject, 0, nPages * sizeof(unsigned int));
    memset(pageObjectNum, 0, nPages * sizeof(int));
  }

  groupLength = nullptr;
  groupOffset = nullptr;
  groupHasSignature = nullptr;
  groupNumObjects = nullptr;
  groupXRefOffset = nullptr;

  ok = true;
  readTables(str, linearization, xref, secHdlr);
}

Hints::~Hints()
{
  gfree(nObjects);
  gfree(pageObjectNum);
  gfree(xRefOffset);
  gfree(pageLength);
  gfree(pageOffset);
  for (int i=0; i< nPages; i++) {
    if (numSharedObject[i]) {
       gfree(sharedObjectId[i]);
    }
  }
  gfree(sharedObjectId);
  gfree(numSharedObject);

  gfree(groupLength);
  gfree(groupOffset);
  gfree(groupHasSignature);
  gfree(groupNumObjects);
  gfree(groupXRefOffset);
}

void Hints::readTables(BaseStream *str, Linearization *linearization, XRef *xref, SecurityHandler *secHdlr)
{
  hintsOffset = linearization->getHintsOffset();
  hintsLength = linearization->getHintsLength();
  hintsOffset2 = linearization->getHintsOffset2();
  hintsLength2 = linearization->getHintsLength2();

  const unsigned int bufLength = hintsLength + hintsLength2;

  if (bufLength == 0) {
      ok = false;
      return;
  }

  std::vector<char> buf(bufLength);
  char *p = &buf[0];

  if (hintsOffset && hintsLength) {
    Stream *s = str->makeSubStream(hintsOffset, false, hintsLength, Object(objNull));
    s->reset();
    for (unsigned int i=0; i < hintsLength; i++) { *p++ = s->getChar(); }
    delete s;
  }

  if (hintsOffset2 && hintsLength2) {
    Stream *s = str->makeSubStream(hintsOffset2, false, hintsLength2, Object(objNull));
    s->reset();
    for (unsigned int i=0; i < hintsLength2; i++) { *p++ = s->getChar(); }
    delete s;
  }

  MemStream *memStream = new MemStream (&buf[0], 0, bufLength, Object(objNull));

  Parser *parser = new Parser(xref, memStream, true);

  int num, gen;
  Object obj;
  if ((obj = parser->getObj(), obj.isInt()) &&
     (num = obj.getInt(), obj = parser->getObj(), obj.isInt()) &&
     (gen = obj.getInt(), obj = parser->getObj(), obj.isCmd("obj")) &&
     (obj = parser->getObj(false,
         secHdlr ? secHdlr->getFileKey() : nullptr,
         secHdlr ? secHdlr->getEncAlgorithm() : cryptRC4,
         secHdlr ? secHdlr->getFileKeyLength() : 0,
         num, gen, 0, true), obj.isStream())) {
    Stream *hintsStream = obj.getStream();
    Dict *hintsDict = obj.streamGetDict();

    int sharedStreamOffset = 0;
    if (hintsDict->lookupInt("S", nullptr, &sharedStreamOffset) &&
        sharedStreamOffset > 0) {

        hintsStream->reset();
        ok = readPageOffsetTable(hintsStream);

        if (ok) {
          hintsStream->reset();
          for (int i=0; i<sharedStreamOffset; i++) hintsStream->getChar();
          ok = readSharedObjectsTable(hintsStream);
        }
    } else {
      error(errSyntaxWarning, -1, "Invalid shared object hint table offset");
      ok = false;
    }
  } else {
    error(errSyntaxWarning, -1, "Failed parsing hints table object");
    ok = false;
  }

  delete parser;
}

bool Hints::readPageOffsetTable(Stream *str)
{
  if (nPages < 1) {
    error(errSyntaxWarning, -1, "Invalid number of pages reading page offset hints table");
    return false;
  }

  StreamBitReader sbr(str);

  nObjectLeast = sbr.readBits(32);
  if (nObjectLeast < 1) {
    error(errSyntaxWarning, -1, "Invalid least number of objects reading page offset hints table");
    nPages = 0;
    return false;
  }

  objectOffsetFirst = sbr.readBits(32);
  if (objectOffsetFirst >= hintsOffset) objectOffsetFirst += hintsLength;

  nBitsDiffObjects = sbr.readBits(16);
  if (nBitsDiffObjects > 32) {
    error(errSyntaxWarning, -1, "Invalid number of bits needed to represent the difference between the greatest and least number of objects in a page");
    nPages = 0;
    return false;
  }

  pageLengthLeast = sbr.readBits(32);

  nBitsDiffPageLength = sbr.readBits(16);

  OffsetStreamLeast = sbr.readBits(32);

  nBitsOffsetStream = sbr.readBits(16);

  lengthStreamLeast = sbr.readBits(32);

  nBitsLengthStream = sbr.readBits(16);

  nBitsNumShared = sbr.readBits(16);

  nBitsShared = sbr.readBits(16);

  nBitsNumerator = sbr.readBits(16);

  denominator = sbr.readBits(16);

  for (int i = 0; i < nPages && !sbr.atEOF(); i++) {
    nObjects[i] = nObjectLeast + sbr.readBits(nBitsDiffObjects);
  }
  if (sbr.atEOF())
    return false;

  nObjects[0] = 0;
  xRefOffset[0] = mainXRefEntriesOffset + 20;
  for (int i=1; i<nPages; i++) {
    xRefOffset[i] = xRefOffset[i-1] + 20*nObjects[i-1];
  }

  pageObjectNum[0] = 1;
  for (int i=1; i<nPages; i++) {
    pageObjectNum[i] = pageObjectNum[i-1] + nObjects[i-1];
  }
  pageObjectNum[0] = pageObjectFirst;

  sbr.resetInputBits(); // reset on byte boundary. Not in specs!
  for (int i = 0; i < nPages && !sbr.atEOF(); i++) {
    pageLength[i] = pageLengthLeast + sbr.readBits(nBitsDiffPageLength);
  }
  if (sbr.atEOF())
    return false;

  sbr.resetInputBits(); // reset on byte boundary. Not in specs!
  numSharedObject[0] = sbr.readBits(nBitsNumShared);
  numSharedObject[0] = 0; // Do not trust the read value to be 0.
  sharedObjectId[0] = nullptr;
  for (int i = 1; i < nPages && !sbr.atEOF(); i++) {
    numSharedObject[i] = sbr.readBits(nBitsNumShared);
    if (numSharedObject[i] >= INT_MAX / (int)sizeof(unsigned int)) {
       error(errSyntaxWarning, -1, "Invalid number of shared objects");
       numSharedObject[i] = 0;
       return false;
    }
    sharedObjectId[i] = (unsigned int *) gmallocn_checkoverflow(numSharedObject[i], sizeof(unsigned int));
    if (numSharedObject[i] && !sharedObjectId[i]) {
       error(errSyntaxWarning, -1, "Failed to allocate memory for shared object IDs");
       numSharedObject[i] = 0;
       return false;
    }
  }
  if (sbr.atEOF())
    return false;

  sbr.resetInputBits(); // reset on byte boundary. Not in specs!
  for (int i=1; i<nPages; i++) {
    for (unsigned int j = 0; j < numSharedObject[i] && !sbr.atEOF(); j++) {
      sharedObjectId[i][j] = sbr.readBits(nBitsShared);
    }
  }

  pageOffset[0] = pageOffsetFirst;
  // find pageOffsets.
  for (int i=1; i<nPages; i++) {
    pageOffset[i] = pageOffset[i-1] + pageLength[i-1];
  }

  return !sbr.atEOF();
}

bool Hints::readSharedObjectsTable(Stream *str)
{
  StreamBitReader sbr(str);

  const unsigned int firstSharedObjectNumber = sbr.readBits(32);

  const unsigned int firstSharedObjectOffset = sbr.readBits(32) + hintsLength;

  const unsigned int nSharedGroupsFirst = sbr.readBits(32);

  const unsigned int nSharedGroups = sbr.readBits(32);

  const unsigned int nBitsNumObjects = sbr.readBits(16);

  const unsigned int groupLengthLeast = sbr.readBits(32);

  const unsigned int nBitsDiffGroupLength = sbr.readBits(16);

  if ((!nSharedGroups) || (nSharedGroups >= INT_MAX / (int)sizeof(unsigned int))) {
     error(errSyntaxWarning, -1, "Invalid number of shared object groups");
     return false;
  }
  if ((!nSharedGroupsFirst) || (nSharedGroupsFirst > nSharedGroups)) {
     error(errSyntaxWarning, -1, "Invalid number of first page shared object groups");
     return false;
  }
  if (nBitsNumObjects > 32 || nBitsDiffGroupLength > 32) {
     error(errSyntaxWarning, -1, "Invalid shared object groups bit length");
     return false;
  }

  groupLength = (unsigned int *) gmallocn_checkoverflow(nSharedGroups, sizeof(unsigned int));
  groupOffset = (unsigned int *) gmallocn_checkoverflow(nSharedGroups, sizeof(unsigned int));
  groupHasSignature = (unsigned int *) gmallocn_checkoverflow(nSharedGroups, sizeof(unsigned int));
  groupNumObjects = (unsigned int *) gmallocn_checkoverflow(nSharedGroups, sizeof(unsigned int));
  groupXRefOffset = (unsigned int *) gmallocn_checkoverflow(nSharedGroups, sizeof(unsigned int));
  if (!groupLength || !groupOffset || !groupHasSignature ||
      !groupNumObjects || !groupXRefOffset) {
     error(errSyntaxWarning, -1, "Failed to allocate memory for shared object groups");
     return false;
  }

  sbr.resetInputBits(); // reset on byte boundary. Not in specs!
  for (unsigned int i = 0; i < nSharedGroups && !sbr.atEOF(); i++) {
    groupLength[i] = groupLengthLeast + sbr.readBits(nBitsDiffGroupLength);
  }
  if (sbr.atEOF())
    return false;

  groupOffset[0] = objectOffsetFirst;
  for (unsigned int i=1; i<nSharedGroupsFirst; i++) {
    groupOffset[i] = groupOffset[i-1] + groupLength[i-1];
  }
  if (nSharedGroups > nSharedGroupsFirst ) {
    groupOffset[nSharedGroupsFirst] = firstSharedObjectOffset;
    for (unsigned int i=nSharedGroupsFirst+1; i<nSharedGroups; i++) {
      groupOffset[i] = groupOffset[i-1] + groupLength[i-1];
    }
  }

  sbr.resetInputBits(); // reset on byte boundary. Not in specs!
  for (unsigned int i = 0; i < nSharedGroups && !sbr.atEOF(); i++) {
    groupHasSignature[i] = sbr.readBits(1);
  }
  if (sbr.atEOF())
    return false;

  sbr.resetInputBits(); // reset on byte boundary. Not in specs!
  for (unsigned int i = 0; i < nSharedGroups && !sbr.atEOF(); i++) {
    if (groupHasSignature[i]) {
       // readBits doesn't supports more than 32 bits.
       sbr.readBits(32);
       sbr.readBits(32);
       sbr.readBits(32);
       sbr.readBits(32);
    }
  }
  if (sbr.atEOF())
    return false;

  sbr.resetInputBits(); // reset on byte boundary. Not in specs!
  for (unsigned int i = 0; i < nSharedGroups && !sbr.atEOF(); i++) {
    groupNumObjects[i] =
       nBitsNumObjects ? 1 + sbr.readBits(nBitsNumObjects) : 1;
  }

  for (unsigned int i=0; i<nSharedGroupsFirst; i++) {
    groupNumObjects[i] = 0;
    groupXRefOffset[i] = 0;
  }
  if (nSharedGroups > nSharedGroupsFirst ) {
    groupXRefOffset[nSharedGroupsFirst] =
        mainXRefEntriesOffset + 20*firstSharedObjectNumber;
    for (unsigned int i=nSharedGroupsFirst+1; i<nSharedGroups; i++) {
      groupXRefOffset[i] = groupXRefOffset[i-1] + 20*groupNumObjects[i-1];
    }
  }

  return !sbr.atEOF();
}

bool Hints::isOk() const
{
  return ok;
}

Goffset Hints::getPageOffset(int page)
{
  if ((page < 1) || (page > nPages)) return 0;

  if (page-1 > pageFirst)
    return pageOffset[page-1];
  else if (page-1 < pageFirst)
    return pageOffset[page];
  else
    return pageOffset[0];
}

std::vector<ByteRange>* Hints::getPageRanges(int page)
{
  if ((page < 1) || (page > nPages)) return nullptr;

  int idx;
  if (page-1 > pageFirst)
     idx = page-1;
  else if (page-1 < pageFirst)
     idx = page;
  else
     idx = 0;

  ByteRange pageRange;
  std::vector<ByteRange> *v = new std::vector<ByteRange>;

  pageRange.offset = pageOffset[idx];
  pageRange.length = pageLength[idx];
  v->push_back(pageRange);

  pageRange.offset = xRefOffset[idx];
  pageRange.length = 20*nObjects[idx];
  v->push_back(pageRange);

  for (unsigned int j=0; j<numSharedObject[idx]; j++) {
     unsigned int k = sharedObjectId[idx][j];

     pageRange.offset = groupOffset[k];
     pageRange.length = groupLength[k];
     v->push_back(pageRange);

     pageRange.offset = groupXRefOffset[k];
     pageRange.length = 20*groupNumObjects[k];
     v->push_back(pageRange);
  }

  return v;
}

int Hints::getPageObjectNum(int page) {
  if ((page < 1) || (page > nPages)) return 0;

  if (page-1 > pageFirst)
    return pageObjectNum[page-1];
  else if (page-1 < pageFirst)
    return pageObjectNum[page];
  else
    return pageObjectNum[0];
}
