/* -*- 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 <vector>

#include <librevenge/librevenge.h>

#include "MWAWTextListener.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWPosition.hxx"
#include "MWAWPrinter.hxx"
#include "MWAWSection.hxx"
#include "MWAWSubDocument.hxx"

#include "MsWksGraph.hxx"
#include "MsWksDocument.hxx"

#include "MsWks4Text.hxx"

#include "MsWks4Zone.hxx"


/** Internal: the structures of a MsWks4Zone */
namespace MsWks4ZoneInternal
{
/**Internal: some data are dupplicated, an enum to know which picture to ignored.
 *
 * For intance, the header text is stored in a separate ole, and we find
 * in the main ole, a picture which represents the headers */
enum { IgnoreFrame = -4 };

//! Internal: a frame ( position, type, ...)
struct Frame {
  //! the type of the frame which can represent header, footer, textbox, ...
  enum Type { Unknown = 0, Header, Footer, Table, Object, Textbox };

  //! constructor
  Frame()
    : m_type(Unknown)
    , m_position()
    , m_pictId()
    , m_error("")
  {
    m_position.setPage(-3);
  }
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Frame const &ft);

  //! the frame type
  Type m_type;

  //! the position of the frame in the document
  MWAWPosition m_position;
  //! some frames are associated with a picture stored in this entry ( name + id)
  MWAWEntry m_pictId;
  //! a string to store unparsed data
  std::string m_error;
};
//! friend operator<< for frame
std::ostream &operator<<(std::ostream &o, Frame const &ft)
{
  switch (ft.m_type) {
  case Frame::Header:
    o<< "header,";
    break;
  case Frame::Footer:
    o<< "footer,";
    break;
  case Frame::Table:
    o<< "table,";
    break;
  case Frame::Textbox:
    o<< "textbox,";
    break;
  case Frame::Object:
    o<< "object,";
    break;
  case Frame::Unknown:
#if !defined(__clang__)
  default:
#endif
    break;
  }

  int page = ft.m_position.page();
  switch (page) {
  case -1:
    o << "allpages,";
    break;
  case -2:
    o << "undef,";
    break;
  case -3:
    o << "def,";
    break;
  default:
    if (page <= 0) o << "###page=" << page << ",";
    break;
  }
  if (ft.m_pictId.name().length())
    o << "pict='" << ft.m_pictId.name() << "':" << ft.m_pictId.id() << ",";

  o << ft.m_position;
  if (!ft.m_error.empty()) o << "errors=(" << ft.m_error << ")";
  return o;
}

//!Internal: the state of a MsWks4Zone
struct State {
  //! constructor
  State()
    : m_mainOle(false)
    , m_numColumns(1)
    , m_hasColumnSep(false)
    , m_parsed(false)
    , m_actPage(0)
    , m_numPages(0)
    , m_defFont(20,12)
    , m_framesList()
  {
  }

  //! true if we parse the main MN0
  bool m_mainOle;

  //! the number of column
  int m_numColumns;
  //! true if a line is added to separated the column
  bool m_hasColumnSep;
  //! a flag to known if the ole is already parsed
  bool m_parsed;

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

  //! the default font
  MWAWFont m_defFont;

  //! the list of frames
  std::vector<Frame> m_framesList;

};
}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
MsWks4Zone::MsWks4Zone(MWAWInputStreamPtr const &input, MWAWParserStatePtr const &parserState,
                       MWAWParser &parser, std::string const &oleName)
  : m_mainParser(&parser)
  , m_parserState(parserState)
  , m_state()
  , m_document()
{
  m_document.reset(new MsWksDocument(input, parser));
  setAscii(oleName);
  m_parserState->m_version=4;
  init();
}

MsWks4Zone::~MsWks4Zone()
{
}

////////////////////////////////////////////////////////////
// small helper to manage interaction between parent and child, ...
////////////////////////////////////////////////////////////
void MsWks4Zone::init()
{
  m_state.reset(new MsWks4ZoneInternal::State);
  m_document->getTextParser4()->setDefault(m_state->m_defFont);
}

MWAWInputStreamPtr MsWks4Zone::getInput()
{
  return m_document->m_input;
}

void MsWks4Zone::setAscii(std::string const &oleName)
{
  std::string fName = libmwaw::Debug::flattenFileName(oleName);
  m_document->initAsciiFile(fName);
}

libmwaw::DebugFile &MsWks4Zone::ascii()
{
  return m_document->ascii();
}

void MsWks4Zone::readFootNote(int id)
{
  m_document->getTextParser4()->readFootNote(m_document->getInput(), id);
}

////////////////////////////////////////////////////////////
// position and height
////////////////////////////////////////////////////////////
double MsWks4Zone::getTextHeight() const
{
  return m_parserState->m_pageSpan.getPageLength();
}

////////////////////////////////////////////////////////////
// text positions
////////////////////////////////////////////////////////////
void MsWks4Zone::newPage(int number, bool /*soft*/)
{
  if (number <= m_state->m_actPage || number > m_state->m_numPages)
    return;

  long pos = m_document->getInput()->tell();
  while (m_state->m_actPage < number) {
    m_state->m_actPage++;
    if (!m_parserState->getMainListener() || m_state->m_actPage == 1)
      continue;
    // FIXME: find a way to force the page break to happen
    //    ie. graphParser must add a space to force it :-~
    if (m_state->m_mainOle) m_parserState->getMainListener()->insertBreak(MWAWTextListener::PageBreak);

    MsWksGraph::SendData sendData;
    sendData.m_type = MsWksGraph::SendData::RBDR;
    sendData.m_anchor =  MWAWPosition::Page;
    sendData.m_page = m_state->m_actPage;
    m_document->getGraphParser()->sendObjects(sendData);
  }
  m_document->getInput()->seek(pos, librevenge::RVNG_SEEK_SET);
}

MWAWEntry MsWks4Zone::getTextPosition() const
{
  return m_document->getTextParser4()->m_textPositions;
}

////////////////////////////////////////////////////////////
// create the main listener ( given a header and a footer document)
////////////////////////////////////////////////////////////
MWAWTextListenerPtr MsWks4Zone::createListener(librevenge::RVNGTextInterface *interface)
{
  std::vector<MWAWPageSpan> pageList;
  m_state->m_actPage = 0;
  m_document->getPageSpanList(pageList, m_state->m_numPages);
  MWAWTextListenerPtr res(new MWAWTextListener(*m_parserState, pageList, interface));

  // ok, now we can update the page position
  std::vector<int> linesH, pagesH;
  pagesH.resize(size_t(m_state->m_numPages), int(72.*getTextHeight()));
  m_document->getGraphParser()->computePositions(-1, linesH, pagesH);
  m_document->getGraphParser()->setPageLeftTop
  (MWAWVec2f(72.f*float(m_parserState->m_pageSpan.getMarginLeft()),
             72.f*float(m_parserState->m_pageSpan.getMarginTop())));

  return res;
}

////////////////////////////////////////////////////////////
//
// entry management
//
////////////////////////////////////////////////////////////
bool MsWks4Zone::parseHeaderIndexEntry(MWAWInputStreamPtr &input)
{
  long pos = input->tell();
  m_document->ascii().addPos(pos);

  libmwaw::DebugStream f;

  auto cch = uint16_t(input->readULong(2));

  // check if the entry can be read
  input->seek(pos + cch, librevenge::RVNG_SEEK_SET);
  if (input->tell() != pos+cch) {
    MWAW_DEBUG_MSG(("MsWks4Zone:parseHeaderIndexEntry error: incomplete entry\n"));
    m_document->ascii().addNote("###IndexEntry incomplete (ignored)");
    return false;
  }
  input->seek(pos + 2, librevenge::RVNG_SEEK_SET);

  if (0x18 != cch) {
    if (cch < 0x18) {
      input->seek(pos + cch, librevenge::RVNG_SEEK_SET);
      m_document->ascii().addNote("MsWks4Zone:parseHeaderIndexEntry: ###IndexEntry too short(ignored)");
      if (cch < 10) throw libmwaw::ParseException();
      return true;
    }
  }

  std::string name;

  // sanity check
  for (size_t i =0; i < 4; i++) {
    name.append(1, char(input->readULong(1)));

    if (name[i] != 0 && name[i] != 0x20 &&
        (41 > static_cast<uint8_t>(name[i]) || static_cast<uint8_t>(name[i]) > 90)) {
      MWAW_DEBUG_MSG(("MsWks4Zone:parseHeaderIndexEntry: bad character=%u (0x%02x) in name in header index\n",
                      static_cast<unsigned int>(name[i]), static_cast<unsigned int>(name[i])));
      m_document->ascii().addNote("###IndexEntry bad name(ignored)");

      input->seek(pos + cch, librevenge::RVNG_SEEK_SET);
      return true;
    }
  }

  f << "Entries("<<name<<")";
  if (cch != 24) f << ", ###size=" << int(cch);
  auto id = static_cast<int>(input->readULong(2));
  f << ", id=" << id << ", (";
  for (int i = 0; i < 2; i ++) {
    auto val = static_cast<int>(input->readLong(2));
    f << val << ",";
  }

  std::string name2;
  for (size_t i =0; i < 4; i++)
    name2.append(1, char(input->readULong(1)));
  f << "), " << name2;

  MWAWEntry hie;
  hie.setName(name);
  hie.setType(name2);
  hie.setId(id);
  hie.setBegin(long(input->readULong(4)));
  hie.setLength(long(input->readULong(4)));

  f << ", offset=" << std::hex << hie.begin() << ", length=" << hie.length();

  if (cch != 0x18) {
    m_document->ascii().addDelimiter(pos+0x18, '|');
    f << ",#extraData";
  }

  input->seek(hie.end(), librevenge::RVNG_SEEK_SET);
  if (input->tell() != hie.end()) {
    f << ", ###ignored";
    m_document->ascii().addNote(f.str().c_str());
    input->seek(pos + cch, librevenge::RVNG_SEEK_SET);
    return true;
  }


  m_document->getEntryMap().insert(std::multimap<std::string, MWAWEntry>::value_type(name, hie));

  m_document->ascii().addPos(pos);
  m_document->ascii().addNote(f.str().c_str());

  m_document->ascii().addPos(hie.begin());
  f.str("");
  f << name;
  if (name != name2) f << "/" << name2;
  f << ":" << std::dec << id;
  m_document->ascii().addNote(f.str().c_str());

  m_document->ascii().addPos(hie.end());
  m_document->ascii().addNote("_");

  input->seek(pos + cch, librevenge::RVNG_SEEK_SET);
  return true;
}

bool MsWks4Zone::parseHeaderIndex(MWAWInputStreamPtr &input)
{
  m_document->getEntryMap().clear();
  input->seek(0x08, librevenge::RVNG_SEEK_SET);

  long pos = input->tell();
  auto i0 = static_cast<int>(input->readLong(2));
  auto i1 = static_cast<int>(input->readLong(2));
  auto n_entries = static_cast<uint16_t>(input->readULong(2));
  // fixme: sanity check n_entries

  libmwaw::DebugStream f;
  f << "Header: N=" << n_entries << ", " << i0 << ", " << i1 << "(";

  for (int i = 0; i < 4; i++)
    f << std::hex << input->readLong(2) << ",";
  f << "), ";
  f << "end=" << std::hex << input->readLong(2);

  m_document->ascii().addPos(pos);
  m_document->ascii().addNote(f.str().c_str());

  input->seek(0x18, librevenge::RVNG_SEEK_SET);
  bool readSome = false;
  do {
    if (input->isEnd()) return readSome;

    pos = input->tell();
    f.str("");
    auto unknown1 = static_cast<uint16_t>(input->readULong(2)); // function of the size of the entries ?

    auto n_entries_local = static_cast<uint16_t>(input->readULong(2));
    f << "Header("<<std::hex << unknown1<<"): N=" << std::dec << n_entries_local;

    if (n_entries_local > 0x20) {
      MWAW_DEBUG_MSG(("MsWks4Zone::parseHeaderIndex: error: n_entries_local=%i\n", n_entries_local));
      return readSome;
    }

    auto next_index_table = static_cast<uint32_t>(input->readULong(4));
    f << std::hex << ", nextHeader=" << next_index_table;
    if (next_index_table != 0xFFFFFFFF && long(next_index_table) < pos) {
      MWAW_DEBUG_MSG(("MsWks4Zone::parseHeaderIndex: error: next_index_table=%x decreasing !!!\n", next_index_table));
      return readSome;
    }

    m_document->ascii().addPos(pos);
    m_document->ascii().addNote(f.str().c_str());

    do {
      if (!parseHeaderIndexEntry(input)) return readSome;

      readSome=true;
      n_entries--;
      n_entries_local--;
    }
    while (n_entries > 0 && n_entries_local);

    if (0xFFFFFFFF == next_index_table && n_entries > 0) {
      MWAW_DEBUG_MSG(("MsWks4Zone::parseHeaderIndex: error: expected more header index entries\n"));
      return n_entries > 0;
    }

    if (0xFFFFFFFF == next_index_table) break;
    if (long(next_index_table) < input->tell()) {
      MWAW_DEBUG_MSG(("MsWks4Zone::parseHeaderIndex: error: next_index_table=%x decreasing !!!\n", next_index_table));
      return readSome;
    }

    if (input->seek(long(next_index_table), librevenge::RVNG_SEEK_SET) != 0) return readSome;
  }
  while (n_entries > 0);

  return true;
}

////////////////////////////////////////////////////////////
// parse the ole part and create the main part
////////////////////////////////////////////////////////////
bool MsWks4Zone::createZones(bool mainOle)
{
  if (m_state->m_parsed) return true;

  auto &entryMap=m_document->getEntryMap();
  MWAWInputStreamPtr input = m_document->getInput();
  m_state->m_parsed = true;

  entryMap.clear();

  m_document->ascii().addPos(0);
  m_document->ascii().addNote("FileHeader");
  /* header index */
  if (!parseHeaderIndex(input)) return false;
  // the text structure
  if (!m_document->getTextParser4()->readStructures(input, mainOle)) return !mainOle;

  // DOP, PRNT
  auto pos = entryMap.find("PRNT");
  if (entryMap.end() != pos) {
    pos->second.setParsed(true);
    MWAWPageSpan page;
    if (readPRNT(input, pos->second, page) && mainOle) m_parserState->m_pageSpan = page;
  }
  pos = entryMap.find("DOP ");
  if (entryMap.end() != pos) {
    MWAWPageSpan page;
    if (readDOP(input, pos->second, page) && mainOle) m_parserState->m_pageSpan = page;
  }

  // RLRB
  pos = entryMap.lower_bound("RLRB");
  while (pos != entryMap.end()) {
    MWAWEntry const &entry = pos++->second;
    if (!entry.hasName("RLRB")) break;
    if (!entry.hasType("RLRB")) continue;

    readRLRB(input, entry);
  }

  // SELN
  pos = entryMap.lower_bound("SELN");
  while (entryMap.end() != pos) {
    MWAWEntry const &entry = pos++->second;
    if (!entry.hasName("SELN")) break;
    if (!entry.hasType("SELN")) continue;
    readSELN(input, entry);
  }

  // FRAM
  m_state->m_framesList.resize(0);
  pos = entryMap.lower_bound("FRAM");
  while (entryMap.end() != pos) {
    MWAWEntry const &entry = pos++->second;
    if (!entry.hasName("FRAM")) break;
    if (!entry.hasType("FRAM")) continue;
    readFRAM(input, entry);
  }

  /* Graph data */
  pos = entryMap.lower_bound("RBDR");
  while (pos != entryMap.end()) {
    MWAWEntry const &entry = pos++->second;
    if (!entry.hasName("RBDR")) break;
    if (!entry.hasType("RBDR")) continue;

    if (m_document->getGraphParser()->readRB(input, entry, 0))
      entry.setParsed(true);
  }
  pos = entryMap.lower_bound("RBIL");
  while (pos != entryMap.end()) {
    MWAWEntry const &entry = pos++->second;
    if (!entry.hasName("RBIL")) break;
    if (!entry.hasType("RBIL")) continue;

    if (m_document->getGraphParser()->readRB(input, entry, 0))
      entry.setParsed(true);
  }

  /* read the pictures */
  // in the main block, pict are used to representant the
  // header/footer, so we skip it.
  // In the others block, maybe there can be interesting, so, we read them
  pos = entryMap.lower_bound("PICT");
  while (pos != entryMap.end()) {
    MWAWEntry const &entry = pos++->second;
    if (!entry.hasName("PICT")) break;
    m_document->getGraphParser()->readPictureV4(input, entry);
  }
  return true;
}

////////////////////////////////////////////////////////////
// send all the data corresponding to a zone
////////////////////////////////////////////////////////////
void MsWks4Zone::readContentZones(MWAWEntry const &entry, bool mainOle)
{
  MWAWInputStreamPtr input = m_document->getInput();
  bool oldMain = m_state->m_mainOle;
  m_state->m_mainOle = mainOle;

  MsWksGraph::SendData sendData;
  sendData.m_type = MsWksGraph::SendData::RBDR;
  sendData.m_anchor = mainOle ? MWAWPosition::Page : MWAWPosition::Paragraph;
  sendData.m_page = 0;
  m_document->getGraphParser()->sendObjects(sendData);

  if (mainOle && m_parserState->getMainListener() && m_state->m_numColumns > 1) {
    if (m_parserState->getMainListener()->isSectionOpened())
      m_parserState->getMainListener()->closeSection();
    MWAWSection sec;
    sec.setColumns(m_state->m_numColumns, m_mainParser->getPageWidth()/double(m_state->m_numColumns), librevenge::RVNG_INCH);
    if (m_state->m_hasColumnSep)
      sec.m_columnSeparator=MWAWBorder();
    m_parserState->getMainListener()->openSection(sec);
  }

  MWAWEntry ent(entry);
  if (!ent.valid()) ent = m_document->getTextParser4()->m_textPositions;
  m_document->getTextParser4()->readText(input, ent, mainOle);

  if (!mainOle) {
    m_state->m_mainOle = oldMain;
    return;
  }

  // send the final data
#ifdef DEBUG
  newPage(m_state->m_numPages);
  m_document->getTextParser4()->flushExtra(input);

  sendData.m_type = MsWksGraph::SendData::ALL;
  sendData.m_anchor = MWAWPosition::Char;
  m_document->getGraphParser()->sendObjects(sendData);
#endif
  m_state->m_mainOle = oldMain;

#ifdef DEBUG
  // check if we have parsed all zones
  auto const &entryMap=m_document->getEntryMap();

  for (auto it : entryMap) {
    MWAWEntry const &zone = it.second;
    if (zone.isParsed() ||
        zone.hasName("TEXT") || // TEXT entries are managed directly by MsWks4Text
        zone.hasName("INK ")) // INK Zone are ignored: always = 2*99
      continue;
    MWAW_DEBUG_MSG(("MsWks4Zone::readContentZones: WARNING zone %s ignored\n",
                    zone.name().c_str()));
  }
#endif
}

////////////////////////////////////////////////////////////
//
// low level
//
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// the printer definition
////////////////////////////////////////////////////////////
bool MsWks4Zone::readPRNT(MWAWInputStreamPtr input, MWAWEntry const &entry, MWAWPageSpan &page)
{
  page = MWAWPageSpan();
  if (!entry.hasType("PRR ")) {
    MWAW_DEBUG_MSG(("Works: unknown PRNT type='%s'\n", entry.type().c_str()));
    return false;
  }

  long debPos = entry.begin();
  input->seek(debPos, librevenge::RVNG_SEEK_SET);
  libmwaw::PrinterInfo info;
  if (!info.read(input)) {
    MWAW_DEBUG_MSG(("Works: error: can not read PRNT\n"));
    return false;
  }
  else {
    MWAWVec2i paperSize = info.paper().size();
    MWAWVec2i pageSize = info.page().size();
    MWAWVec2i margin = paperSize - pageSize;
    margin.set(margin.x()/2, margin.y()/2);

    page.setMarginTop(margin.y()/72.0);
    page.setMarginBottom(margin.y()/72.0);
    page.setMarginLeft(margin.x()/72.0);
    page.setMarginRight(margin.x()/72.0);
    page.setFormLength(pageSize.y()/72.);
    page.setFormWidth(pageSize.x()/72.);

    if (paperSize.y() > paperSize.x())
      page.setFormOrientation(MWAWPageSpan::PORTRAIT);
    else
      page.setFormOrientation(MWAWPageSpan::LANDSCAPE);

    libmwaw::DebugStream f;
    f << info;

    m_document->ascii().addPos(debPos);
    m_document->ascii().addNote(f.str().c_str());
  }
  return true;
}

////////////////////////////////////////////////////////////
// document
////////////////////////////////////////////////////////////
bool MsWks4Zone::readDOP(MWAWInputStreamPtr input, MWAWEntry const &entry, MWAWPageSpan &page)
{
  if (!entry.hasType("DOP ")) {
    MWAW_DEBUG_MSG(("Works: unknown DOP type='%s'\n", entry.type().c_str()));
    return false;
  }
  entry.setParsed(true);

  // FIXME: update the page
  page=MWAWPageSpan();

  libmwaw::DebugStream f, f2;

  long debPage = entry.begin();
  long length = entry.length();
  long endPage = entry.end();

  input->seek(debPage, librevenge::RVNG_SEEK_SET);
  auto sz = static_cast<int>(input->readULong(1));
  if (sz != length-1) f << "###sz=" << sz << ",";

  bool ok = true;
  double dim[6] = {-1., -1., -1., -1., -1., -1. };
  while (input->tell() < endPage) {
    long debPos = input->tell();
    auto val = static_cast<int>(input->readULong(1));

    switch (val) {
    case 0x41: // w (or 4000 when frame)
    case 0x42: // h (or 4000 when frame)
    case 0x43: // and margin (y,x)
    case 0x44:
    case 0x45:
    case 0x46: {
      if (debPos+2 > endPage) {
        ok = false;
        break;
      }
      long d = static_cast<int>(input->readULong(2));
      if (d == 4000) break;
      dim[val-0x41] = double(d)/72.;
      break;
    }

    case 0x4d: { // cols-1 ?
      if (debPos+2 > endPage) {
        ok = false;
        break;
      }
      auto v = static_cast<int>(input->readULong(2));
      m_state->m_numColumns=v+1;
      if (v) f2 << "cols=" << m_state->m_numColumns << ",";
      break;
    }
    case 0x4f: {
      if (debPos+1 > endPage) {
        ok = false;
        break;
      }
      auto v = static_cast<int>(input->readLong(1));
      if (v==1) {
        m_state->m_hasColumnSep=true;
        f2 << "hasColSep,";
      }
      else if (v)
        f2 << "#hasColSeps=" <<  v << ",";
      break;

    }
    case 0x60: // always 1 ?
    case 0x61: {
      if (debPos+2 > endPage) {
        ok = false;
        break;
      }
      auto v = static_cast<int>(input->readULong(2));
      f2 << "f" << val;
      if (v != 1) f2 << "=" << v << "#";
      f2 << ",";
      break;
    }

    case 0x62: { // some flag : 1| 11| 31| 42| 44| 80 | 100 ?
      if (debPos+2 > endPage) {
        ok = false;
        break;
      }
      auto v = static_cast<int>(input->readULong(2));
      f2 << "f" << val << "=" << std::hex << v << std::dec << ",";
      break;
    }

    case 0x6c: // always follows by three 0xFFFF ? white color ?
      if (debPos+6 > endPage) {
        ok = false;
        break;
      }
      f2 << "bkcol?=(";
      for (int i = 0; i < 3; i++) {
        f2 << input->readULong(1) << ",";
        input->readULong(1);
      }
      f2 << "),";
      break;

    case 0x48: // 12
    case 0x4c: { // 36
      if (debPos+2 > endPage) {
        ok = false;
        break;
      }
      auto v = static_cast<int>(input->readLong(2));
      f2 << "f" << val << "=" <<  v << ",";
      break;
    }

    case 0x6a: { // 1
      if (debPos+1 > endPage) {
        ok = false;
        break;
      }
      auto v = static_cast<int>(input->readLong(1));
      f2 << "f" << val << "=" <<  v << ",";
      break;
    }

    case 0x5f: // -1 if header|footer ?
    case 0x63: // -1 if header ?
    case 0x64: // -1 if footer ?
    case 0x66: // 1
    case 0x67: { // 1
      if (debPos+2 > endPage) {
        ok = false;
        break;
      }
      auto v = static_cast<int>(input->readLong(1));
      f2 << "f" << val << "=" <<  v << ",";
      break;
    }

    default:
      ok = false;
      break;
    }

    if (ok) continue;

    if (!ok) {
      m_document->ascii().addPos(debPos);
      m_document->ascii().addNote("DOP ###");

      break;
    }
  }
  bool dimOk=dim[1]>2.*(dim[2]+dim[4]) && dim[0]>2.*(dim[3]+dim[5]);
  for (int i = 2; i < 6; i++)
    dimOk = dimOk && dim[i]>=0;
  if (dimOk) {
    if (dim[1] > dim[0])
      page.setFormOrientation(MWAWPageSpan::PORTRAIT);
    else
      page.setFormOrientation(MWAWPageSpan::LANDSCAPE);
    page.setFormLength(dim[1]);
    page.setFormWidth(dim[0]);
    page.setMarginTop(dim[2]);
    page.setMarginBottom(dim[4]);
    page.setMarginLeft(dim[3]);
    page.setMarginRight(dim[5]);
  }
  if (dim[0] > 0. || dim[1] > 0.)
    f << "sz=" << dim[0] << "x" << dim[1] << ",";
  bool hasMargin = false;
  for (int i = 2; i < 6; i++) {
    if (dim[i] > 0.0) {
      hasMargin = true;
      break;
    }
  }
  if (hasMargin) {
    f << "margin=(";
    for (int i = 2; i < 6; i++) {
      if (dim[i] < 0) f << "_";
      else f << dim[i];
      if (i == 2 || i == 4) f << "x";
      else f << " ";
    }
    f << "),";
  }
  f << f2.str();
  m_document->ascii().addPos(debPage);
  m_document->ascii().addNote(f.str().c_str());

  return dimOk;
}

// the position in the page ? Constant size 0x28c ?
// link to the graphic entry RBDR ?
// does this means RL -> RB while RBDR means RB->DRawing
bool MsWks4Zone::readRLRB(MWAWInputStreamPtr input, MWAWEntry const &entry)
{
  if (entry.length() < 13+32) return false;
  entry.setParsed(true);

  long debPos = entry.begin();
  input->seek(debPos, librevenge::RVNG_SEEK_SET);
  libmwaw::DebugStream f;
  f << "BDB1=("; // some kind of bdbox: BDB1
  for (int i = 0; i < 4; i++)
    f << input->readLong(2) << ", ";
  f << "), ";
  f << input->readLong(1) << ", ";
  f << input->readLong(2) << ", ";
  for (int i = 0; i < 2; i++)
    f << input->readLong(1) << ", ";

  m_document->ascii().addPos(debPos);
  m_document->ascii().addNote(f.str().c_str());
  m_document->ascii().addPos(input->tell());
  m_document->ascii().addNote("RLRB(2)");


  debPos = entry.end()-32;
  input->seek(debPos, librevenge::RVNG_SEEK_SET);
  f.str("");
  // v0=[29|72..79|189], v1=[-1|2|4|33], (v2xv3) = dim?=(~700x~1000)
  // seems related to BDB1
  f << "RLRB(3):BDB2(";
  for (int i = 0; i< 4; i++)
    f << input->readLong(2) << ",";
  f << ")," << input->readLong(1) << ","; // always 1 ?
  f << "unk1=(" << std::hex;
  for (int i = 0; i < 9; i++)
    f << std::setw(2) << input->readULong(1) << ",";
  f << ")," << input->readLong(1); // always 1 ?
  f << ",unk2=(" << std::hex;
  for (int i = 0; i < 5; i++)
    f << std::setw(2) << input->readULong(1) << ",";
  // header? : [4f|50|5e|75], 0, 0, 0,[80|8b]
  // footer? : 48, 0, 0, 0,8b
  // qfrm, main : no specific form ?
  f << "),dims=(" << std::dec;
  for (int i = 0; i < 4; i++)
    f << input->readLong(2) << ", ";
  f << "), ";

  m_document->ascii().addPos(debPos);
  m_document->ascii().addNote(f.str().c_str());

  return true;
}

// the frame position
bool MsWks4Zone::readFRAM(MWAWInputStreamPtr input, MWAWEntry const &entry)
{
  libmwaw::DebugStream f;

  long debPage = entry.begin();
  long endPage = entry.end();

  input->seek(debPage, librevenge::RVNG_SEEK_SET);
  auto numFram = static_cast<int>(input->readULong(2));
  if (numFram <= 0) return false;
  entry.setParsed(true);

  f << "N=" << numFram;

  m_document->ascii().addPos(debPage);
  m_document->ascii().addNote(f.str().c_str());

  for (int i = 0; i < numFram; i++) {
    long debPos = input->tell();
    auto size = static_cast<int>(input->readULong(1));
    if (size <= 0) break;

    f.str("");

    bool ok = true;
    long endPos = debPos+1+size;
    if (endPos > endPage) break;

    MsWks4ZoneInternal::Frame frame;
    MWAWVec2f fOrig, fSiz;
    while (input->tell() < endPos) {
      auto val = static_cast<int>(input->readULong(1));
      long pos = input->tell();

      int sz = 0;
      bool done = true;
      switch (val) {
      case 0x2e: // x
      case 0x2f: // y
        if (pos+2 > endPos) {
          ok = false;
          break;
        }
        if (val == 0x2e) fOrig.setX(float(input->readLong(2))/72.f);
        else fOrig.setY(float(input->readLong(2))/72.f);
        break;
      case 0x30: // width
      case 0x31: // height
        if (pos+2 > endPos) {
          ok = false;
          break;
        }
        if (val == 0x30) fSiz.setX(float(input->readLong(2))/72.f);
        else fSiz.setY(float(input->readLong(2))/72.f);
        break;
      case 0x3c: { // type : checkme ?
        if (pos+2 > endPos) {
          ok = false;
          break;
        }
        auto value = static_cast<int>(input->readLong(2));
        switch (value) {
        case 1:
          frame.m_type = MsWks4ZoneInternal::Frame::Textbox;
          break;
        case 2:
          frame.m_type = MsWks4ZoneInternal::Frame::Header;
          break;
        case 4:
          frame.m_type = MsWks4ZoneInternal::Frame::Footer;
          break;
        default:
          f << "###type=" << value << ",";
        }
        break;
      }
      case 0x29: { // always -1 ?
        if (pos+2 > endPos) {
          ok = false;
          break;
        }
        auto value = static_cast<int>(input->readLong(2));
        if (value != -1) f << "###29=" << value << ",";
      }
      break;
      case 0x2a: // in main 2a=6, in frame 2a=4, 2b=4
      case 0x2b:
        done = false;
        sz = 1;
        break;
      case 0x3d: // only in textBox, related to the field id of Graphics15
        if (pos+4 > endPos) {
          ok = false;
          break;
        }
        f << "textBoxId=X" << std::hex << input->readLong(4) << "," << std::dec;
        break;
      case 0x3e: { // appear only in QFrm with value ~ 1/2, 2000
        if (pos+4 > endPos) {
          ok = false;
          break;
        }
        f << std::hex << val << "=(" << std::dec;
        for (int j = 0; j < 2; j++)
          f << input->readLong(2) << ",";
        f << "),";
      }
      break;

      case 0x37: {
        if (pos+6 > endPos) {
          ok = false;
          break;
        }
        std::string fName("");
        for (int j = 0; j < 4; j++) {
          auto c = char(input->readULong(1));
          if (c == '\0') continue;
          fName +=  c;
        }
        frame.m_pictId.setName(fName);
        frame.m_pictId.setId(static_cast<int>(input->readULong(2)));
        done = true;
      }
      break;
      default:
        done = false;
        break;
      }

      if (!ok) break;
      if (done) continue;

      if (sz == 0 || pos + sz > endPos) {
        input->seek(-1, librevenge::RVNG_SEEK_CUR);
        ok = false;
        break;
      }

      f << std::hex << val << "=" << std::dec;
      if (sz) {
        auto v2 = static_cast<int>(input->readLong(sz));
        f << v2;
      }
      f << ", ";
    }

    frame.m_position=MWAWPosition(fOrig, fSiz);
    frame.m_position.setPage(-3);
    frame.m_error = f.str();
    f.str("");
    m_state->m_framesList.push_back(frame);

    f << "FRAM(" << size << "):" << frame;
    m_document->ascii().addPos(debPos);
    m_document->ascii().addNote(f.str().c_str());
    if (!ok) {
      m_document->ascii().addPos(input->tell());
      m_document->ascii().addNote("FRAM###");
    }

    input->seek(endPos, librevenge::RVNG_SEEK_SET);
  }

  return true;
}

// the selection
bool MsWks4Zone::readSELN(MWAWInputStreamPtr input, MWAWEntry const &entry)
{
  libmwaw::DebugStream f;

  long debPage = entry.begin();
  long endPage = entry.end();

  input->seek(debPage, librevenge::RVNG_SEEK_SET);
  if (endPage-debPage <= 12) {
    MWAW_DEBUG_MSG(("MsWks4Zone::readSELN: SELN size=%ld too short\n", endPage-debPage));
    return false;
  }
  entry.setParsed(true);

  auto type = static_cast<int>(input->readLong(1));
  // 2,3
  switch (type) {
  case 2:
    f << "textPoint, ";
    break;
  case 3:
    f << "textZone, ";
    break;
  default:
    f << "type=###" << type << ",";
    break;
  }
  // 0 0xFF 0
  for (int i = 0; i < 3; i++) {
    auto val = static_cast<int>(input->readLong(1));
    if ((i%2)+val)
      f << "unk" << i << "=" << val << ",";
  }
  // textBegin, textEnd
  f << "textPos?=(";
  for (int i = 0; i < 2; i++) {
    f << input->readLong(4);
    if (i == 0) f << "<->";
  }
  f << ")";

  // I find f2=-1,f20=-1,f21=-1 and f22=0|1
  // maybe f22=0 -> mainTextZone, f22=1 -> header, ...
  for (int i = 0; i < (endPage-debPage-12)/2; i++) {
    auto val = static_cast<int>(input->readLong(2));
    if (val) f << ",f" << i << "=" << val;
  }

  m_document->ascii().addPos(debPage);
  m_document->ascii().addNote(f.str().c_str());

  if (long(input->tell()) != endPage) {
    m_document->ascii().addPos(input->tell());
    m_document->ascii().addNote("SELN###");
  }
  return true;
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab:
