/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libe-book project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <algorithm>
#include <cassert>
#include <cstring>

#include <boost/scoped_ptr.hpp>

#include "libebook_utils.h"
#include "EBOOKCharsetConverter.h"
#include "PDBParser.h"
#include "PDXLZ77Stream.h"

using std::string;
using std::vector;

namespace libebook
{

//static const unsigned PDB_BLOCK_SIZE = 4096;

static const unsigned PDB_TYPE = PDX_CODE("TEXt");
static const unsigned PDB_CREATOR = PDX_CODE("REAd");

PDBParser::PDBParser(librevenge::RVNGInputStream *input, librevenge::RVNGTextInterface *document)
  : PDXParser(input, document, PDB_TYPE, PDB_CREATOR)
  , m_compressed(false)
  , m_textLength(0)
  , m_recordCount(0)
  , m_recordSize(0)
  , m_read(0)
  , m_openedParagraph(false)
  , m_openedDocument(false)
  , m_converter(0)
{
}

PDBParser::~PDBParser()
{
  delete m_converter;
}

bool PDBParser::checkType(const unsigned type, const unsigned creator)
{
  return (PDB_TYPE == type) && (PDB_CREATOR == creator);
}

void PDBParser::readAppInfoRecord(librevenge::RVNGInputStream *)
{
  // there is no appInfo in PalmDoc
}

void PDBParser::readSortInfoRecord(librevenge::RVNGInputStream *)
{
  // there is no sortInfo in PalmDoc
}

void PDBParser::readIndexRecord(librevenge::RVNGInputStream *const input)
{
  const uint16_t compression = readU16(input, true);
  if ((1 != compression) && (2 != compression))
  {
    EBOOK_DEBUG_MSG(("unexpected compression type %u, the file is likely damaged\n", compression));
  }
  m_compressed = 1 != compression;
  skip(input, 2);
  m_textLength = readU32(input, true);
  m_recordCount = readU16(input, true);
  m_recordSize = readU16(input, true);

  // check consistency
  // assert(m_recordCount == getDataRecordCount());
  // assert(PDB_BLOCK_SIZE == m_recordSize);
}

void PDBParser::readDataRecord(librevenge::RVNGInputStream *input, const bool last)
{
  vector<char> uncompressed;
  uncompressed.reserve(m_recordSize);

  boost::scoped_ptr<librevenge::RVNGInputStream> compressedInput;

  if (m_compressed)
  {
    compressedInput.reset(new PDXLZ77Stream(input));
    input = compressedInput.get();
  }

  const long origPos = input->tell();
  while (!input->isEnd())
    uncompressed.push_back((char) readU8(input));
  m_read += unsigned(input->tell() - origPos);

  // assert(m_read <= m_textLength);
  // if (last)
  // assert(m_read == m_textLength);

  if (!m_openedDocument)
  {
    createConverter(uncompressed);
    openDocument();
  }

  handleText(uncompressed);

  if (last)
    closeDocument();
}

void PDBParser::createConverter(const std::vector<char> &text)
{
  if (text.empty())
    return;

  EBOOKCharsetConverter *const converter = new EBOOKCharsetConverter();
  if (converter->guessEncoding(&text[0], (unsigned) text.size()))
    m_converter = converter;
  else
  {
    delete converter;
    throw GenericException();
  }
}

void PDBParser::openDocument()
{
  if (m_openedDocument)
    return;

  librevenge::RVNGPropertyList metadata;

  if (*getName())
  {
    vector<char> nameUtf8;
    if (m_converter->convertBytes(getName(), std::strlen(getName()), nameUtf8) && !nameUtf8.empty())
    {
      nameUtf8.push_back(0);
      metadata.insert("dc:title", librevenge::RVNGString(&nameUtf8[0]));
    }
  }

  getDocument()->startDocument(librevenge::RVNGPropertyList());
  getDocument()->setDocumentMetaData(metadata);
  getDocument()->openPageSpan(librevenge::RVNGPropertyList());

  m_openedDocument = true;
}

void PDBParser::closeDocument()
{
  // Hey! There was no paragraph break at the end of the last record!
  if (m_openedParagraph)
    closeParagraph();
  m_openedParagraph = false;

  getDocument()->closePageSpan();
  getDocument()->endDocument();
  m_openedDocument = false;
}

void PDBParser::handleText(const vector<char> &text)
{
  vector<char>::const_iterator first(text.begin());
  vector<char>::const_iterator last(text.begin());
  const vector<char>::const_iterator end(text.end());

  while (first != end)
  {
    last = std::find(first, end, '\n');

    openParagraph();
    if (last > first)
    {
      vector<char> out;
      if (m_converter->convertBytes(&*first, static_cast<unsigned>(last - first), out) && !out.empty())
      {
        out.push_back(0);
        handleCharacters(&out[0]);
      }
    }
    closeParagraph(last == end);

    first = last;
    if (first != end)
      ++first;
  }
}

void PDBParser::openParagraph()
{
  if (!m_openedParagraph)
    getDocument()->openParagraph(librevenge::RVNGPropertyList());
  m_openedParagraph = true;
}

void PDBParser::closeParagraph(const bool continuing)
{
  assert(m_openedParagraph);
  if (!continuing)
    getDocument()->closeParagraph();
  m_openedParagraph = continuing;
}

void PDBParser::handleCharacters(const char *const text)
{
  if (text == 0)
    return;

  getDocument()->insertText(librevenge::RVNGString(text));
}

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
