/* -*- 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 <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>
#include <utility>

#include <librevenge/librevenge.h>

#include "MWAWGraphicListener.hxx"
#include "MWAWGraphicShape.hxx"
#include "MWAWGraphicStyle.hxx"
#include "MWAWHeader.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPictBitmap.hxx"
#include "MWAWPictData.hxx"
#include "MWAWPosition.hxx"
#include "MWAWSection.hxx"
#include "MWAWSubDocument.hxx"

#include "SpringBoardParser.hxx"

/** Internal: the structures of a SpringBoardParser */
namespace SpringBoardParserInternal
{
//! Internal: a structure used to store the frame property in SpringBoardParserInternal
struct Frame {
  //! constructor
  Frame()
    : m_type(-1)
    , m_style(MWAWGraphicStyle::emptyStyle())
    , m_numSubZones(0)
    , m_numColumns(1)
    , m_columnBoxes()
  {
  }
  //! the frame type: 0: layout, 1: text, 2: graphic
  int m_type;
  //! the frame style
  MWAWGraphicStyle m_style;
  //! the number of sub zone (for a text frame)
  int m_numSubZones;
  //! the external and the internal box
  MWAWBox2i m_boxes[2];
  //! the number of columns: for a text frame
  int m_numColumns;
  //! the child box: one by column for text and layout
  std::vector<MWAWBox2i> m_columnBoxes;
};

//! Internal: a structure used to store a link in SpringBoardParserInternal
struct Link {
  //! constructor
  Link()
    : m_font()
  {
    for (auto &lnk : m_links)
      lnk=std::make_pair(-1,-1);
  }
  //! returns true if the link stores somme text
  bool hasText() const
  {
    for (auto const &entry : m_texts) {
      if (entry.valid()) return true;
    }
    return false;
  }
  //! the font
  MWAWFont m_font;
  //! the top and bottom text
  MWAWEntry m_texts[2];
  //! the next and previous link
  std::pair<int,int> m_links[2];
};
//! Internal: a structure used to store the shape data in SpringBoardParserInternal
struct Shape {
  //! a paragraph contained in a SpringBoardParserInternal::Shape
  struct Paragraph {
    //! constructor
    Paragraph()
      : m_numChars(0)
      , m_firstChar(0)
      , m_newColumn(false)
      , m_numCharStyles(0)
      , m_textZoneId(-1)
      , m_paragraphStyle(-1)
      , m_charEntry()
    {
    }
    //! the number of character
    int m_numChars;
    //! the first character
    int m_firstChar;
    //! a flag to know if we need to change column: USEME
    bool m_newColumn;
    //! the number of character style
    int m_numCharStyles;
    //! the text zone id
    int m_textZoneId;
    //! the paragraph style
    int m_paragraphStyle;
    //! the character style zone
    MWAWEntry m_charEntry;
  };
  //! constructor
  Shape()
    : m_type(0)
    , m_paragraphs()
    , m_pictEntry()
  {
  }
  //! the shape type: 0: layout, 1: text, 2: graphic
  int m_type;
  //! the list of paragraph
  std::vector<Paragraph> m_paragraphs;
  //! the picture entry: graphic only
  MWAWEntry m_pictEntry;
};

////////////////////////////////////////
//! Internal: the Header/Footer of a SpringBoardParser
struct HeaderFooter {
  //! constructor
  HeaderFooter()
    : m_position(0)
    , m_type(0)
    , m_pageNumber(0)
    , m_font()
  {
  }
  //! the header/footer position 1: TC, TR, TL, BC, BR, BL, FacingT, FacingB
  int m_position;
  //! the numbering type: 0: numeric, 1/2: small/big roman, 3/4: small/big alpha, 5-7: full minu,MAJU,Title, 8: none
  int m_type;
  //! the first page numbering
  int m_pageNumber;
  //! the font
  MWAWFont m_font;
  //! the prefix/postfix zones string
  MWAWEntry m_zones[2];
};

////////////////////////////////////////
//! Internal: the state of a SpringBoardParser
struct State {
  //! constructor
  State()
    : m_numPages(1)
    , m_entriesListMap()
    , m_entries2ListMap()
    , m_pageIdToDimensionMap()
    , m_pageIdToHeaderFooterMap()
    , m_idToFrameMap()
    , m_idToShapeMap()
    , m_idToLinkMap()
    , m_idToTextEntriesMap()
    , m_idToParagraphMap()

    , m_parsedZonesMap()
  {
  }

  //! returns the name of a data zone
  std::string getDataZoneName(int id) const
  {
    char const *wh[]= {"Doc", "Page", "FramStyle", "FramLink", nullptr,
                       nullptr, nullptr, nullptr, "ParaStyle", "Text"
                      };
    if (id>=0 && id<10 && wh[id])
      return wh[id];
    std::stringstream s;
    s << "List" << id;
    return s.str();
  }
  //! the number of pages
  int m_numPages;
  //! a map zone id to list's entry
  std::map<int, MWAWEntry> m_entriesListMap;
  //! a map id to the last zone entries
  std::map<int, MWAWEntry> m_entries2ListMap;
  //! the map pageId to page dimension
  std::map<int, MWAWBox2i> m_pageIdToDimensionMap;
  //! the map pageId to header footer
  std::map<int, HeaderFooter> m_pageIdToHeaderFooterMap;
  //! the map (page,id) to frame map
  std::map<std::pair<int,int>, Frame> m_idToFrameMap;
  //! the map (page,id) to shape map
  std::map<std::pair<int,int>, Shape> m_idToShapeMap;
  //! the map (page,id) to link map
  std::map<std::pair<int,int>, Link> m_idToLinkMap;
  //! the map id to text entries
  std::map<int, MWAWEntry> m_idToTextEntriesMap;
  //! the map id to paragraph
  std::map<int, MWAWParagraph> m_idToParagraphMap;

  //! the list of parsed zone
  std::map<long,long> m_parsedZonesMap;
};

////////////////////////////////////////
//! Internal: the subdocument of a SpringBoardParser
class SubDocument final : public MWAWSubDocument
{
public:
  //! constructor knowning the header/footer id
  SubDocument(SpringBoardParser &pars, MWAWInputStreamPtr const &input, int hfId)
    : MWAWSubDocument(&pars, input, MWAWEntry())
    , m_hfId(hfId)
    , m_linkId({-1,-1})
    , m_linkTop(true)
    , m_pageId({-1,-1})
    , m_minParagraph(-1)
    , m_maxParagraph(-1)
  {
  }

  //! constructor corresponding to a layout/textbox shape
  SubDocument(SpringBoardParser &pars, MWAWInputStreamPtr const &input, std::pair<int,int> const &pageId,
              std::pair<int,int> const &paras= {-1,-1})
    : MWAWSubDocument(&pars, input, MWAWEntry())
    , m_hfId(-1)
    , m_linkId({-1,-1})
    , m_linkTop(true)
    , m_pageId(pageId)
    , m_minParagraph(paras.first)
    , m_maxParagraph(paras.second)
  {
  }
  //! constructor corresponding to a link shape
  SubDocument(SpringBoardParser &pars, MWAWInputStreamPtr const &input, std::pair<int,int> const &linkId,
              bool isTop)
    : MWAWSubDocument(&pars, input, MWAWEntry())
    , m_hfId(-1)
    , m_linkId(linkId)
    , m_linkTop(isTop)
    , m_pageId({-1,-1})
  , m_minParagraph(-1)
  , m_maxParagraph(-1)
  {
  }
  //! destructor
  ~SubDocument() final {}

  //! operator!=
  bool operator!=(MWAWSubDocument const &doc) const final
  {
    if (MWAWSubDocument::operator!=(doc)) return true;
    auto const *sDoc = dynamic_cast<SubDocument const *>(&doc);
    if (!sDoc) return true;
    if (sDoc->m_hfId != m_hfId) return true;
    if (sDoc->m_linkId != m_linkId) return true;
    if (sDoc->m_linkTop != m_linkTop) return true;
    if (sDoc->m_pageId != m_pageId) return true;
    if (sDoc->m_minParagraph != m_minParagraph) return true;
    if (sDoc->m_maxParagraph != m_maxParagraph) return true;
    return false;
  }

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

protected:
  //! the header/footer zone id
  int m_hfId;
  //! the text link id
  std::pair<int,int> m_linkId;
  //! a flag to know if we need to send the top or the bottom text
  bool m_linkTop;
  //! the text page id
  std::pair<int,int> m_pageId;
  //! the minimum and maximum paragraph
  int m_minParagraph, m_maxParagraph;
private:
  SubDocument(SubDocument const &orig) = delete;
  SubDocument &operator=(SubDocument const &orig) = delete;
};

void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType)
{
  if (!listener || !listener->canWriteText()) {
    MWAW_DEBUG_MSG(("SpringBoardParserInternal::SubDocument::parse: no listener\n"));
    return;
  }
  auto parser=dynamic_cast<SpringBoardParser *>(m_parser);
  if (!parser) {
    MWAW_DEBUG_MSG(("SpringBoardParserInternal::SubDocument::parse: no parser\n"));
    return;
  }
  long pos = m_input->tell();
  if (m_hfId>=0)
    parser->sendHeaderFooter(m_hfId);
  else if (m_linkId.first != -1)
    parser->sendLinkShape(m_linkId, m_linkTop);
  else
    parser->sendTextShape(m_pageId, m_minParagraph, m_maxParagraph);
  m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}


}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
SpringBoardParser::SpringBoardParser(MWAWInputStreamPtr const &input, MWAWRSRCParserPtr const &rsrcParser, MWAWHeader *header)
  : MWAWGraphicParser(input, rsrcParser, header)
  , m_state(new SpringBoardParserInternal::State)
{
  setAsciiName("main-1");

  getPageSpan().setMargins(0.1);
}

SpringBoardParser::~SpringBoardParser()
{
}

bool SpringBoardParser::isPositionValid(long pos) const
{
  MWAWEntry entry;
  entry.setBegin(pos);
  entry.setLength(0);
  return isEntryValid(entry);
}

bool SpringBoardParser::isEntryValid(MWAWEntry const &entry) const
{
  auto input=const_cast<SpringBoardParser *>(this)->getInput();
  if (!input || entry.begin()<0x80 || !input->checkPosition(entry.end()))
    return false;
  auto it=m_state->m_parsedZonesMap.upper_bound(entry.begin());
  if (it!=m_state->m_parsedZonesMap.begin())
    --it;
  while (it!=m_state->m_parsedZonesMap.end()) {
    if (it->first>=entry.end())
      return true;
    if (it->second>entry.begin())
      return false;
    ++it;
  }
  return true;
}

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

      auto listener=getGraphicListener();
      if (!listener)
        ok=false;
      else {
        for (int pg=0; pg<m_state->m_numPages; ++pg) {
          if (pg)
            listener->insertBreak(MWAWListener::PageBreak);
          auto fIt=m_state->m_idToFrameMap.lower_bound(std::make_pair(pg+1, -1));
          while (fIt!=m_state->m_idToFrameMap.end() && fIt->first.first==pg+1)
            sendFrame(fIt++->first);
        }
      }
    }
    ascii().reset();
  }
  catch (...) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parse: exception catched when parsing\n"));
    ok = false;
  }

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

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

  // create the page list
  int numPages=std::max<int>(1,m_state->m_numPages);
  auto fIt=m_state->m_idToFrameMap.rbegin();
  if (fIt!=m_state->m_idToFrameMap.rend()) {
    if (fIt->first.first>numPages) {
      MWAW_DEBUG_MSG(("SpringBoardParser::createDocument: oops, find some frame which are on unknown page\n"));
    }
    else if (fIt->first.first<numPages && fIt->first.first>1)
      numPages=fIt->first.first;
  }
  // FIXME: we must also look if we find some page numbering with pageId>numPages
  m_state->m_numPages=numPages;
  std::vector<MWAWPageSpan> pageList;
  MWAWPageSpan ps;
  for (int pg=1; pg<=numPages; ++pg) {
    ps=getPageSpan();
    auto dimIt=m_state->m_pageIdToDimensionMap.find(pg);
    if (dimIt!=m_state->m_pageIdToDimensionMap.end()) {
      auto const &sz=dimIt->second[1];
      if (sz[0]>0 && sz[1]>0) {
        ps.setFormWidth(double(sz[0])/72);
        ps.setFormLength(double(sz[1])/72);
      }
    }
    // FIXME: look like a header/footer in page N is also replicated as header/footer for page N+1, N+2, ...
    auto hfIt=m_state->m_pageIdToHeaderFooterMap.find(pg);
    if (hfIt!=m_state->m_pageIdToHeaderFooterMap.end()) {
      auto const &hf=hfIt->second;
      if (hf.m_type>0 && hf.m_type<=8) {
        MWAWHeaderFooter hF((hf.m_type<=3 || hf.m_type==7) ? MWAWHeaderFooter::HEADER : MWAWHeaderFooter::FOOTER,
                            MWAWHeaderFooter::ALL);
        auto subdoc=std::make_shared<SpringBoardParserInternal::SubDocument>(*this, getInput(), pg);
        hF.m_subDocument=subdoc;
        ps.setHeaderFooter(hF);
      }
      else {
        MWAW_DEBUG_MSG(("SpringBoardParser::createDocument: oops, find unknown hf type=%d\n", hf.m_type));
      }
    }
    pageList.push_back(ps);
  }

  MWAWGraphicListenerPtr listen(new MWAWGraphicListener(*getParserState(), pageList, documentInterface));
  setGraphicListener(listen);
  listen->startDocument();
}


////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool SpringBoardParser::createZones()
{
  MWAWInputStreamPtr input = getInput();
  input->seek(4, librevenge::RVNG_SEEK_SET);

  libmwaw::DebugStream f;
  f << "limits=[";
  for (int i=0; i<2; ++i) {
    long ptr=long(input->readULong(4));
    f << std::hex << ptr << std::dec << ",";
    if (ptr<0x80 || !input->checkPosition(ptr)) {
      MWAW_DEBUG_MSG(("SpringBoardParser::createZones: find bad limits\n"));
      f << "###";
      ascii().addPos(0);
      ascii().addNote(f.str().c_str());
      return false;
    }
    ascii().addPos(ptr);
    ascii().addNote(i==0 ? "[A]" : "[B]");
  }
  f << "],";

  int val;
  MWAWEntry entry;
  entry.setBegin(long(input->readULong(4)));
  entry.setLength(long(input->readULong(4)));
  if (entry.valid()) {
    f << "pagNumb=" << std::hex << entry.begin() << "<->" << entry.end() << std::dec << ",";
    if (!isEntryValid(entry)) {
      MWAW_DEBUG_MSG(("SpringBoardParser::createZones: find bad entries for page numbering\n"));
      f << "###";
      return false;
    }
    m_state->m_parsedZonesMap[entry.begin()]=entry.end();
    long actPos=input->tell();
    parsePageNumbering(entry);
    input->seek(actPos, librevenge::RVNG_SEEK_SET);
  }
  for (int i=0; i<10; ++i) {
    entry.setBegin(long(input->readULong(4)));
    entry.setLength(long(input->readULong(2)));
    if (!entry.valid())
      continue;
    char const *wh[]= {
      "doc", "page", "framstyle", "framlink", nullptr,
      nullptr, nullptr, nullptr, "parastyle", "text"
    };
    if (wh[i])
      f << wh[i] << "=" << std::hex << entry.begin() << "<->" << entry.end() << std::dec << ",";
    else
      f << "list" << i << "=" << std::hex << entry.begin() << "<->" << entry.end() << std::dec << ",";
    if (!isEntryValid(entry)) {
      MWAW_DEBUG_MSG(("SpringBoardParser::createZones: find bad entries %d\n", i));
      f << "###";
      ascii().addPos(0);
      ascii().addNote(f.str().c_str());
      return false;
    }
    m_state->m_parsedZonesMap[entry.begin()]=entry.end();
    m_state->m_entriesListMap[i]=entry;
  }
  for (int i=0; i<3; ++i) {
    val=int(input->readLong(2));
    if (val)
      f << "f" << i+4 << "=" << val << ",";
  }
  for (int i=0; i<3; ++i) {
    entry.setBegin(long(input->readULong(4)));
    entry.setLength(long(input->readULong(4)));
    if (!entry.valid())
      continue;
    f << (i==0 ? "shape" : i==1 ? "txtPos" : "frame") << "=" << std::hex << entry.begin() << "<->" << entry.end() << std::dec << ",";
    if (!isEntryValid(entry)) {
      MWAW_DEBUG_MSG(("SpringBoardParser::createZones: find bad entries %d\n", i));
      f << "###";
      ascii().addPos(0);
      ascii().addNote(f.str().c_str());
      return false;
    }
    m_state->m_parsedZonesMap[entry.begin()]=entry.end();
    m_state->m_entries2ListMap[i]=entry;
  }
  for (int i=0; i<3; ++i) {
    val=int(input->readLong(2));
    if (val)
      f << "g" << i << "=" << val << ",";
  }
  val=int(input->readULong(4)); // 0 or 68ad08
  if (val)
    f << "unkn=" << std::hex << val << std::dec << ",";
  for (int i=0; i<3; ++i) { // g3=0|8
    val=int(input->readLong(2));
    if (val)
      f << "g" << i+3 << "=" << val << ",";
  }
  f << "num[shapes]=" << input->readLong(2) << ",";
  ascii().addPos(0);
  ascii().addNote(f.str().c_str());

  for (auto const &it : m_state->m_entriesListMap)
    parseDataList(it.first, it.second);

  for (auto const &it : m_state->m_entries2ListMap)
    parseLastZone(it.first, it.second);
  return true;
}

bool SpringBoardParser::readFont(MWAWFont &font, int szFieldSize)
{
  font=MWAWFont();
  MWAWInputStreamPtr input = getInput();
  if (!input)
    return false;
  long pos=input->tell();
  if ((szFieldSize!=1 && szFieldSize!=2) || !input->checkPosition(pos+4+szFieldSize)) {
    MWAW_DEBUG_MSG(("SpringBoardParser::readFont: the zone seems bad\n"));
    return false;
  }

  libmwaw::DebugStream f;
  font.setId(int(input->readULong(2)));
  int val=int(input->readULong(2));
  uint32_t flags=0;
  if (val&0x1) flags |= MWAWFont::boldBit;
  if (val&0x2) flags |= MWAWFont::italicBit;
  if (val&0x4) {
    font.setUnderlineStyle(MWAWFont::Line::Simple);
    if (val&0x80)
      f << "underline[char],";
    if (val&0x100)
      f << "underline[word],";
    // &0x8000 all
    val&=0x7e7f;
  }
  if (val&0x8) flags |= MWAWFont::embossBit;
  if (val&0x10) flags |= MWAWFont::shadowBit;
  if (val&0x20)
    font.set(MWAWFont::Script::super100());
  if (val&0x40)
    font.set(MWAWFont::Script::sub100());
  if ((val&0x600)==0x600) {
    flags |= MWAWFont::reverseVideoBit;
    f << "pat[inverse]=" << ((val&0x7800)>>11) << ","; // FIXME use pattern
    val &= 0x81ff;
  }
  if (val&0xff80) f << "font[fl]=#" << std::hex << (val&0xff80) << std::dec << ",";
  font.setSize(float(input->readLong(szFieldSize)));
  font.setFlags(flags);
  font.m_extra=f.str();
  return true;
}

bool SpringBoardParser::parsePageNumbering(MWAWEntry const &entry)
{
  MWAWInputStreamPtr input = getInput();
  if (!input)
    return false;
  if (!entry.valid() || (entry.length()%98)!=0 || !input->checkPosition(entry.end())) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parsePageNumbering: find unvalid entry\n"));
    return false;
  }
  ascii().addPos(entry.end());
  ascii().addNote("_");

  libmwaw::DebugStream f;
  f << "Entries(PagNumbering):";
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  ascii().addPos(entry.begin());
  ascii().addNote(f.str().c_str());
  int N=int(entry.length()/98);
  int val;
  for (int i=0; i<N; ++i) {
    long pos=input->tell();
    f.str("");
    f << "PagNumbering-" << i << ":";
    int pg=int(input->readLong(2));
    f << "page=" << pg << ",";
    SpringBoardParserInternal::HeaderFooter hf;
    hf.m_type=int(input->readLong(2));
    if (hf.m_type) f << "number[format]=" << hf.m_type << ",";
    hf.m_pageNumber=int(input->readLong(2));
    if (hf.m_pageNumber!=pg) f << "page[number]=" << hf.m_pageNumber << ",";
    hf.m_position=int(input->readLong(2));
    f << "pos=" << hf.m_position << ",";

    readFont(hf.m_font, 2);
    f << hf.m_font.getDebugString(getFontConverter())  << ",";
    for (int j=0; j<2; ++j) {
      long actPos=input->tell();
      int cLen=int(input->readULong(1));
      if (cLen>40) {
        MWAW_DEBUG_MSG(("SpringBoardParser::parsePageNumbering: can not read a string\n"));
        f << "###cLen" << j << "=" << cLen << ",";
        cLen=0;
      }
      hf.m_zones[j].setBegin(actPos+1);
      hf.m_zones[j].setLength(cLen);
      std::string text;
      for (int c=0; c<cLen; ++c)
        text+=char(input->readULong(1));
      f << text << ",";
      input->seek(actPos+40, librevenge::RVNG_SEEK_SET);
    }
    for (int j=0; j<2; ++j) {
      val=int(input->readLong(2));
      if (val!=(j==0 ? 50 : 90))
        f << "from[" << (j==0 ? "bottom" : "right") << "]=" << val << ",";
    }
    if (m_state->m_pageIdToHeaderFooterMap.count(pg)) {
      f << "###dup";
      MWAW_DEBUG_MSG(("SpringBoardParser::parsePageNumbering: an header/footer already exists for the page %d\n", pg));
    }
    else
      m_state->m_pageIdToHeaderFooterMap[pg]=hf;
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    input->seek(pos+98, librevenge::RVNG_SEEK_SET);
  }
  return true;
}

bool SpringBoardParser::parseDataList(int id, MWAWEntry const &entry)
{
  MWAWInputStreamPtr input = getInput();
  if (!input)
    return false;
  if (id<0 || id>=10 || !entry.valid() || !input->checkPosition(entry.end())) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parseDataList: find unvalid entry for id=%d\n", id));
    return false;
  }
  ascii().addPos(entry.end());
  ascii().addNote("_");

  libmwaw::DebugStream f;
  std::string const what=m_state->getDataZoneName(id);
  f << "Entries(" << what << "):";
  if ((entry.length()%16)!=0) {
    f << "###";
    MWAW_DEBUG_MSG(("SpringBoardParser::parseDataList: find unexpected size for zone %s\n", what.c_str()));
    ascii().addPos(entry.begin());
    ascii().addNote(f.str().c_str());
    return false;
  }

  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  int num=int(entry.length()/16);
  std::map<int, std::pair<MWAWVec2i,MWAWEntry> > listZones;
  for (int st=0; st<num; ++st) {
    long pos=input->tell();
    f.str("");
    if (st==0)
      f << "Entries(" << what << "):";
    else
      f << what << "[st=" << st << "]:";
    int firstId=int(input->readLong(4));
    if (firstId)
      f << "id[first]=" << firstId << ",";
    MWAWEntry data;
    data.setBegin(long(input->readULong(4)));
    data.setLength(long(input->readULong(4)));
    f << "zone=" << std::hex << data.begin() << "x" << data.end() << std::dec << ",";
    int nextId=int(input->readLong(4));
    f << "next[id]=" << nextId << ",";
    if (!isEntryValid(data)) {
      f << "###";
      MWAW_DEBUG_MSG(("SpringBoardParser::parseDataList: find unexpected zone for zone %s\n", what.c_str()));
      return false;
    }
    m_state->m_parsedZonesMap[entry.begin()]=entry.end();
    listZones[st]=std::make_pair(MWAWVec2i(firstId,nextId), data);
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  for (auto const &it : listZones) {
    MWAWEntry data;
    MWAWVec2i indices;
    std::tie(indices,data)=it.second;
    if (id==8)
      parseParagraphStyle(data, indices);
    else
      parseData(id, data, indices);
  }
  return true;
}

bool SpringBoardParser::parseData(int id, MWAWEntry const &entry, MWAWVec2i const &indices)
{
  MWAWInputStreamPtr input = getInput();
  if (!input)
    return false;
  if (id<0 || id>=10 || !entry.valid() || !input->checkPosition(entry.end())) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parseData: find unvalid entry for id=%d\n", id));
    return false;
  }

  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  long pos=input->tell();
  ascii().addPos(entry.end());
  ascii().addNote("_");

  libmwaw::DebugStream f;
  std::string const what=m_state->getDataZoneName(id);
  int const expectedSize[]= {6, 10, 26, 182, -1,
                             -1, -1, -1, -1, 512
                            };
  if (expectedSize[id]<=0) {
    f << what << "[header,index=" << indices << "]:";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return true;
  }
  if (indices[1]<indices[0] || indices[1]-indices[0]<entry.length()/expectedSize[id]) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parseData: find unexpected length for data zone %s\n", what.c_str()));
    ascii().addPos(pos);
    ascii().addNote("###");
    return false;
  }
  for (int i=indices[0]; i<indices[1]; ++i) {
    pos=input->tell();
    f.str("");
    f << what << "-dt" << i << "]:";
    int val;
    switch (id) {
    case 0: { // Doc
      for (int j=0; j<3; ++j) {
        val=int(input->readLong(2));
        if (val==1)
          continue;
        if (j==2) {
          m_state->m_numPages=val;
          f << "num[pages]=" << val << ",";
        }
        else
          f << "num" << j << "=" << val << ",";
      }
      break;
    }
    case 1: { // Page
      for (int j=0; j<5; ++j) {
        val=int(input->readLong(2));
        int const expected[]= {0,0,0,0,-1};
        if (val==expected[j])
          continue;
        if (j==0)
          f << "page=" << val << ",";
        else if (j==3)
          f << "first[shape]=" << val << ",";
        else
          f << "f" << j << "=" << val << ",";
      }
      break;
    }
    case 2: { // FrameStyle
      SpringBoardParserInternal::Frame frame;
      int pageId=int(input->readLong(1));
      f << "page[id]=" << pageId << ",";
      val=int(input->readULong(1));
      frame.m_type=val&3;
      f << "type=" << frame.m_type << ",";
      if (val&0x80) f << "crop[image],";
      val&=0x7c;
      // checkme what means type+0x10: multicolumn?
      f << "fl=" << std::hex << val << std::dec << ","; // 0: layout, 1: text, 2: graphic
      val=int(input->readULong(1));
      if (val==0x40)
        f << "crop[image],"; // checkme
      else if (val)
        f << "f0=" << val << ",";
      auto &style=frame.m_style;
      int bStyle=int(input->readLong(1));
      f << "border[style]=" << bStyle << ","; // 0: single, 1: double, 2: 2,1,1, 3: 1,1,2, 4: 1,1,2,1,1, 5: dashed, 6; dotted, 7: big dotted
      style.m_lineWidth=float(input->readLong(1));
      if (style.m_lineWidth>0) {
        f << "line[width]=" << style.m_lineWidth << ",";
        if (bStyle>=1 && bStyle<=4)
          style.m_lineWidth*=2;
        else if (bStyle==5)
          style.m_lineDashWidth= {10,10};
        else if (bStyle>=6 && bStyle<=7)
          style.m_lineDashWidth= {2,2};
      }
      val=int(input->readLong(1));
      if (val!=1) f << "line[pat]=" << val << ",";
      int page=int(input->readLong(2));
      f << "page=" << page << ",";
      for (int j=0; j<5; ++j) { // f5=0|b, f6=0|12, f7=0|100
        val=int(input->readLong(2));
        if (!val)
          continue;
        if (j==1) {
          frame.m_numSubZones=val;
          f << "num[subZones]=" << val << ",";
        }
        else if (j==4)
          f << "gray[scale]=" << val << ",";
        else
          f << "f" << j+3 << "=" << val << ",";
      }
      for (int j=0; j<2; ++j) {
        val=int(input->readLong(2));
        if (val==-1)
          continue;
        f << "g" << j << "=" << val << ",";
      }
      val=int(input->readLong(4));
      if (val!=-1) f << "next=F"  << val << ",";
      auto index=std::make_pair(page, pageId);
      if (m_state->m_idToFrameMap.count(index)==0)
        m_state->m_idToFrameMap[index]=frame;
      else {
        MWAW_DEBUG_MSG(("SpringBoardParser::parseData: find a duplicated frame with id=%dx%d\n", page, pageId));
        f << "###dup,";
      }
      break;
    }
    case 3: { // frame header/footer link
      SpringBoardParserInternal::Link link;
      f << "unkn=" << std::hex << input->readLong(2) << std::dec << ",";
      val=int(input->readULong(2));
      if (val!=1)
        f << "f0=" << val << ",";
      readFont(link.m_font, 2);
      f << link.m_font.getDebugString(getFontConverter())  << ",";

      for (int j=0; j<2; ++j) {
        long actPos=input->tell();
        int cLen=int(input->readULong(1));
        if (cLen>80) {
          MWAW_DEBUG_MSG(("SpringBoardParser::parseData: can not read a string\n"));
          f << "###cLen" << j << "=" << cLen << ",";
          cLen=0;
        }
        link.m_texts[j].setBegin(actPos+1);
        link.m_texts[j].setLength(cLen);
        std::string text;
        for (int c=0; c<cLen; ++c) text+=char(input->readULong(1));
        if (!text.empty())
          f << (j==0 ? "top" : "bottom") << "[text]=" << text << ",";
        input->seek(actPos+80, librevenge::RVNG_SEEK_SET);
      }
      std::pair<int,int> index;
      for (int j=0; j<3; ++j) {
        int pageId[2];
        for (auto &w : pageId) w=int(input->readLong(2));
        if (pageId[0]==-1 && pageId[1]==-1) continue;
        char const *wh[]= {"id", "prev[id]", "next[id]"};
        if (j==0)
          index=std::make_pair(pageId[0], pageId[1]);
        else
          link.m_links[j-1]=std::make_pair(pageId[0], pageId[1]);
        f << wh[j] << "=" << MWAWVec2i(pageId[0], pageId[1]) << ",";
      }
      if (m_state->m_idToLinkMap.count(index)==0)
        m_state->m_idToLinkMap[index]=link;
      else {
        MWAW_DEBUG_MSG(("SpringBoardParser::parseData: find a duplicated link with id=%dx%d\n", index.first, index.second));
        f << "###dup,";
      }
      break;
    }
    // 8: paragraph style, already done
    case 9: { // Text
      if (m_state->m_idToTextEntriesMap.count(i)) {
        MWAW_DEBUG_MSG(("SpringBoardParser::parseData: a text entry with id=%d already exists\n", i));
      }
      else {
        MWAWEntry textEntry;
        textEntry.setBegin(pos);
        textEntry.setLength(512);
        m_state->m_idToTextEntriesMap[i]=textEntry;
      }
      std::string text;
      for (int j=0; j<512; ++j) {
        char c=char(input->readULong(1));
        if (!c)
          break;
        text+=c;
      }
      f << text << ",";
      break;
    }
    default:
      break;
    }
    input->seek(pos+expectedSize[id], librevenge::RVNG_SEEK_SET);
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  return true;
}

bool SpringBoardParser::parseParagraphStyle(MWAWEntry const &entry, MWAWVec2i const &indices)
{
  MWAWInputStreamPtr input = getInput();
  if (!input)
    return false;
  int const N=indices[1]-indices[0];
  if (!entry.valid() || !input->checkPosition(entry.end()) || N<0 || entry.length()<4*N) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parseParagraphStyle: find invalid entry\n"));
    return false;
  }
  ascii().addPos(entry.end());
  ascii().addNote("_");

  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  long pos=input->tell();
  libmwaw::DebugStream f;
  f << "ParaStyle[" << N << "]:zones=[";
  std::map<int, long> ptrs;
  for (int i=indices[0]; i<indices[1]; ++i) {
    long ptr=long(input->readULong(4));
    f << std::hex << ptr << std::dec << ",";
    if (ptr>=entry.begin()+4*N && ptr+16<=entry.end())
      ptrs[i]=ptr;
    else {
      f << "###";
      MWAW_DEBUG_MSG(("SpringBoardParser::parseParagraphStyle: find bad ptr\n"));
    }
  }
  f << "],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  int val;
  for (auto const &it : ptrs) {
    f.str("");
    f << "ParaStyle-P" << it.first << ":";
    MWAWParagraph para;
    for (int i=0; i<4; ++i) {
      val=int(input->readULong(1));
      if (val==(i==3 ? 1 : 0)) continue;
      if (i==0) {
        switch (val) {
        case 1:
          para.m_justify = MWAWParagraph::JustificationCenter;
          break;
        case 2:
          para.m_justify = MWAWParagraph::JustificationRight;
          break;
        case 3:
          para.m_justify = MWAWParagraph::JustificationFull;
          break;
        default:
          MWAW_DEBUG_MSG(("SpringBoardParser::parseParagraphStyle: find unknown justification\n"));
          f << "###align=" << val << ","; // 1: center, 2:right, 3: justify?
          break;
        }
      }
      else if (i==1 || i==2)
        para.m_spacings[i]=12*float(val)/72; // in percent, so asume font size=12
      else
        f << "f0=" << val << ",";
    }
    para.m_marginsUnit=librevenge::RVNG_POINT;
    for (int i=0; i<5; ++i) {
      val=int(input->readULong(2));
      int const expected[]= {1,0,0,0,0};
      if (val==expected[i])
        continue;
      if (i==1) {
        if (val&0x8000)
          para.m_spacings[2]=12*float(val&0x7fff)/72;
        else
          para.setInterline(1+float(val), librevenge::RVNG_PERCENT);
      }
      else if (i>=2 && i<=4)
        para.m_margins[i-2]=float(val);
      else
        f << "f" << i+1 << "=" << val << ","; // 8: related to char style
    }
    int nTabs=int(input->readULong(2));
    if (nTabs) {
      if (pos+16+4*nTabs>entry.end()) {
        f << "###nTabs=" << nTabs << ",";
        MWAW_DEBUG_MSG(("SpringBoardParser::parseParagraphStyle: the number of tabs seems bad\n"));
        nTabs=0;
      }
      for (int i=0; i<nTabs; ++i) {
        MWAWTabStop tab;
        char c=char(input->readLong(1));
        if (c>=0x20 && c<0x7f)
          tab.m_leaderCharacter=uint16_t(c);
        else {
          static bool first=true;
          if (first) {
            MWAW_DEBUG_MSG(("SpringBoardParser::parseParagraphStyle: find unexpected leader characters\n"));
            first=false;
          }
          f << "###tab" << i << "=" << int(c) << ",";
        }
        input->seek(1, librevenge::RVNG_SEEK_CUR);
        tab.m_position=float(input->readLong(2))/72;
      }
    }
    f << para;
    if (m_state->m_idToParagraphMap.count(it.first)) {
      MWAW_DEBUG_MSG(("SpringBoardParser::parseParagraphStyle: oops a paragraph with id=%d already exists\n", it.first));
      f << "###dup";
    }
    else
      m_state->m_idToParagraphMap[it.first]=para;
    ascii().addPos(it.second);
    ascii().addNote(f.str().c_str());
  }
  return true;
}

bool SpringBoardParser::parseLastZone(int id, MWAWEntry const &entry)
{
  MWAWInputStreamPtr input = getInput();
  if (!input)
    return false;
  if (id<0 || id>=3 || !entry.valid() || !input->checkPosition(entry.end())) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parseLastZone: find invalid entry for id=%d\n", id));
    return false;
  }
  ascii().addPos(entry.end());
  ascii().addNote("_");

  libmwaw::DebugStream f, f2;
  char const *wh[]= {"Shape", "TxtPos", "Frame"};
  std::string const what(wh[id]);
  f << "Entries(" << what << "):";
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  switch (id) {
  case 0: {
    if ((entry.length()%8)!=0) {
      MWAW_DEBUG_MSG(("SpringBoardParser::parseLastZone: unexpected size\n"));
      f << "###";
      break;
    }
    int N=int(entry.length()/8);
    std::map<std::pair<int,int>,long> idToPtrs;
    for (int i=0; i<N; ++i) {
      long pos=input->tell();
      f2.str("");
      f2 << what << "-" << i << ":";
      long beg=long(input->readULong(4));
      f2 << "pos=" << std::hex << beg << std::dec << ",";
      int pg=int(input->readLong(2));
      int pgId=int(input->readLong(2));
      if (!isPositionValid(beg) || !input->checkPosition(beg+4)) {
        MWAW_DEBUG_MSG(("SpringBoardParser::parseLastZone: a shape position seems bad\n"));
        f2 << "###";
      }
      else
        idToPtrs[std::make_pair(pg,pgId)]=beg;
      f2 << "page=" << pg << ",";
      f2 << "id=" << pgId << ",";
      ascii().addPos(pos);
      ascii().addNote(f2.str().c_str());
    }
    for (auto const &it : idToPtrs)
      parseShape(it.second, it.first);
    break;
  }
  case 1: { // checkme maybe link
    if ((entry.length()%6)!=0) {
      MWAW_DEBUG_MSG(("SpringBoardParser::parseLastZone: unexpected size\n"));
      f << "###";
      break;
    }
    int N=int(entry.length()/6);
    f << "unkn=[";
    for (int i=0; i<N; ++i) {
      int val=int(input->readLong(4));
      f << "last[char]=" << input->readLong(2);
      if (val!=-1)
        f << ":" << val;
      f << ",";
    }
    f << "],";
    break;
  }
  case 2: { // a list of pages
    f << "list,";
    if (entry.length()<2) {
      MWAW_DEBUG_MSG(("SpringBoardParser::parseLastZone: unexpected size\n"));
      f << "###";
      break;
    }
    int N=int(input->readLong(2));
    f << "N=" << N << ",";
    if (2+8*N>entry.length()) {
      MWAW_DEBUG_MSG(("SpringBoardParser::parseLastZone: unexpected N\n"));
      f << "###";
      break;
    }
    std::map<int,long> positions;
    for (int i=0; i<N; ++i) {
      long pos=input->tell();
      f2.str("");
      f2 << what << "-" << i << "[list]:";
      int pg=int(input->readLong(2));
      f2 << "page=" << pg << ",";
      int val=int(input->readLong(2));
      if (val!=pg) f2 << "page2=" << val << ",";
      long beg=long(input->readULong(4));
      f2 << "pos=" << std::hex << beg << std::dec << ",";
      if (beg<entry.begin() || beg>=entry.end()) {
        MWAW_DEBUG_MSG(("SpringBoardParser::parseLastZone: a position seems bad\n"));
        f2 << "###";
      }
      else
        positions[pg]=beg;
      ascii().addPos(pos);
      ascii().addNote(f2.str().c_str());
    }
    for (auto const &it : positions)
      parseFrames(it.first, it.second);
    break;
  }
  default:
    break;
  }
  ascii().addPos(entry.begin());
  ascii().addNote(f.str().c_str());
  return true;
}

bool SpringBoardParser::parseShape(long begPos, std::pair<int,int> const &pageId)
{
  MWAWInputStreamPtr input = getInput();
  if (!input)
    return false;
  if (begPos<0x80 || !input->checkPosition(begPos+4)) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parseShape: the begin position seems bad\n"));
    return false;
  }
  input->seek(begPos, librevenge::RVNG_SEEK_SET);
  int type=-1;
  auto const fIt=m_state->m_idToFrameMap.find(pageId);
  if (fIt==m_state->m_idToFrameMap.end()) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parseShape: can not find the shape %d-%d\n", pageId.first, pageId.second));
  }
  else
    type=fIt->second.m_type;
  SpringBoardParserInternal::Shape shape;
  shape.m_type=type;

  libmwaw::DebugStream f;
  f << "Shape-" << pageId.first << "x" << pageId.second << "[type=" << type << "]:";
  input->seek(begPos, librevenge::RVNG_SEEK_SET);
  bool asciiUpdated=false;
  switch (type) {
  case 0:
  case 1: {
    int N=int(input->readULong(4));
    f << "N=" << N << ",";
    if (N<0 || N+1>(input->size()-begPos)/4 || !isPositionValid(begPos+4+4*N)) {
      MWAW_DEBUG_MSG(("SpringBoardParser::parseShape: can not read the number of shapes\n"));
      f << "###";
      N=0;
    }
    std::vector<long> listPtrs;
    for (int i=0; i<N; ++i) listPtrs.push_back(long(input->readULong(4)));
    ascii().addPos(begPos);
    ascii().addNote(f.str().c_str());
    asciiUpdated=true;

    for (long ptr : listPtrs) {
      if (!isPositionValid(ptr) || !input->checkPosition(ptr+24)) {
        MWAW_DEBUG_MSG(("SpringBoardParser::parseShape: a text pointer seems bad\n"));
        if (ptr>0)
          break;
        continue;
      }
      f.str("");
      f << "Shape-" << pageId.first << "x" << pageId.second << ":text,";
      SpringBoardParserInternal::Shape::Paragraph para;
      input->seek(ptr, librevenge::RVNG_SEEK_SET);
      int val=int(input->readULong(2));
      if (val&0x8000) {
        para.m_newColumn=true;
        f << "new[column],";
        val &= 0x7fff;
      }
      if (val)
        f << "fl=" << std::hex << val << std::dec << ",";
      para.m_numChars=int(input->readULong(2));
      f << "num[char]=" << para.m_numChars << ",";
      for (int i=0; i<2; ++i) {
        val=int(input->readLong(2));
        if (!val) continue;
        if (i==0) {
          para.m_firstChar=val;
          f << "first[char]=" << val << ",";
        }
        else
          f << "y[pos]=" << val << ",";
      }
      val=int(input->readLong(4));
      if (val!=ptr) f << "ptr=" << std::hex << val << std::dec << ",";
      para.m_numCharStyles=int(input->readLong(4));
      f << "num[style]=" << para.m_numCharStyles << ",";
      para.m_paragraphStyle=int(input->readLong(4));
      f << "para[style]=" << para.m_paragraphStyle << ",";
      para.m_textZoneId=int(input->readLong(4));
      if (para.m_textZoneId!=-1)
        f << "text[id]=" << para.m_textZoneId << ",";
      if (ptr+24+10*para.m_numCharStyles<ptr+24 || (input->size()-24-ptr)/10<para.m_numCharStyles ||
          !input->checkPosition(ptr+24+10*para.m_numCharStyles)) {
        f << "###";
        MWAW_DEBUG_MSG(("SpringBoardParser::parseShape: the number of style seems bad\n"));
        para.m_numCharStyles=0;
      }
      para.m_charEntry.setBegin(input->tell());
      para.m_charEntry.setLength(10*para.m_numCharStyles);
      shape.m_paragraphs.push_back(para);
      ascii().addPos(ptr);
      ascii().addNote(f.str().c_str());
    }
    break;
  }
  case 2: {
    int val=int(input->readULong(4));
    if (val)
      f << "f0=" << val << ",";
    long len=long(input->readULong(4));
    f << "len=" << len << ",";
    if ((len && len<10) || begPos+12+len<begPos+12 || !input->checkPosition(begPos+12+len)) {
      MWAW_DEBUG_MSG(("SpringBoardParser::parseShape: can not read the picture length\n"));
      f << "###";
      break;
    }
    shape.m_pictEntry.setBegin(begPos+12);
    shape.m_pictEntry.setLength(len);
    val=int(input->readULong(4));
    if (val)
      f << "f1=" << val << ",";
    break;
  }
  default:
    MWAW_DEBUG_MSG(("SpringBoardParser::parseShape: find unknown type of shapes\n"));
    f << "###";
    break;
  }
  if (m_state->m_idToShapeMap.count(pageId)) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parseShape: find a duplicated shape %d:%d\n", pageId.first, pageId.second));
    f << "###";
  }
  else
    m_state->m_idToShapeMap[pageId]=shape;
  if (!asciiUpdated) {
    ascii().addPos(begPos);
    ascii().addNote(f.str().c_str());
  }
  return true;
}

bool SpringBoardParser::parseFrames(int page, long begPos)
{
  MWAWInputStreamPtr input = getInput();
  if (!input)
    return false;
  if (begPos<0x80 || !input->checkPosition(begPos+14)) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parseFrames: the begin position seems bad\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(Frame):page=" << page << ",";
  input->seek(begPos, librevenge::RVNG_SEEK_SET);
  int val;
  for (int i=0; i<2; ++i) { // f0=0|-4|10
    val=int(input->readLong(2));
    if (val!=i)
      f << "f" << i << "=" << val << ",";
  }
  int dim[4];
  for (auto &d : dim) d=int(input->readLong(2));
  MWAWBox2i dimension=MWAWBox2i(MWAWVec2i(dim[1], dim[0]),MWAWVec2i(dim[3], dim[2]));
  f << "box=" << dimension << ",";
  if (m_state->m_pageIdToDimensionMap.count(page)) {
    f << "###dupl,";
    MWAW_DEBUG_MSG(("SpringBoardParser::parseFrames: the page %d dimension is already set\n", page));
  }
  else
    m_state->m_pageIdToDimensionMap[page]=dimension;
  int N=int(input->readULong(2));
  f << "N=" << N << ",";
  if (!input->checkPosition(begPos+14+N*28)) {
    MWAW_DEBUG_MSG(("SpringBoardParser::parseFrames: can not read N\n"));
    f << "###";
    ascii().addPos(begPos);
    ascii().addNote(f.str().c_str());
    return false;
  }
  ascii().addPos(begPos);
  ascii().addNote(f.str().c_str());

  SpringBoardParserInternal::Frame dummy;
  for (int i=0; i<N; ++i) {
    long pos=input->tell();
    f.str("");
    int id=int(input->readLong(1));
    f << "Frame-" << page << "x" << id << ":";
    auto fIt=m_state->m_idToFrameMap.find(std::make_pair(page,id));
    auto &frame=fIt!=m_state->m_idToFrameMap.end() ? fIt->second : dummy;
    if (fIt==m_state->m_idToFrameMap.end() && (page!=0 || id!=0)) { // 0x0 seems to be the default layout
      MWAW_DEBUG_MSG(("SpringBoardParser::parseFrames: can not find frame %dx%d\n", page, id));
    }
    val=int(input->readLong(1));
    switch (val) {
    case 0:
      f << "layout,";
      break;
    case 1:
      f << "text,";
      break;
    case 2:
      f << "graphics,";
      break;
    default:
      MWAW_DEBUG_MSG(("SpringBoardParser::parseFrames: find unknown frame type\n"));
      f << "###type=" << val << ",";
      break;
    }
    for (int st=0; st<2; ++st) {
      for (auto &d : dim) d=int(input->readLong(2));
      frame.m_boxes[st]=MWAWBox2i(MWAWVec2i(dim[1], dim[0]),MWAWVec2i(dim[3], dim[2]));
      if (st==0)
        f << "box=" << frame.m_boxes[0] << ",";
      else if (frame.m_boxes[0]!=frame.m_boxes[1])
        f << "box[internal]=" << frame.m_boxes[1] << ",";
    }
    for (int j=0; j<4; ++j) {
      val=int(input->readLong(2));
      if (val==0) continue;
      f << "f" << j << "=" << val << ",";
    }
    frame.m_numColumns=int(input->readULong(2));
    if (frame.m_numColumns!=1)
      f << "N[col]=" << frame.m_numColumns << ",";
    if (!input->checkPosition(pos+28+frame.m_numColumns*10)) {
      MWAW_DEBUG_MSG(("SpringBoardParser::parseFrames: find unexpected subzone N\n"));
      f << "###";
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      return false;
    }
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());

    for (int col=0; col<frame.m_numColumns; ++col) {
      pos=input->tell();
      f.str("");
      f << "Frame-" << page << "x" << id << "[" << col << "]:";
      val=int(input->readULong(1));
      if (val)
        f << "f0=" << val << ",";
      val=int(input->readULong(1));
      if (val!=1)
        f << "col=" << val << ",";
      for (auto &d : dim) d=int(input->readLong(2));
      // checkme can we have some non increasing columns ?
      frame.m_columnBoxes.push_back(MWAWBox2i(MWAWVec2i(dim[1], dim[0]),MWAWVec2i(dim[3], dim[2])));
      f << "box=" << frame.m_columnBoxes.back() << ",";
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
    }
  }
  return true;
}

////////////////////////////////////////////////////////////
// send shapes
////////////////////////////////////////////////////////////
bool SpringBoardParser::sendFrame(std::pair<int,int> const &pageId)
{
  auto input=getInput();
  auto listener=getGraphicListener();
  if (!input || !listener) {
    MWAW_DEBUG_MSG(("SpringBoardParser::sendFrame: can not find the listener\n"));
    return false;
  }
  auto fIt=m_state->m_idToFrameMap.find(pageId);
  if (fIt==m_state->m_idToFrameMap.end()) {
    MWAW_DEBUG_MSG(("SpringBoardParser::sendFrame: can not find any frame for %d:%d\n", pageId.first, pageId.second));
    return false;
  }
  auto lIt=m_state->m_idToLinkMap.find(pageId);
  int whichBox = (lIt!=m_state->m_idToLinkMap.end() && lIt->second.hasText()) ? 1 : 0;
  SpringBoardParserInternal::Frame const &frame=fIt->second;
  MWAWPosition pos(MWAWVec2f(frame.m_boxes[whichBox][0]), MWAWVec2f(frame.m_boxes[whichBox].size()), librevenge::RVNG_POINT);
  pos.setRelativePosition(MWAWPosition::Page);

  auto sIt=m_state->m_idToShapeMap.find(pageId);
  if (sIt==m_state->m_idToShapeMap.end()) {
    MWAW_DEBUG_MSG(("SpringBoardParser::sendFrame: can not find any shape for %d:%d\n", pageId.first, pageId.second));
    auto gShape=MWAWGraphicShape::rectangle(MWAWBox2f(frame.m_boxes[whichBox]));
    listener->insertShape(pos, gShape, frame.m_style);
    return false;
  }

  if (whichBox==1) {
    auto const &link=lIt->second;
    for (int p=0; p<2; ++p) {
      if (!link.m_texts[p].valid())
        continue;
      int limits[]= {p==0 ? frame.m_boxes[0][0][1] : frame.m_boxes[1][1][1],
                     p==0 ? frame.m_boxes[1][0][1] : frame.m_boxes[0][1][1]
                    };
      MWAWPosition posL(MWAWVec2f((float)frame.m_boxes[0][0][0],(float)limits[0]),
                        MWAWVec2f((float)frame.m_boxes[0].size()[0],float(limits[1]-limits[0])), librevenge::RVNG_POINT);
      posL.setRelativePosition(MWAWPosition::Page);
      auto subdoc=std::make_shared<SpringBoardParserInternal::SubDocument>(*this, getInput(), pageId, p==0);
      listener->insertTextBox(posL, subdoc, MWAWGraphicStyle::emptyStyle());
    }
  }
  SpringBoardParserInternal::Shape const &shape=sIt->second;
  switch (shape.m_type) {
  case 0: { // layout
    auto subdoc=std::make_shared<SpringBoardParserInternal::SubDocument>(*this, getInput(), pageId);
    listener->insertTextBox(pos, subdoc, frame.m_style);
    return true;
  }
  case 1: { // text zone
    size_t numCols=frame.m_columnBoxes.size();
    std::vector<int> paraLimits;
    paraLimits.push_back(0);
    for (size_t p=0; p<shape.m_paragraphs.size(); ++p) {
      if (shape.m_paragraphs[p].m_newColumn)
        paraLimits.push_back(int(p));
    }
    if (paraLimits.size()>numCols)
      paraLimits.resize(numCols);

    for (size_t c=0; c<std::max<size_t>(1,numCols); ++c) {
      MWAWPosition posi;
      if (numCols) {
        posi.setUnit(librevenge::RVNG_POINT);
        posi.setOrigin(MWAWVec2f(frame.m_boxes[whichBox][0]+frame.m_columnBoxes[c][0]));
        posi.setSize(MWAWVec2f(frame.m_columnBoxes[c].size()));
        posi.setRelativePosition(MWAWPosition::Page);
      }
      int limits[]= {0,-1}; // ie. all
      if (c+1<paraLimits.size()) {
        limits[0]=paraLimits[c];
        limits[1]=paraLimits[c+1]-1;
      }
      else if (c<paraLimits.size()) {
        limits[0]=paraLimits[c];
        limits[1]=-1; // ie. to last paragraph
      }
      else if (c)
        limits[0]=int(paraLimits.size()); // ie. nothing to retrieve
      auto subdoc=std::make_shared<SpringBoardParserInternal::SubDocument>(*this, getInput(), pageId, std::make_pair(limits[0], limits[1]));
      listener->insertTextBox(numCols==0 ? pos : posi, subdoc, frame.m_style);
    }
    return true;
  }
  case 2: {
    if (!shape.m_pictEntry.valid())
      break;
    input->seek(shape.m_pictEntry.begin(), librevenge::RVNG_SEEK_SET);
    std::shared_ptr<MWAWPict> pict(MWAWPictData::get(input, int(shape.m_pictEntry.length())));
    MWAWEmbeddedObject image;
    if (pict && pict->getBinary(image) && !image.m_dataList.empty()) {
#ifdef DEBUG_WITH_FILES
      static int volatile pictName = 0;
      libmwaw::DebugStream f2;
      f2 << "PICT-" << ++pictName << ".pct";
      libmwaw::Debug::dumpFile(image.m_dataList[0], f2.str().c_str());
      ascii().skipZone(shape.m_pictEntry.begin(), shape.m_pictEntry.end()-1);
#endif
      listener->insertPicture(pos, image, frame.m_style);
      return true;
    }
    else {
      MWAW_DEBUG_MSG(("SpringBoardParser::parseShape: sorry, can not retrieve a picture\n"));
    }
    break;
  }
  default:
    break;
  }
  auto gShape=MWAWGraphicShape::rectangle(MWAWBox2f(frame.m_boxes[0]));
  listener->insertShape(pos, gShape, frame.m_style);
  return true;
}

bool SpringBoardParser::sendLinkShape(std::pair<int,int> const &linkId, bool isTop)
{
  auto input=getInput();
  auto listener=getGraphicListener();
  if (!input || !listener) {
    MWAW_DEBUG_MSG(("SpringBoardParser::sendTextLink: can not find the listener\n"));
    return false;
  }
  auto lIt=m_state->m_idToLinkMap.find(linkId);
  if (lIt==m_state->m_idToLinkMap.end()) {
    MWAW_DEBUG_MSG(("SpringBoardParser::sendTextLink: can not find any link for %d:%d\n", linkId.first, linkId.second));
    return false;
  }
  SpringBoardParserInternal::Link const &link=lIt->second;
  listener->setFont(link.m_font);

  MWAWEntry const &entry=link.m_texts[isTop ? 0 : 1];
  if (!entry.valid()) {
    MWAW_DEBUG_MSG(("SpringBoardParser::sendTextLink: can not find text for link for %d:%d\n", linkId.first, linkId.second));
    return false;
  }
  input->seek(entry.begin(), librevenge::RVNG_SEEK_SET);
  for (long i=0; i<entry.length(); ++i) {
    unsigned char c=(unsigned char)(input->readLong(1));
    if (c == 0x9)
      listener->insertTab();
    else if (c == 0xd)
      listener->insertEOL();
    else if (c<0x1f) {
      MWAW_DEBUG_MSG(("SpringBoardParser::sendTextLink: find bad character %d\n", int(c)));
    }
    else
      listener->insertCharacter(c);
  }
  return true;
}

bool SpringBoardParser::sendTextShape(std::pair<int,int> const &pageId, int minParagraph, int maxParagraph)
{
  auto input=getInput();
  auto listener=getGraphicListener();
  if (!input || !listener) {
    MWAW_DEBUG_MSG(("SpringBoardParser::sendTextShape: can not find the listener\n"));
    return false;
  }
  auto sIt=m_state->m_idToShapeMap.find(pageId);
  if (sIt==m_state->m_idToShapeMap.end()) {
    MWAW_DEBUG_MSG(("SpringBoardParser::sendTextShape: can not find any shape for %d:%d\n", pageId.first, pageId.second));
    return false;
  }
  SpringBoardParserInternal::Shape const &shape=sIt->second;
  if (shape.m_paragraphs.empty() || minParagraph>=int(shape.m_paragraphs.size()))
    return true;
  if (minParagraph<0) minParagraph=0;
  if (maxParagraph<minParagraph || maxParagraph>=int(shape.m_paragraphs.size()))
    maxParagraph=int(shape.m_paragraphs.size()-1);
  int textZoneId=-1;
  for (int p=0; p<minParagraph; ++p) {
    auto const &para = shape.m_paragraphs[size_t(p)];
    if (para.m_textZoneId!=-1)
      textZoneId=para.m_textZoneId;
  }
  for (int p=minParagraph; p<=maxParagraph; ++p) {
    auto const &para = shape.m_paragraphs[size_t(p)];
    auto const &pIt=m_state->m_idToParagraphMap.find(para.m_paragraphStyle);
    if (pIt==m_state->m_idToParagraphMap.end()) {
      MWAW_DEBUG_MSG(("SpringBoardParser::sendTextShape: unknown paragraph=%d\n", para.m_paragraphStyle));
    }
    else
      listener->setParagraph(pIt->second);
    if (para.m_textZoneId!=-1)
      textZoneId=para.m_textZoneId;
    if (textZoneId==-1)
      continue;
    auto const &tIt=m_state->m_idToTextEntriesMap.find(textZoneId);
    if (tIt==m_state->m_idToTextEntriesMap.end() || !tIt->second.valid()) {
      MWAW_DEBUG_MSG(("SpringBoardParser::sendTextShape: unknown text id=%d\n", textZoneId));
      continue;
    }

    // now retrieve the font styles
    input->seek(para.m_charEntry.begin(), librevenge::RVNG_SEEK_SET);
    std::map<int, MWAWFont> posToCharMap;
    int cPos=0;
    libmwaw::DebugStream f;
    for (int i=0; i<para.m_numCharStyles; ++i) {
      long pos=input->tell();
      f.str("");
      f << "Shape-char[" << i << "]:";
      f << "cPos=" << cPos << ",";
      int newPos=cPos+int(input->readLong(2));
      MWAWFont font;
      readFont(font,1);
      int val=int(input->readLong(2));
      if (val) {
        MWAW_DEBUG_MSG(("SpringBoardParser::sendTextShape: unknown styles=%d\n", val));
        f << "#f0=" << val << ",";
      }
      val=int(input->readLong(1));
      if (val>=0 && val<63)
        font.setDeltaLetterSpacing(float(val));
      else {
        MWAW_DEBUG_MSG(("SpringBoardParser::sendTextShape: unexpected char spacing=%d\n", val));
        f << "##char[spacing]=" << val << ",";
      }
      f << font.getDebugString(getFontConverter())  << ",";
      input->seek(pos+10, librevenge::RVNG_SEEK_SET);
      posToCharMap[cPos]=font;
      cPos=newPos;
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
    }

    MWAWEntry const textZone=tIt->second;
    input->seek(textZone.begin()+para.m_firstChar, librevenge::RVNG_SEEK_SET);
    for (int l=0; l<para.m_numChars; ++l) {
      auto cIt=posToCharMap.find(l);
      if (cIt!=posToCharMap.end())
        listener->setFont(cIt->second);
      unsigned char c=(unsigned char)(input->readLong(1));
      if (c == 0x9)
        listener->insertTab();
      else if (c == 0xd)
        listener->insertEOL();
      else if (c<0x1f) {
        MWAW_DEBUG_MSG(("SpringBoardParser::sendTextShape: find bad character %d\n", int(c)));
      }
      else
        listener->insertCharacter(c);
    }
  }
  return true;
}

bool SpringBoardParser::sendHeaderFooter(int hfId)
{
  auto input=getInput();
  auto listener=getGraphicListener();
  if (!input || !listener) {
    MWAW_DEBUG_MSG(("SpringBoardParser::sendHeaderFooter: can not find the listener\n"));
    return false;
  }
  auto hfIt=m_state->m_pageIdToHeaderFooterMap.find(hfId);
  if (hfIt==m_state->m_pageIdToHeaderFooterMap.end()) {
    MWAW_DEBUG_MSG(("SpringBoardParser::sendHeaderFooter: can not find the header footer for id=%d\n", hfId));
    return false;
  }
  auto const &hf=hfIt->second;
  listener->setFont(hf.m_font);
  MWAWParagraph para; // FIXME: this does nothing, find another way to send center and right header/footer
  if (hf.m_type==1 || hf.m_type==4)
    para.m_justify = MWAWParagraph::JustificationCenter;
  else if (hf.m_type==2 || hf.m_type==5)
    para.m_justify = MWAWParagraph::JustificationRight;
  listener->setParagraph(para);
  if (hf.m_zones[0].valid()) { // prefix
    input->seek(hf.m_zones[0].begin(), librevenge::RVNG_SEEK_SET);
    for (long l=0; l<hf.m_zones[0].length(); ++l) {
      unsigned char c=(unsigned char)(input->readLong(1));
      if (c == 0x9)
        listener->insertTab();
      else if (c == 0xd)
        listener->insertEOL();
      else if (c<0x1f) {
        MWAW_DEBUG_MSG(("SpringBoardParser::sendHeaderFooter: find bad character %d\n", int(c)));
      }
      else
        listener->insertCharacter(c);
    }
  }
  MWAWField pageNumber(MWAWField::PageNumber);
  switch (hf.m_type) {
  case 1:
    pageNumber.m_numberingType=libmwaw::LOWERCASE_ROMAN;
    break;
  case 2:
    pageNumber.m_numberingType=libmwaw::UPPERCASE_ROMAN;
    break;
  case 3:
    pageNumber.m_numberingType=libmwaw::LOWERCASE;
    break;
  case 4:
    pageNumber.m_numberingType=libmwaw::UPPERCASE;
    break;
  default: // also 5-7 full number minuscule, majuscule, title
    break;
  }
  if (pageNumber.m_type!=8)
    listener->insertField(pageNumber);
  if (hf.m_zones[1].valid()) { // suffix
    input->seek(hf.m_zones[1].begin(), librevenge::RVNG_SEEK_SET);
    for (long l=0; l<hf.m_zones[1].length(); ++l) {
      unsigned char c=(unsigned char)(input->readLong(1));
      if (c == 0x9)
        listener->insertTab();
      else if (c == 0xd)
        listener->insertEOL();
      else if (c<0x1f) {
        MWAW_DEBUG_MSG(("SpringBoardParser::sendHeaderFooter: find bad character %d\n", int(c)));
      }
      else
        listener->insertCharacter(c);
    }
  }
  return true;
}


////////////////////////////////////////////////////////////
// read shapes
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// read the header
////////////////////////////////////////////////////////////
bool SpringBoardParser::checkHeader(MWAWHeader *header, bool strict)
{
  *m_state = SpringBoardParserInternal::State();
  MWAWInputStreamPtr input = getInput();
  if (!input || !input->hasDataFork() || !input->checkPosition(128))
    return false;

  libmwaw::DebugStream f;
  f << "FileHeader:";
  input->seek(0, librevenge::RVNG_SEEK_SET);
  if (input->readULong(2)!=0xcd90)
    return false;
  for (int i=0; i<2; ++i) {
    int val=int(input->readLong(1));
    if (val==i) continue;
    if (i==0)
      f << "rev" << val << ","; // look like a counter (modulo 10)
    else
      f << "f" << i << "=" << val << ",";
  }
  if ((input->readULong(2)&0xff00)!=0) return false;

  if (strict) {
    input->seek(20, librevenge::RVNG_SEEK_SET);
    for (int i=0; i<10; ++i) {
      long beg=long(input->readULong(4));
      long len=long(input->readULong(2));
      if (!len)
        continue;
      if (beg<=0 || beg+len<=0 || !input->checkPosition(beg+len)) {
        MWAW_DEBUG_MSG(("SpringBoardParser::checkHeader: find unexpected position: %d\n", int(i)));
        f << "###";
        ascii().addPos(0);
        ascii().addNote(f.str().c_str());

        return false;
      }
    }
  }

  ascii().addPos(0);
  ascii().addNote(f.str().c_str());
  if (header)
    header->reset(MWAWDocument::MWAW_T_SPRINGBOARD_PUBLISHER, 2, MWAWDocument::MWAW_K_DRAW);
  input->seek(4, librevenge::RVNG_SEEK_SET);

  return true;
}

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