/* -*- Mode: C++; c-default-style: "k&r"; indent-tabs-mode: nil; tab-width: 2; c-basic-offset: 2 -*- */

/* libmwaw
* Version: MPL 2.0 / LGPLv2+
*
* The contents of this file are subject to the Mozilla Public License Version
* 2.0 (the "License"); you may not use this file except in compliance with
* the License or as specified alternatively below. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Major Contributor(s):
* Copyright (C) 2002 William Lachance (wrlach@gmail.com)
* Copyright (C) 2002,2004 Marc Maurer (uwog@uwog.net)
* Copyright (C) 2004-2006 Fridrich Strba (fridrich.strba@bluewin.ch)
* Copyright (C) 2006, 2007 Andrew Ziem
* Copyright (C) 2011, 2012 Alonso Laurent (alonso@loria.fr)
*
*
* All Rights Reserved.
*
* For minor contributions see the git repository.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"),
* in which case the provisions of the LGPLv2+ are applicable
* instead of those above.
*/

#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <sstream>

#include <librevenge/librevenge.h>

#include "MWAWTextListener.hxx"
#include "MWAWHeader.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWPosition.hxx"
#include "MWAWPictMac.hxx"
#include "MWAWPrinter.hxx"

#include "WriteNowEntry.hxx"
#include "WriteNowText.hxx"

#include "WriteNowParser.hxx"

/** Internal: the structures of a WriteNowParser */
namespace WriteNowParserInternal
{
////////////////////////////////////////
//! Internal: the state of a WriteNowParser
struct State {
  //! constructor
  State()
    : m_endPos(-1)
    , m_colorMap()
    , m_picturesList()
    , m_actPage(0)
    , m_numPages(0)
    , m_headerHeight(0)
    , m_footerHeight(0)
    , m_numColumns(1)
    , m_columnWidth(-1)
  {
  }

  //! the last position
  long m_endPos;
  //! the color map
  std::vector<MWAWColor> m_colorMap;
  //! the list of picture entries
  std::vector<WriteNowEntry> m_picturesList;

  int m_actPage /** the actual page */, m_numPages /** the number of page of the final document */;

  int m_headerHeight /** the header height if known */,
      m_footerHeight /** the footer height if known */;

  int m_numColumns /** the number of columns */, m_columnWidth /** the columns size */;
};

////////////////////////////////////////
//! Internal: the subdocument of a WriteNowParser
class SubDocument final : public MWAWSubDocument
{
public:
  SubDocument(WriteNowParser &pars, MWAWInputStreamPtr const &input, WriteNowEntry const &pos)
    : MWAWSubDocument(&pars, input, MWAWEntry())
    , m_pos(pos)
  {
  }

  //! destructor
  ~SubDocument() final {}

  //! operator!=
  bool operator!=(MWAWSubDocument const &doc) const final;

  //! the parser function
  void parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType type) final;

protected:
  //! the subdocument file position
  WriteNowEntry m_pos;
};

void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType /*type*/)
{
  if (!listener.get()) {
    MWAW_DEBUG_MSG(("WriteNowParserInternal::SubDocument::parse: no listener\n"));
    return;
  }
  auto *parser=dynamic_cast<WriteNowParser *>(m_parser);
  if (!parser) {
    MWAW_DEBUG_MSG(("WriteNowParserInternal::SubDocument::parse: no parser\n"));
    return;
  }

  long pos = m_input->tell();
  parser->send(m_pos);
  m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}

bool SubDocument::operator!=(MWAWSubDocument const &doc) const
{
  if (MWAWSubDocument::operator!=(doc)) return true;
  auto const *sDoc = dynamic_cast<SubDocument const *>(&doc);
  if (!sDoc) return true;
  if (m_pos != sDoc->m_pos) return true;
  return false;
}
}


////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
WriteNowParser::WriteNowParser(MWAWInputStreamPtr const &input, MWAWRSRCParserPtr const &rsrcParser, MWAWHeader *header)
  : MWAWTextParser(input, rsrcParser, header)
  , m_state()
  , m_entryManager()
  , m_textParser()
{
  init();
}

WriteNowParser::~WriteNowParser()
{
}

void WriteNowParser::init()
{
  resetTextListener();
  setAsciiName("main-1");

  m_state.reset(new WriteNowParserInternal::State);
  m_entryManager.reset(new WriteNowEntryManager);

  // reduce the margin (in case, the page is not defined)
  getPageSpan().setMargins(0.1);

  m_textParser.reset(new WriteNowText(*this));
}

////////////////////////////////////////////////////////////
// position and height
////////////////////////////////////////////////////////////
void WriteNowParser::getColumnInfo(int &numColumns, int &width) const
{
  numColumns = m_state->m_numColumns;
  width = m_state->m_columnWidth;
}

////////////////////////////////////////////////////////////
// new page and color
////////////////////////////////////////////////////////////
void WriteNowParser::newPage(int number)
{
  if (number <= m_state->m_actPage || number > m_state->m_numPages)
    return;

  while (m_state->m_actPage < number) {
    m_state->m_actPage++;
    if (!getTextListener() || m_state->m_actPage == 1)
      continue;
    getTextListener()->insertBreak(MWAWTextListener::PageBreak);
  }
}

bool WriteNowParser::getColor(int colId, MWAWColor &col) const
{
  if (colId >= 0 && colId < int(m_state->m_colorMap.size())) {
    col = m_state->m_colorMap[size_t(colId)];
    return true;
  }
  return false;
}

void WriteNowParser::sendFootnote(WriteNowEntry const &entry)
{
  if (!getTextListener()) return;

  MWAWSubDocumentPtr subdoc(new WriteNowParserInternal::SubDocument(*this, getInput(), entry));
  getTextListener()->insertNote(MWAWNote(MWAWNote::FootNote), subdoc);
}

void WriteNowParser::send(WriteNowEntry const &entry)
{
  m_textParser->send(entry);
}

bool WriteNowParser::sendGraphic(int gId, MWAWBox2i const &bdbox)
{
  if (gId < 8 || (gId-8) >= int(m_state->m_picturesList.size())) {
    MWAW_DEBUG_MSG(("WriteNowParser::sendGraphic: called with bad id=%d\n", gId));
    return false;
  }
  if (!m_state->m_picturesList[size_t(gId)-8].isZone()) {
    MWAW_DEBUG_MSG(("WriteNowParser::sendGraphic: called with a no zone id=%d\n", gId));
    return false;
  }

  sendPicture(m_state->m_picturesList[size_t(gId)-8], bdbox);
  return true;
}


////////////////////////////////////////////////////////////
// the parser
////////////////////////////////////////////////////////////
void WriteNowParser::parse(librevenge::RVNGTextInterface *docInterface)
{
  if (!getInput().get() || !checkHeader(nullptr))  throw(libmwaw::ParseException());
  bool ok = true;
  try {
    // create the asciiFile
    ascii().setStream(getInput());
    ascii().open(asciiName());

    m_entryManager->reset();
    checkHeader(nullptr);
    ascii().addPos(getInput()->tell());
    ascii().addNote("_");

    ok = createZones();
    if (ok) {
      createDocument(docInterface);
      m_textParser->sendZone(0);

      // ok, we can now send what is not send.
      m_textParser->flushExtra();
      MWAWBox2i emptyBdBox;
      for (auto &picture : m_state->m_picturesList) {
        if (picture.isParsed() || !picture.isZone()) continue;
        sendPicture(picture, emptyBdBox);
      }
    }

    ascii().reset();
  }
  catch (...) {
    MWAW_DEBUG_MSG(("WriteNowParser::parse: exception catched when parsing\n"));
    ok = false;
  }

  resetTextListener();
  if (!ok) throw(libmwaw::ParseException());
}

////////////////////////////////////////////////////////////
// create the document
////////////////////////////////////////////////////////////
void WriteNowParser::createDocument(librevenge::RVNGTextInterface *documentInterface)
{
  if (!documentInterface) return;
  if (getTextListener()) {
    MWAW_DEBUG_MSG(("WriteNowParser::createDocument: listener already exist\n"));
    return;
  }

  // update the page
  m_state->m_actPage = 0;

  // create the page list
  MWAWPageSpan ps(getPageSpan());

  WriteNowEntry entry = m_textParser->getHeader();
  if (entry.valid()) {
    MWAWHeaderFooter header(MWAWHeaderFooter::HEADER, MWAWHeaderFooter::ALL);
    header.m_subDocument.reset(new WriteNowParserInternal::SubDocument(*this, getInput(), entry));
    ps.setHeaderFooter(header);
  }

  entry = m_textParser->getFooter();
  if (entry.valid()) {
    MWAWHeaderFooter footer(MWAWHeaderFooter::FOOTER, MWAWHeaderFooter::ALL);
    footer.m_subDocument.reset(new WriteNowParserInternal::SubDocument(*this, getInput(), entry));
    ps.setHeaderFooter(footer);
  }

  int numPage = m_textParser->numPages();
  m_state->m_numPages = numPage;
  ps.setPageSpan(m_state->m_numPages+1);
  std::vector<MWAWPageSpan> pageList(1,ps);
  //
  MWAWTextListenerPtr listen(new MWAWTextListener(*getParserState(), pageList, documentInterface));
  setTextListener(listen);
  listen->startDocument();
}


////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// try to find the different zone
////////////////////////////////////////////////////////////
bool WriteNowParser::createZones()
{
  if (version() < 3) {
    if (!readDocEntriesV2())
      return false;
  }
  else if (!readDocEntries())
    return false;

  // the Color map zone
  auto iter = m_entryManager->m_typeMap.find("ColMap");
  if (iter != m_entryManager->m_typeMap.end())
    readColorMap(*iter->second);

  // the graphic zone
  iter = m_entryManager->m_typeMap.find("GraphZone");
  if (iter != m_entryManager->m_typeMap.end())
    parseGraphicZone(*iter->second);


  iter = m_entryManager->m_typeMap.find("UnknZone1");
  if (iter != m_entryManager->m_typeMap.end())
    readGenericUnkn(*iter->second);

  iter = m_entryManager->m_typeMap.find("PrintZone");
  if (iter != m_entryManager->m_typeMap.end())
    readPrintInfo(*iter->second);

  // checkme: never seens, but probably also a list of zone...
  iter = m_entryManager->m_typeMap.find("UnknZone2");
  if (iter != m_entryManager->m_typeMap.end())
    readGenericUnkn(*iter->second);

  bool ok = m_textParser->createZones();

  // fixme: we must continue....
  libmwaw::DebugStream f;
  for (auto it : m_entryManager->m_typeMap) {
    WriteNowEntry const &ent = *it.second;
    if (ent.isParsed()) continue;
    ascii().addPos(ent.begin());
    f.str("");
    f << "Entries(" << it.first << ")";
    if (ent.id() >= 0) f << "[" << ent.id() << "]";
    ascii().addNote(f.str().c_str());
    ascii().addPos(ent.end());
    ascii().addNote("_");
  }
  return ok;
}

////////////////////////////////////////////////////////////
//
// Low level
//
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// try to read the document main zone
////////////////////////////////////////////////////////////
bool WriteNowParser::readDocEntries()
{
  MWAWInputStreamPtr input = getInput();

  auto it = m_entryManager->m_typeMap.find("DocEntries");
  if (it == m_entryManager->m_typeMap.end()) {
    MWAW_DEBUG_MSG(("WriteNowParser::readDocEntries: can not find last zone\n"));
    return false;
  }
  WriteNowEntry const &entry = *(it->second);
  if (!entry.valid() || entry.length() < 148) {
    MWAW_DEBUG_MSG(("WriteNowParser::readDocEntries: last entry is invalid\n"));
    return false;
  }

  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  bool ok = input->readLong(4) == entry.length();
  if (!ok || input->readLong(4) != entry.begin()) {
    MWAW_DEBUG_MSG(("WriteNowParser::readDocEntries: bad begin of last zone\n"));
    return false;
  }
  entry.setParsed(true);
  libmwaw::DebugStream f;
  f << "Entries(DocEntries):";

  long val;
  long expectedVal[] = { 0, 0x80, 0x40000000L};
  long freePos = 0;
  for (int i = 0; i < 7; i++) { // 0, 80, 4000
    val = long(input->readULong(4));
    if (i == 3) {
      freePos = val;
      continue;
    }
    if ((i < 3 && val!= expectedVal[i]) || (i >=3 && val))
      f << "f" << i << "=" << std::hex << val << std::dec << ",";
  }
  if (freePos) { // checkme
    if (freePos > m_state->m_endPos) {
      MWAW_DEBUG_MSG(("WriteNowParser::readDocEntries: find odd freePos\n"));
    }
    else {
      f << "freeZone?=" << std::hex << freePos << std::dec << ",";
      ascii().addPos(freePos);
      ascii().addNote("Entries(Free)");
    }
  }
  char const *entryName[] = { "TextZone", "TextZone", "TextZone", "UnknZone0",
                              "GraphZone", "ColMap", "StylZone", "FontZone",
                              "UnknZone1", "UnknZone2"
                            };
  for (int i = 0; i < 10; i++) {
    WriteNowEntry zone = readEntry();
    zone.setType(entryName[i]);
    if (i < 3) zone.setId(i);
    if (zone.isZone())
      m_entryManager->add(zone);
    f << zone;
  }
  ascii().addDelimiter(input->tell(), '|');
  ascii().addPos(entry.begin());
  ascii().addNote(f.str().c_str());
  if (entry.length() > 386) {
    long pos = entry.begin()+376;
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    f.str("");
    f << "DocEntries-II:";
    m_state->m_numColumns = static_cast<int>(input->readLong(1));
    f << "nCol=" << m_state->m_numColumns << ",";
    val = input->readLong(1);
    if (val != 1) f << "unkn=" << val << ",";
    m_state->m_columnWidth = static_cast<int>(input->readLong(2));
    f << "colWidth=" << m_state->m_columnWidth << ",";

    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());

    if (long(input->tell()) != entry.end())
      ascii().addDelimiter(input->tell(), '|');
  }
  ascii().addPos(entry.end());
  ascii().addNote("_");

  return true;
}

////////////////////////////////////////////////////////////
// try to read the document main zone : v2
////////////////////////////////////////////////////////////
bool WriteNowParser::readDocEntriesV2()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell(), debPos=pos;
  libmwaw::DebugStream f;
  std::stringstream s;
  f << "Entries(DocEntries):";
  for (int i = 0; i < 5; i++) {
    auto val=static_cast<int>(input->readLong(1));
    if (val != 4 && val != 0x44) {
      MWAW_DEBUG_MSG(("WriteNowParser::readDocEntriesV2: can not find entries header:%d\n",i));
      return false;
    }
    auto dataPos = long(input->readULong(1));
    dataPos = (dataPos<<16)+long(input->readULong(2));
    if (!checkIfPositionValid(dataPos)) {
      MWAW_DEBUG_MSG(("WriteNowParser::readDocEntriesV2:  find an invalid position for entry:%d\n",i));
      continue;
    }
    WriteNowEntry entry;
    entry.setBegin(dataPos);
    switch (i) {
    case 0:
    case 1:
    case 2:
      entry.setType("TextZone");
      entry.setId(i);
      break;
    case 4:
      entry.setType("PrintZone");
      break;
    default: { // find 2 time 0006000800000000
      std::stringstream name;
      s << "Unknown" << i;
      entry.setType(name.str());
    }
    }
    long actPos = input->tell();
    input->seek(dataPos, librevenge::RVNG_SEEK_SET);
    entry.setLength(long(input->readULong(2)+2));
    input->seek(actPos, librevenge::RVNG_SEEK_SET);
    m_entryManager->add(entry);
  }
  f << "ptr=[";
  for (int i = 0; i < 5; i++)
    f << std::hex << input->readULong(4) << std::dec << ",";
  f << "],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(debPos+0x6E,librevenge::RVNG_SEEK_SET);
  pos=input->tell();
  f.str("");
  f << "DocEntries-II:";

  if (version()==2) {
    m_state->m_numColumns = static_cast<int>(input->readLong(1));
    f << "nCol=" << m_state->m_numColumns << ",";
    long val = input->readLong(1);
    if (val != 1) f << "unkn=" << val << ",";
    m_state->m_columnWidth = static_cast<int>(input->readLong(2));
    f << "colWidth=" << m_state->m_columnWidth << ",";
    ascii().addDelimiter(input->tell(),'|');
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  // another copy (visibly the previous version)
  ascii().addPos(debPos+0xFc);
  ascii().addNote("DocEntries[Old]:");
  ascii().addPos(debPos+0x16a);
  ascii().addNote("DocEntries-II[Old]:");
  ascii().addPos(debPos+0x1F8);
  ascii().addNote("_");
  return true;
}
////////////////////////////////////////////////////////////
// try to read a graphic list zone
////////////////////////////////////////////////////////////
bool WriteNowParser::parseGraphicZone(WriteNowEntry const &entry)
{
  MWAWInputStreamPtr input = getInput();

  if (!entry.valid() || entry.length() < 24) {
    MWAW_DEBUG_MSG(("WriteNowParser::parseGraphicZone: zone size is invalid\n"));
    return false;
  }

  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  if (input->readLong(4) != entry.length()) {
    MWAW_DEBUG_MSG(("WriteNowParser::parseGraphicZone: bad begin of last zone\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(GraphicZone):";
  f << "ptr?=" << std::hex << input->readULong(4) << std::dec << ",";
  f << "ptr2?=" << std::hex << input->readULong(4) << std::dec << ",";
  long val;
  for (int i = 0; i < 3; i++) { // always 3, 80, 0 ?
    val = input->readLong(2);
    if (val)  f << "f" << i << "=" << val << ",";
  }
  auto N = static_cast<int>(input->readLong(2));
  f << "N?=" << N << ",";
  for (int i = 4; i < 6; i++) { // alway 0 ?
    val = input->readLong(2);
    if (val) f << "f" << i << "=" << val << ",";
  }
  ascii().addPos(entry.begin());
  ascii().addNote(f.str().c_str());

  if (entry.length() != 24+12*N) {
    MWAW_DEBUG_MSG(("WriteNowParser::parseGraphicZone: zone size is invalid(II)\n"));
    return false;
  }
  for (int i = 0; i < N; i++) {
    long pos = input->tell();
    WriteNowEntry zone = readEntry();
    f.str("");
    if (i < 8)
      f << "GraphicZoneA-" << i << ":";
    else
      f << "GraphicZone-" << i-8 << ":";

    zone.setId((i < 8) ? i : i-8);
    if (zone.isZone()) {
      if (i == 0)
        zone.setType("PrintZone");
      else if (i < 8) {
        std::stringstream s;
        s << "GraphicUnkn" << i;
        zone.setType(s.str());
      }
      else
        zone.setType("GraphicData");
      if (i < 8)
        m_entryManager->add(zone);
    }
    else if (zone.m_val[2]==-1 && zone.m_val[3]==0x76543210L) {
      zone.m_val[2]= zone.m_val[3]=0;
      f << "*";
    }
    if (i >= 8)
      m_state->m_picturesList.push_back(zone);
    f << zone;

    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }

  entry.setParsed(true);
  ascii().addPos(entry.end());
  ascii().addNote("_");
  return true;
}

////////////////////////////////////////////////////////////
// try to read a graphic zone
////////////////////////////////////////////////////////////
bool WriteNowParser::sendPicture(WriteNowEntry const &entry, MWAWBox2i const &bdbox)
{
  MWAWInputStreamPtr input = getInput();

  if (!entry.valid() || entry.length() < 24) {
    MWAW_DEBUG_MSG(("WriteNowParser::sendPicture: zone size is invalid\n"));
    return false;
  }

  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  if (input->readLong(4) != entry.length()) {
    MWAW_DEBUG_MSG(("WriteNowParser::sendPicture: bad begin of last zone\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(GraphicData):";
  f << "ptr?=" << std::hex << input->readULong(4) << std::dec << ",";
  f << "ptr2?=" << std::hex << input->readULong(4) << std::dec << ",";
  auto type = static_cast<int>(input->readLong(2));
  if (type != 14) f << "#type=" << type << ",";
  long val;
  /* fl0 : 0[pict1] or 1[pict2] : graphic type ?, fl1 : always 0
   */
  for (int i = 0; i < 2; i++) {
    val = input->readLong(1);
    if (val) f << "fl" << i << "=" << val << ",";
  }

  f << "ptr3?=" << std::hex << input->readULong(4) << std::dec << ",";
  for (int i = 0; i < 2; i++) { // alway 0 ?
    val = input->readLong(2);
    if (val) f << "f" << i << "=" << val << ",";
  }

  ascii().addPos(entry.begin());
  ascii().addNote(f.str().c_str());

  int sz = static_cast<int>(entry.length())-24;
  if (sz) {
    long pos = input->tell();
    std::shared_ptr<MWAWPict> pict(MWAWPictData::get(input, sz));
    if (!pict) {
      MWAW_DEBUG_MSG(("WriteNowParser::sendPicture: can not read the picture\n"));
      ascii().addDelimiter(pos, '|');
    }
    else {
      if (getTextListener()) {
        MWAWPosition pictPos;
        if (bdbox.size().x() > 0 && bdbox.size().y() > 0) {
          pictPos=MWAWPosition(MWAWVec2f(0,0),MWAWVec2f(bdbox.size()), librevenge::RVNG_POINT);
          pictPos.setNaturalSize(pict->getBdBox().size());
        }
        else
          pictPos=MWAWPosition(MWAWVec2f(0,0),pict->getBdBox().size(), librevenge::RVNG_POINT);
        pictPos.setRelativePosition(MWAWPosition::Char);

        MWAWEmbeddedObject picture;
        if (pict->getBinary(picture))
          getTextListener()->insertPicture(pictPos, picture);
      }

#ifdef DEBUG_WITH_FILES
      if (!entry.isParsed()) {
        ascii().skipZone(pos, entry.end()-1);
        librevenge::RVNGBinaryData file;
        input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
        input->readDataBlock(entry.length(), file);
        static int volatile pictName = 0;
        libmwaw::DebugStream f2;
        f2 << "PICT-" << ++pictName << ".pct";
        libmwaw::Debug::dumpFile(file, f2.str().c_str());
      }
#endif
    }
  }

  entry.setParsed(true);
  ascii().addPos(entry.end());
  ascii().addNote("_");

  return true;
}


////////////////////////////////////////////////////////////
// try to read the color map zone
////////////////////////////////////////////////////////////
bool WriteNowParser::readColorMap(WriteNowEntry const &entry)
{
  m_state->m_colorMap.resize(0);

  MWAWInputStreamPtr input = getInput();

  if (!entry.valid() || entry.length() < 0x10) {
    MWAW_DEBUG_MSG(("WriteNowParser::readColorMap: zone size is invalid\n"));
    return false;
  }

  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  if (input->readLong(4) != entry.length()) {
    MWAW_DEBUG_MSG(("WriteNowParser::readColorMap: bad begin of last zone\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(ColorMap):";
  f << "ptr?=" << std::hex << input->readULong(4) << std::dec << ",";
  f << "ptr2?=" << std::hex << input->readULong(4) << std::dec << ",";
  long pos, val;
  for (int i = 0; i < 3; i++) { // always 4, 0, 0 ?
    val = input->readLong(2);
    if (val) f << "f" << i << "=" << val << ",";
  }
  auto N=static_cast<int>(input->readULong(2));
  f << "N=" << N << ",";
  for (int i = 0; i < 2; i++) { // 0
    val = static_cast<int>(input->readLong(2));
    if (val) f << "g" << i << "=" << val << ",";
  }
  if (long(input->tell())+N*8 > entry.end()) {
    MWAW_DEBUG_MSG(("WriteNowParser::readColorMap: the zone is too short\n"));
    return false;
  }
  ascii().addPos(entry.begin());
  ascii().addNote(f.str().c_str());
  std::vector<long> defPos;
  for (int n = 0; n < N; n++) {
    pos = input->tell();
    f.str("");
    f << "ColorMap[" << n << "]:";
    auto type = static_cast<int>(input->readLong(1));
    switch (type) {
    case 1:
      f << "named(RGB),";
      break;
    case 2:
      f << "unamed,";
      break; // check me : are the color in RGB ?
    case 3:
      f << "unamed(RGB),";
      break;
    default:
      MWAW_DEBUG_MSG(("WriteNowParser::readColorMap: find unknown type %d\n", type));
      f << "#type=" << type << ",";
      break;
    }
    for (int i = 0; i < 3; i++) { // always 0
      val = input->readLong(1);
      if (val) f << "f" << i << "=" << std::hex << val << std::dec << ",";
    }
    val = long(input->readULong(4));
    defPos.push_back(pos+val);
    // fixme: used this to read the data...
    f << "defPos=" << std::hex << pos+val << std::dec << ",";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }

  for (int n = 0; n < N; n++) {
    pos = defPos[size_t(n)];
    if (pos+12 > entry.end()) {
      MWAW_DEBUG_MSG(("WriteNowParser::readColorMap: can not read entry : %d\n", n));
      return false;
    }

    input->seek(pos, librevenge::RVNG_SEEK_SET);
    f.str("");
    f << "ColorMapData[" << n << "]:";
    unsigned char col[4];
    for (auto &c : col) c = static_cast<unsigned char>(input->readULong(2)/256);
    f << "col=" << MWAWColor(col[0],col[1],col[2],col[3]) << ",";
    m_state->m_colorMap.push_back(MWAWColor(static_cast<unsigned char>(col[0]),static_cast<unsigned char>(col[1]),static_cast<unsigned char>(col[2])));

    auto sz = static_cast<int>(input->readULong(1));
    if (pos+8+1+sz > entry.end()) {
      MWAW_DEBUG_MSG(("WriteNowParser::readColorMap: can not read end of entry : %d\n", n));
      return false;
    }
    std::string name("");
    for (int i = 0; i < sz; i++) name += char(input->readULong(1));

    if (name.length()) f << name;
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }

  entry.setParsed(true);
  ascii().addPos(entry.end());
  ascii().addNote("_");
  return true;
}

////////////////////////////////////////////////////////////
// try to read the print info zone
////////////////////////////////////////////////////////////
bool WriteNowParser::readPrintInfo(WriteNowEntry const &entry)
{
  MWAWInputStreamPtr input = getInput();
  int expectedLength = version() <= 2 ? 0x78+2 : 0x88;
  if (!entry.valid() || entry.length() < expectedLength) {
    MWAW_DEBUG_MSG(("WriteNowParser::readPrintInfo: zone size is invalid\n"));
    return false;
  }

  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  long sz = version() <= 2 ? 2+long(input->readULong(2)) : long(input->readULong(4));
  if (sz != entry.length()) {
    MWAW_DEBUG_MSG(("WriteNowParser::readPrintInfo: bad begin of last zone\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(PrintInfo):";
  if (version()>=3) {
    f << "ptr?=" << std::hex << input->readULong(4) << std::dec << ",";
    f << "ptr2?=" << std::hex << input->readULong(4) << std::dec << ",";
    long val;
    for (int i = 0; i < 4; i++) { // 15, 0, ??, ???
      val = input->readLong(2);
      if (val) f << "f" << i << "=" << std::hex << val << std::dec << ",";
    }
    for (int i = 0; i < 2; i++) { // 0
      val = input->readLong(2);
      if (val) f << "g" << i << "=" << val << ",";
    }
  }
  libmwaw::PrinterInfo info;
  if (!info.read(input)) {
    MWAW_DEBUG_MSG(("WriteNowParser::readPrintInfo: can not read print info\n"));
    return false;
  }
  f << info;

  MWAWVec2i paperSize = info.paper().size();
  MWAWVec2i pageSize = info.page().size();
  if (pageSize.x() <= 0 || pageSize.y() <= 0 ||
      paperSize.x() <= 0 || paperSize.y() <= 0) return false;

  // define margin from print info
  MWAWVec2i lTopMargin= -1 * info.paper().pos(0);
  MWAWVec2i rBotMargin=info.paper().size() - info.page().size();

  // move margin left | top
  int decalX = lTopMargin.x() > 14 ? lTopMargin.x()-14 : 0;
  int decalY = lTopMargin.y() > 14 ? lTopMargin.y()-14 : 0;
  lTopMargin -= MWAWVec2i(decalX, decalY);
  rBotMargin += MWAWVec2i(decalX, decalY);

  // decrease right | bottom
  int rightMarg = rBotMargin.x() -50;
  if (rightMarg < 0) rightMarg=0;
  int botMarg = rBotMargin.y() -50;
  if (botMarg < 0) botMarg=0;

  getPageSpan().setMarginTop(lTopMargin.y()/72.0);
  getPageSpan().setMarginBottom(botMarg/72.0);
  getPageSpan().setMarginLeft(lTopMargin.x()/72.0);
  getPageSpan().setMarginRight(rightMarg/72.0);
  getPageSpan().setFormLength(paperSize.y()/72.);
  getPageSpan().setFormWidth(paperSize.x()/72.);

  entry.setParsed(true);
  ascii().addPos(entry.begin());
  ascii().addNote(f.str().c_str());
  ascii().addPos(entry.end());
  ascii().addNote("_");
  return true;
}

////////////////////////////////////////////////////////////
// try to read the last generic zone ( always find N=0 :-~)
////////////////////////////////////////////////////////////
bool WriteNowParser::readGenericUnkn(WriteNowEntry const &entry)
{
  MWAWInputStreamPtr input = getInput();

  if (!entry.valid() || entry.length() < 0x10) {
    MWAW_DEBUG_MSG(("WriteNowParser::readGenericUnkn: zone size is invalid\n"));
    return false;
  }

  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  if (input->readLong(4) != entry.length()) {
    MWAW_DEBUG_MSG(("WriteNowParser::readGenericUnkn: bad begin of last zone\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(" << entry.type() << "):";
  f << "ptr?=" << std::hex << input->readULong(4) << std::dec << ",";
  f << "ptr2?=" << std::hex << input->readULong(4) << std::dec << ",";
  long pos, val;
  for (int i = 0; i < 3; i++) { // 7, 0, 0
    val = input->readLong(2);
    if (val) f << "f" << i << "=" << std::hex << val << std::dec << ",";
  }
  auto N=static_cast<int>(input->readULong(2));
  f << "N=" << N << ",";
  for (int i = 0; i < 2; i++) { // 0
    val = input->readLong(2);
    if (val) f << "g" << i << "=" << val << ",";
  }
  if (long(input->tell())+N*8 > entry.end()) {
    MWAW_DEBUG_MSG(("WriteNowParser::readGenericUnkn: the zone is too short\n"));
    return false;
  }
  ascii().addPos(entry.begin());
  ascii().addNote(f.str().c_str());
  std::vector<long> defPos;
  for (int n = 0; n < N; n++) {
    pos = input->tell();
    f.str("");
    f << entry.type() << "[" << n << "]:";
    auto type = static_cast<int>(input->readULong(1));
    switch (type) {
    case 0:
      f << "def,";
      break;
    default:
      MWAW_DEBUG_MSG(("WriteNowParser::readGenericUnkn: find unknown type %d\n", type));
      f << "#type=" << type << ",";
      break;
    }
    for (int i = 0; i < 3; i++) { // always 0
      val = input->readLong(1);
      if (val) f << "f" << i << "=" << std::hex << val << std::dec << ",";
    }
    val = long(input->readULong(4));
    defPos.push_back(pos+val);
    // fixme: used this to read the data...
    f << "defPos=" << std::hex << pos+val << std::dec << ",";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }

  for (int n = 0; n < N; n++) {
    pos = defPos[size_t(n)];
    if (pos == entry.end()) continue;
    if (pos+12 > entry.end()) {
      MWAW_DEBUG_MSG(("WriteNowParser::readGenericUnkn: can not read entry : %d\n", n));
      return false;
    }

    input->seek(pos, librevenge::RVNG_SEEK_SET);
    f.str("");
    f << entry.type() << "Data[" << n << "]:";

    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }

  entry.setParsed(true);
  ascii().addPos(entry.end());
  ascii().addNote("_");
  return true;
}

bool WriteNowParser::checkIfPositionValid(long pos)
{
  if (pos <= m_state->m_endPos)
    return true;
  MWAWInputStreamPtr input = getInput();
  long actPos = input->tell();
  input->seek(pos, librevenge::RVNG_SEEK_SET);
  bool ok = long(input->tell())==pos;
  if (ok) m_state->m_endPos = pos;

  input->seek(actPos, librevenge::RVNG_SEEK_SET);
  return ok;
}

WriteNowEntry WriteNowParser::readEntry()
{
  WriteNowEntry res;
  MWAWInputStreamPtr input = getInput();
  auto val = static_cast<int>(input->readULong(2));
  res.m_fileType = (val >> 12);
  res.m_val[0]= val & 0x0FFF;
  res.m_val[1]= static_cast<int>(input->readLong(2));
  if (res.isZoneType()) {
    res.setBegin(long(input->readULong(4)));
    res.setLength(long(input->readULong(4)));
    if (!checkIfPositionValid(res.end())) {
      MWAW_DEBUG_MSG(("WriteNowParser::readEntry: find an invalid entry\n"));
      res.setLength(0);
    }
  }
  else {
    res.m_val[2]= static_cast<int>(input->readLong(4));
    res.m_val[3]= static_cast<int>(input->readLong(4));
  }
  return res;
}

////////////////////////////////////////////////////////////
// read the header
////////////////////////////////////////////////////////////
bool WriteNowParser::checkHeader(MWAWHeader *header, bool strict)
{
  *m_state = WriteNowParserInternal::State();

  MWAWInputStreamPtr input = getInput();
  if (!input || !input->hasDataFork())
    return false;

  libmwaw::DebugStream f;
  int headerSize=28;
  input->seek(headerSize,librevenge::RVNG_SEEK_SET);
  if (int(input->tell()) != headerSize) {
    MWAW_DEBUG_MSG(("WriteNowParser::checkHeader: file is too short\n"));
    return false;
  }
  input->seek(0, librevenge::RVNG_SEEK_SET);
  auto val = long(input->readULong(4));
  int vers = 0;
  switch (val) {
  case 0:
    if (input->readULong(4) != 0)
      return false;
    vers = 2;
    break;
  case 0x57726974: // Writ
    if (input->readULong(4) != 0x654e6f77) // eNow
      return false;
    vers = 3;
    break;
  default:
    return false;
  }
  setVersion(vers);
  f << "FileHeader:";

  if (vers < 3) {
    if (strict) {
      for (int i=0; i < 4; ++i) {
        val = long(input->readLong(1));
        if (val!=4 && val!=0x44) return false;
        input->seek(3, librevenge::RVNG_SEEK_CUR);
      }
      input->seek(8, librevenge::RVNG_SEEK_SET);
    }

    ascii().addPos(0);
    ascii().addNote(f.str().c_str());
    ascii().addPos(input->tell());

    return true;
  }

  val = long(input->readULong(2));
  if (strict && val > 3)
    return false;
#ifndef DEBUG
  if (val != 2) return false;
#endif
  f << "f0=" << val << ",";

  for (int i = 1; i < 4; i++) {
    // all zero, excepted f1=1 in one file...
    val = input->readLong(2);
    if (val) f << "f" << i << "=" << val << ",";
  }

  // a flag ??
  val = long(input->readULong(2));
  if (val != 0x4000) f << "fl=" << std::hex << val << std::dec << ",";
  val = long(input->readLong(2));
  if (val) f << "f4=" << val << ",";

  // last entry
  WriteNowEntry entry;
  entry.setBegin(long(input->readULong(4)));
  entry.setLength(long(input->readULong(4)));
  entry.m_fileType = 4;

  if (!checkIfPositionValid(entry.end())) {
    MWAW_DEBUG_MSG(("WriteNowParser::checkHeader: can not find final zone\n"));
    return false;
  }
  entry.setType("DocEntries");
  m_entryManager->add(entry);

  f << "entry=" << std::hex << entry.begin() << ":" << entry.end() << std::dec << ",";

  // ok, we can finish initialization
  if (header)
    header->reset(MWAWDocument::MWAW_T_WRITENOW, version());

  //
  input->seek(headerSize, librevenge::RVNG_SEEK_SET);

  ascii().addPos(0);
  ascii().addNote(f.str().c_str());
  ascii().addPos(headerSize);

  return true;
}

// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab:
